diff --git a/sapl/api/forms.py b/sapl/api/forms.py index e69de29bb..57fb48bdc 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -0,0 +1,81 @@ +from django.contrib.contenttypes.fields import GenericRel +from django.db.models import Q +from django_filters.filters import MethodFilter, ModelChoiceFilter +from rest_framework.filters import FilterSet + +from sapl.base.models import Autor, TipoAutor +from sapl.utils import SaplGenericRelation + + +def autores_models_generic_relations(): + models_of_generic_relations = list(map( + lambda x: x.related_model, + filter( + lambda obj: obj.is_relation and + hasattr(obj, 'field') and + isinstance(obj, GenericRel), + + Autor._meta.get_fields(include_hidden=True)) + )) + + models = list(map( + lambda x: (x, + list(filter( + lambda field: ( + isinstance( + field, SaplGenericRelation) and + field.related_model == Autor), + x._meta.get_fields(include_hidden=True)))), + models_of_generic_relations + )) + + return models + + +class AutorChoiceFilterSet(FilterSet): + q = MethodFilter() + tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) + + class Meta: + model = Autor + fields = ['q', + 'tipo', + 'nome', ] + + def filter_q(self, queryset, value): + + query = value.split(' ') + if query: + q = Q() + for qtext in query: + if not qtext: + continue + q_fs = Q(nome__icontains=qtext) + + order_by = [] + + for gr in autores_models_generic_relations(): + model = gr[0] + sgr = gr[1] + for item in sgr: + if item.related_model != Autor: + continue + flag_order_by = True + for field in item.fields_search: + if flag_order_by: + flag_order_by = False + order_by.append('%s__%s' % ( + item.related_query_name(), + field[0]) + ) + q_fs = q_fs | Q(**{'%s__%s%s' % ( + item.related_query_name(), + field[0], + field[1]): qtext}) + + q = q & q_fs + + if q: + queryset = queryset.filter(q).order_by(*order_by) + + return queryset diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py index 0cbade069..b07befcf1 100644 --- a/sapl/api/serializers.py +++ b/sapl/api/serializers.py @@ -1,9 +1,79 @@ +from django.contrib.contenttypes.fields import GenericRel from rest_framework import serializers +from sapl.base.models import Autor +from sapl.utils import SaplGenericRelation + class ChoiceSerializer(serializers.Serializer): - pk = serializers.IntegerField() - display = serializers.SerializerMethodField() + value = serializers.SerializerMethodField() + text = serializers.SerializerMethodField() + + def get_text(self, obj): + return obj[1] + + def get_value(self, obj): + return obj[0] + + +class AutorChoiceSerializer(ChoiceSerializer): + + def get_text(self, obj): + return obj.nome + + def get_value(self, obj): + return obj.id + + class Meta: + model = Autor + fields = ['id', 'tipo', 'nome', 'object_id', 'autor_related', 'user'] + +# Models que apontaram uma GenericRelation com Autor + + +def autores_models_generic_relations(): + models_of_generic_relations = list(map( + lambda x: x.related_model, + filter( + lambda obj: obj.is_relation and + hasattr(obj, 'field') and + isinstance(obj, GenericRel), + + Autor._meta.get_fields(include_hidden=True)) + )) + + models = list(map( + lambda x: (x, + list(filter( + lambda field: ( + isinstance( + field, SaplGenericRelation) and + field.related_model == Autor), + x._meta.get_fields(include_hidden=True)))), + models_of_generic_relations + )) + + return models + + +class AutorObjectRelatedField(serializers.RelatedField): + + def to_representation(self, value): + + return str(value) + + for gr in autores_models_generic_relations(): + if isinstance(value, gr[0]): + verbose_name = gr[0]._meta.verbose_name + fields_search = gr[1][0].fields_search + + raise Exception(_('Erro na seleção de autor')) + + +class AutorSerializer(serializers.ModelSerializer): + + autor_related = AutorObjectRelatedField(read_only=True) - def get_display(self, obj): - return str(obj) + class Meta: + model = Autor + fields = ['id', 'tipo', 'nome', 'object_id', 'autor_related', 'user'] diff --git a/sapl/api/urls.py b/sapl/api/urls.py index 3c5b6a265..324a51b66 100644 --- a/sapl/api/urls.py +++ b/sapl/api/urls.py @@ -1,18 +1,24 @@ -from django.conf.urls import url - -from sapl.api.views import TipoAutorContentOfModelContentTypeView +from django.conf.urls import url, include +from sapl.api.views import AutorListView from .apps import AppConfig + app_name = AppConfig.name # router = DefaultRouter() -urlpatterns = [ - url(r'^autor/possiveis-pelo-tipo/(?P[0-9]+)$', - TipoAutorContentOfModelContentTypeView.as_view(), - name='autores_possiveis_pelo_tipo'), +# urlpatterns += router.urls + + +urlpatterns_api = [ + # url(r'^$', api_root), + url(r'^autor', + AutorListView.as_view(), + name='autor_list'), ] -# urlpatterns += router.urls +urlpatterns = [ + url(r'^api/', include(urlpatterns_api)) +] diff --git a/sapl/api/views.py b/sapl/api/views.py index 3e839127c..42cfa847e 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -1,50 +1,109 @@ + from django.db.models import Q from django.http import Http404 -from rest_framework.generics import ListAPIView, get_object_or_404 +from rest_framework.filters import DjangoFilterBackend +from rest_framework.generics import ListAPIView from rest_framework.permissions import IsAuthenticated -from rest_framework.viewsets import ModelViewSet -from sapl.api.serializers import ChoiceSerializer +from sapl.api.forms import AutorChoiceFilterSet +from sapl.api.serializers import ChoiceSerializer, AutorSerializer,\ + AutorChoiceSerializer from sapl.base.models import Autor, TipoAutor from sapl.utils import SaplGenericRelation -class TipoAutorContentOfModelContentTypeView(ListAPIView): - serializer_class = ChoiceSerializer +class AutorListView(ListAPIView): + """ + Listagem de Autores com filtro para autores já cadastrados + e/ou possíveis autores. + + - tipo - chave primária do Tipo de Autor a ser filtrado + - provaveis - variável sem relevância de valor, porém, sua presença + faz com que a AutorListView + mostre a lista de provaveis Autores armazenados segundo o + ContentType associado ao Tipo de Autor via relacionamento + genérico. + - q - busca textual no nome do Autor ou em fields_search + declarados no field SaplGenericRelation das GenericFks + + A busca textual acontece via django-filter se não + estiver presente a variável `provaveis`. Em caso + contrário, o django-filter é desativado e a busca é feita + no model do ContentType associado ao tipo. + """ # FIXME aplicar permissão correta de usuário permission_classes = (IsAuthenticated,) - queryset = TipoAutor.objects.all() - model = TipoAutor + serializer_class = AutorSerializer + queryset = Autor.objects.all() + model = Autor + + def get(self, request, *args, **kwargs): + """ + desativa o django-filter se a busca for por provaveis autores + """ + provaveis = 'provaveis' in request.GET + self.filter_class = None if provaveis else AutorChoiceFilterSet + self.filter_backends = [] if provaveis else [DjangoFilterBackend] + self.serializer_class = ChoiceSerializer\ + if provaveis else AutorChoiceSerializer + + return ListAPIView.get(self, request, *args, **kwargs) def get_queryset(self): - queryset = ModelViewSet.get_queryset(self) + queryset = ListAPIView.get_queryset(self) - if not self.kwargs['pk']: - raise Http404() + if self.filter_backends: + return queryset + + params = {'content_type__isnull': False} + + tipo = '' + try: + tipo = int(self.request.GET.get('tipo', '')) + if tipo: + params['id'] = tipo + except: + pass - obj = get_object_or_404(queryset, pk=self.kwargs['pk']) + tipos = TipoAutor.objects.filter(**params) - if not obj.content_type: + if not tipos.exists() and tipo: raise Http404() - q = self.request.GET.get('q', '').strip() + r = [] + for tipo in tipos: + q = self.request.GET.get('q', '').strip() - model_class = obj.content_type.model_class() + model_class = tipo.content_type.model_class() - fields = list(filter( - lambda field: isinstance(field, SaplGenericRelation) and - field.related_model == Autor, - model_class._meta.get_fields(include_hidden=True))) + fields = list(filter( + lambda field: isinstance(field, SaplGenericRelation) and + field.related_model == Autor, + model_class._meta.get_fields(include_hidden=True))) - assert len(fields) == 1 + # retirar assert + assert len(fields) == 1 - fields_search = fields[0].fields_search + qs = model_class.objects.all().order_by( + fields[0].fields_search[0][0]) - if q: q_filter = Q() - for fs in fields_search: - q_filter |= Q(**{'%s__icontains' % fs: q}) + if q: + for item in fields: + if item.related_model != Autor: + continue + q_fs = Q() + for field in item.fields_search: + q_fs = q_fs | Q(**{'%s%s' % ( + field[0], + field[1]): q}) + q_filter = q_filter & q_fs + + qs = qs.filter(q_filter).distinct( + fields[0].fields_search[0][0]) + + qs = qs.values_list('id', fields[0].fields_search[0][0]) - return model_class.objects.filter(q_filter) - else: - return model_class.objects.all() + r += list(qs) + r.sort(key=lambda x: x[1]) + return r diff --git a/sapl/base/forms.py b/sapl/base/forms.py index 7765bb0f2..937f5e382 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -228,9 +228,6 @@ class AutorForm(ModelForm): User = get_user_model() cd = self.cleaned_data - if 'username' not in cd or not cd['username']: - raise ValidationError(_('O username deve ser informado.')) - if 'action_user' not in cd or not cd['action_user']: raise ValidationError(_('Informe se o Autor terá usuário ' 'vinculado para acesso ao Sistema.')) @@ -292,6 +289,10 @@ class AutorForm(ModelForm): '"Criar novo Usuário".') % cd['username']) if cd['action_user'] != 'N': + + if 'username' not in cd or not cd['username']: + raise ValidationError(_('O username deve ser informado.')) + if qs_autor.filter(user__username=cd['username']).exists(): raise ValidationError( _('Já existe um Autor para este usuário.')) @@ -333,7 +334,6 @@ class AutorForm(ModelForm): @transaction.atomic def save(self, commit=False): - print('aqui') autor = super(AutorForm, self).save(commit) user_old = autor.user if autor.user_id else None @@ -349,6 +349,7 @@ class AutorForm(ModelForm): u.set_password(self.cleaned_data['senha']) # Define usuário como ativo em ambiente de desenvolvimento # pode logar sem a necessidade de passar pela validação de email + # troque par False para testar o envio de email em desenvolvimento u.is_active = settings.DEBUG u.save() autor.user = u @@ -384,7 +385,7 @@ class AutorForm(ModelForm): elif self.cleaned_data['status_user'] == 'R': user_old.groups.remove(grupo) - else: + elif user_old: user_old.groups.remove(grupo) return autor diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py index 47321fde6..441eacba6 100644 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -80,14 +80,19 @@ class Comissao(models.Model): choices=YES_NO_CHOICES, verbose_name=_('Comissão Ativa?')) - autor = SaplGenericRelation(Autor, fields_search=('nome', 'sigla')) + autor = SaplGenericRelation(Autor, + related_query_name='comissao_set', + fields_search=( + ('nome', '__icontains'), + ('sigla', '__icontains') + )) class Meta: verbose_name = _('Comissão') verbose_name_plural = _('Comissões') def __str__(self): - return self.nome + return self.sigla + ' - ' + self.nome class Periodo(models.Model): # PeriodoCompComissao diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 8b0574083..e1604a74c 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -33,13 +33,10 @@ from sapl.materia.forms import AnexadaForm, LegislacaoCitadaForm from sapl.norma.models import LegislacaoCitada from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, autor_modal, gerar_hash_arquivo, get_base_url, -<<<<<<< 3276eb12726b741df770d5a6ed2a9a1a83c15849 permissoes_autor, permissoes_materia, - permissoes_protocoloadm, permission_required_for_app) -======= - montar_row_autor, permissoes_autor, permissoes_materia, - permissoes_protocoloadm) ->>>>>>> Conc refatoração no Cada de Autor e Tipos de Autor + permissoes_protocoloadm, permission_required_for_app, + montar_row_autor) + from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, ConfirmarProposicaoForm, DocumentoAcessorioForm, diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 66e72e0cb..2703b2416 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -265,8 +265,15 @@ class Parlamentar(models.Model): # campo conceitual de reversão genérica para o model Autor que dá a # o meio possível de localização de tipos de autores. - autor = SaplGenericRelation(Autor, fields_search=('nome_completo', - 'nome_parlamentar')) + autor = SaplGenericRelation( + Autor, + related_query_name='parlamentar_set', + fields_search=( + # na primeira posição dever ser campo simples sem __ + ('nome_completo', '__icontains'), + ('nome_parlamentar', '__icontains'), + ('filiacao__partido__sigla', '__icontains'), + )) class Meta: verbose_name = _('Parlamentar') @@ -455,8 +462,15 @@ class Frente(models.Model): # campo conceitual de reversão genérica para o model Autor que dá a # o meio possível de localização de tipos de autores. - autor = SaplGenericRelation(Autor, fields_search=('nome', - 'descricao')) + autor = SaplGenericRelation( + Autor, + related_query_name='frente_set', + fields_search=( + ('nome', '__icontains'), + ('descricao', '__icontains'), + ('parlamentares__filiacao__partido__sigla', '__icontains'), + ('parlamentares__filiacao__partido__nome', '__icontains'), + )) class Meta: verbose_name = _('Frente') diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index c1c258690..6add47520 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -1,5 +1,5 @@ -import json from datetime import date, datetime +import json from braces.views import FormValidMessageMixin from django.contrib import messages @@ -31,6 +31,7 @@ from .models import (Autor, DocumentoAcessorioAdministrativo, StatusTramitacaoAdministrativo, TipoDocumentoAdministrativo, TramitacaoAdministrativo) + TipoDocumentoAdministrativoCrud = CrudAux.build( TipoDocumentoAdministrativo, '') @@ -605,11 +606,13 @@ def pesquisa_autores(request): if request.method == 'GET': q = request.GET.get('q', '') - autor = Autor.objects.filter( + """autor = Autor.objects.filter( Q(nome__icontains=q) | Q(parlamentar__nome_parlamentar__icontains=q) | Q(comissao__nome__icontains=q) - ) + )""" + + autor = Autor.objects.filter(nome__icontains=q) autores = [] diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index ba28d3dac..dc375fff7 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -42,8 +42,13 @@ class Bancada(models.Model): # campo conceitual de reversão genérica para o model Autor que dá a # o meio possível de localização de tipos de autores. - autor = SaplGenericRelation(Autor, fields_search=('nome', - 'descricao')) + autor = SaplGenericRelation(Autor, related_query_name='bancada_set', + fields_search=( + ('nome', '__icontains'), + ('descricao', '__icontains'), + ('partido__sigla', '__icontains'), + ('partido__nome', '__icontains'), + )) class Meta: verbose_name = _('Bancada') @@ -350,8 +355,14 @@ class Bloco(models.Model): # campo conceitual de reversão genérica para o model Autor que dá a # o meio possível de localização de tipos de autores. - autor = SaplGenericRelation(Autor, fields_search=('nome', - 'descricao')) + autor = SaplGenericRelation(Autor, + related_query_name='bloco_set', + fields_search=( + ('nome', '__icontains'), + ('descricao', '__icontains'), + ('partidos__sigla', '__icontains'), + ('partidos__nome', '__icontains'), + )) class Meta: verbose_name = _('Bloco') diff --git a/sapl/settings.py b/sapl/settings.py index 59037f791..7112c70cc 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -93,7 +93,7 @@ MIDDLEWARE_CLASSES = ( REST_FRAMEWORK = { "DEFAULT_RENDERER_CLASSES": ( "rest_framework.renderers.JSONRenderer", - # "rest_framework.renderers.BrowsableAPIRenderer", + #"rest_framework.renderers.BrowsableAPIRenderer", ), "DEFAULT_PARSER_CLASSES": ( "rest_framework.parsers.JSONParser", @@ -102,7 +102,6 @@ REST_FRAMEWORK = { "rest_framework.permissions.IsAuthenticated", ), "DEFAULT_PERMISSION_CLASSES": ( - # "rest_framework.permissions.IsAuthenticated", "sapl.api.permissions.DjangoModelPermissions", ), "DEFAULT_AUTHENTICATION_CLASSES": ( diff --git a/sapl/static/js/app.js b/sapl/static/js/app.js index 8cb30f44e..c41407552 100644 --- a/sapl/static/js/app.js +++ b/sapl/static/js/app.js @@ -90,28 +90,25 @@ function autorModal() { $("#pesquisar").click(function() { var query = $("#q").val() - $.get("/protocoloadm/pesquisar-autor?q="+ query, function( - data, status){ - $("#div-resultado").children().remove(); + $.get("/api/autor?q=" + query, function(data, status) { + $("#div-resultado").children().remove(); + if (data.pagination.total_entries == 0) { + $("#selecionar").attr("hidden", "hidden"); + $("#div-resultado").html( + "Nenhum resultado"); + return; + } - if (data.length == 0) { - $("#selecionar").attr("hidden", "hidden"); - $("#div-resultado").html( - "Nenhum resultado"); - return; - } + var select = $( + ''); + data.models.forEach(function(item, index) { + select.append($("