diff --git a/sapl/api/deprecated.py b/sapl/api/deprecated.py index e38ed3065..1b5fb84e5 100644 --- a/sapl/api/deprecated.py +++ b/sapl/api/deprecated.py @@ -1,11 +1,20 @@ +import logging import logging from django.contrib.contenttypes.models import ContentType from django.db.models import Q +from django.db.models import Q +from django.forms.fields import CharField, MultiValueField +from django.forms.widgets import MultiWidget, TextInput from django.http import Http404 +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _ +from django_filters.filters import CharFilter, ModelChoiceFilter, DateFilter from django_filters.rest_framework.backends import DjangoFilterBackend +from django_filters.rest_framework.filterset import FilterSet +from rest_framework import serializers from rest_framework import serializers from rest_framework.generics import ListAPIView from rest_framework.mixins import ListModelMixin, RetrieveModelMixin @@ -13,14 +22,231 @@ from rest_framework.permissions import (IsAuthenticated, IsAuthenticatedOrReadOnly, AllowAny) from rest_framework.viewsets import GenericViewSet -from sapl.api.forms import (AutorChoiceFilterSet, AutoresPossiveisFilterSet, - AutorSearchForFieldFilterSet) from sapl.api.serializers import ModelChoiceSerializer, AutorSerializer,\ ChoiceSerializer from sapl.base.models import TipoAutor, Autor, CasaLegislativa from sapl.materia.models import MateriaLegislativa +from sapl.parlamentares.models import Legislatura from sapl.sessao.models import SessaoPlenaria, OrdemDia from sapl.utils import SaplGenericRelation +from sapl.utils import generic_relations_for_model + + +class SaplGenericRelationSearchFilterSet(FilterSet): + q = CharFilter(method='filter_q') + + def filter_q(self, queryset, name, 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 generic_relations_for_model(self._meta.model): + sgr = gr[1] + for item in sgr: + if item.related_model != self._meta.model: + + 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]) + ) + # if len(field) == 3 and field[2](qtext) is not + # None: + q_fs = q_fs | Q(**{'%s__%s%s' % ( + item.related_query_name(), + field[0], + field[1]): qtext if len(field) == 2 + else field[2](qtext)}) + + q = q & q_fs + + if q: + queryset = queryset.filter(q).order_by(*order_by) + + return queryset + + +class SearchForFieldWidget(MultiWidget): + + def decompress(self, value): + if value is None: + return [None, None] + return value + + def __init__(self, attrs=None): + widgets = (TextInput, TextInput) + MultiWidget.__init__(self, widgets, attrs) + + +class SearchForFieldField(MultiValueField): + widget = SearchForFieldWidget + + def __init__(self, *args, **kwargs): + fields = ( + CharField(), + CharField()) + super(SearchForFieldField, self).__init__(fields, *args, **kwargs) + + def compress(self, parameters): + if parameters: + return parameters + return None + + +class SearchForFieldFilter(CharFilter): + field_class = SearchForFieldField + + +class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet): + q = CharFilter(method='filter_q') + tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) + + class Meta: + model = Autor + fields = ['q', + 'tipo', + 'nome', ] + + def filter_q(self, queryset, name, value): + return super().filter_q( + queryset, name, value).distinct('nome').order_by('nome') + + +class AutorSearchForFieldFilterSet(AutorChoiceFilterSet): + q = SearchForFieldFilter(method='filter_q') + + class Meta(AutorChoiceFilterSet.Meta): + pass + + def filter_q(self, queryset, name, value): + + value[0] = value[0].split(',') + value[1] = value[1].split(',') + + params = {} + for key, v in list(zip(value[0], value[1])): + if v in ['True', 'False']: + v = '1' if v == 'True' else '0' + params[key] = v + return queryset.filter(**params).distinct('nome').order_by('nome') + + +class AutoresPossiveisFilterSet(FilterSet): + logger = logging.getLogger(__name__) + data_relativa = DateFilter(method='filter_data_relativa') + tipo = CharFilter(method='filter_tipo') + + class Meta: + model = Autor + fields = ['data_relativa', 'tipo', ] + + def filter_data_relativa(self, queryset, name, value): + return queryset + + def filter_tipo(self, queryset, name, value): + + try: + self.logger.debug( + "Tentando obter TipoAutor correspondente à pk {}.".format(value)) + tipo = TipoAutor.objects.get(pk=value) + except: + self.logger.error("TipoAutor(pk={}) inexistente.".format(value)) + raise serializers.ValidationError(_('Tipo de Autor inexistente.')) + + qs = queryset.filter(tipo=tipo) + + return qs + + @property + def qs(self): + qs = super().qs + + data_relativa = self.form.cleaned_data['data_relativa'] \ + if 'data_relativa' in self.form.cleaned_data else None + + tipo = self.form.cleaned_data['tipo'] \ + if 'tipo' in self.form.cleaned_data else None + + if not tipo: + return qs + + tipo = TipoAutor.objects.get(pk=tipo) + if not tipo.content_type: + return qs + + filter_for_model = 'filter_%s' % tipo.content_type.model + + if not hasattr(self, filter_for_model): + return qs + + if not data_relativa: + data_relativa = timezone.now() + + return getattr(self, filter_for_model)(qs, data_relativa).distinct() + + def filter_parlamentar(self, queryset, data_relativa): + # não leva em conta afastamentos + legislatura_relativa = Legislatura.objects.filter( + data_inicio__lte=data_relativa, + data_fim__gte=data_relativa).first() + + q = Q( + parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa, + parlamentar_set__mandato__data_fim_mandato__isnull=True) | Q( + parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa, + parlamentar_set__mandato__data_fim_mandato__gte=data_relativa) + + if legislatura_relativa.atual(): + q = q & Q(parlamentar_set__ativo=True) + + return queryset.filter(q) + + def filter_comissao(self, queryset, data_relativa): + return queryset.filter( + Q(comissao_set__data_extincao__isnull=True, + comissao_set__data_fim_comissao__isnull=True) | + Q(comissao_set__data_extincao__gte=data_relativa, + comissao_set__data_fim_comissao__isnull=True) | + Q(comissao_set__data_extincao__gte=data_relativa, + comissao_set__data_fim_comissao__isnull=True) | + Q(comissao_set__data_extincao__isnull=True, + comissao_set__data_fim_comissao__gte=data_relativa) | + Q(comissao_set__data_extincao__gte=data_relativa, + comissao_set__data_fim_comissao__gte=data_relativa), + comissao_set__data_criacao__lte=data_relativa) + + def filter_frente(self, queryset, data_relativa): + return queryset.filter( + Q(frente_set__data_extincao__isnull=True) | + Q(frente_set__data_extincao__gte=data_relativa), + frente_set__data_criacao__lte=data_relativa) + + def filter_bancada(self, queryset, data_relativa): + return queryset.filter( + Q(bancada_set__data_extincao__isnull=True) | + Q(bancada_set__data_extincao__gte=data_relativa), + bancada_set__data_criacao__lte=data_relativa) + + def filter_bloco(self, queryset, data_relativa): + return queryset.filter( + Q(bloco_set__data_extincao__isnull=True) | + Q(bloco_set__data_extincao__gte=data_relativa), + bloco_set__data_criacao__lte=data_relativa) + + def filter_orgao(self, queryset, data_relativa): + # na implementação, não havia regras a implementar para orgao + return queryset class AutorChoiceSerializer(ModelChoiceSerializer): diff --git a/sapl/api/forms.py b/sapl/api/forms.py index 0c8a1889f..7cb249ff3 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -1,231 +1,65 @@ -import logging - -from django.db.models import Q -from django.forms.fields import CharField, MultiValueField -from django.forms.widgets import MultiWidget, TextInput -from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ -from django_filters.filters import CharFilter, ModelChoiceFilter, DateFilter +from django.db.models.fields.files import FileField +from django.template.defaultfilters import capfirst +import django_filters +from django_filters.filters import CharFilter, NumberFilter from django_filters.rest_framework.filterset import FilterSet -from rest_framework import serializers - -from sapl.base.models import Autor, TipoAutor -from sapl.parlamentares.models import Legislatura -from sapl.utils import generic_relations_for_model - - -class SaplGenericRelationSearchFilterSet(FilterSet): - q = CharFilter(method='filter_q') - - def filter_q(self, queryset, name, 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 generic_relations_for_model(self._meta.model): - sgr = gr[1] - for item in sgr: - if item.related_model != self._meta.model: - - 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]) - ) - # if len(field) == 3 and field[2](qtext) is not - # None: - q_fs = q_fs | Q(**{'%s__%s%s' % ( - item.related_query_name(), - field[0], - field[1]): qtext if len(field) == 2 - else field[2](qtext)}) - - q = q & q_fs - - if q: - queryset = queryset.filter(q).order_by(*order_by) - - return queryset - - -class SearchForFieldWidget(MultiWidget): +from django_filters.utils import resolve_field +from sapl.sessao.models import SessaoPlenaria - def decompress(self, value): - if value is None: - return [None, None] - return value - def __init__(self, attrs=None): - widgets = (TextInput, TextInput) - MultiWidget.__init__(self, widgets, attrs) +class SaplFilterSetMixin(FilterSet): - -class SearchForFieldField(MultiValueField): - widget = SearchForFieldWidget - - def __init__(self, *args, **kwargs): - fields = ( - CharField(), - CharField()) - super(SearchForFieldField, self).__init__(fields, *args, **kwargs) - - def compress(self, parameters): - if parameters: - return parameters - return None - - -class SearchForFieldFilter(CharFilter): - field_class = SearchForFieldField - - -class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet): - q = CharFilter(method='filter_q') - tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) + o = CharFilter(method='filter_o') class Meta: - model = Autor - fields = ['q', - 'tipo', - 'nome', ] - - def filter_q(self, queryset, name, value): - return super().filter_q( - queryset, name, value).distinct('nome').order_by('nome') - - -class AutorSearchForFieldFilterSet(AutorChoiceFilterSet): - q = SearchForFieldFilter(method='filter_q') - - class Meta(AutorChoiceFilterSet.Meta): - pass - - def filter_q(self, queryset, name, value): - - value[0] = value[0].split(',') - value[1] = value[1].split(',') - - params = {} - for key, v in list(zip(value[0], value[1])): - if v in ['True', 'False']: - v = '1' if v == 'True' else '0' - params[key] = v - return queryset.filter(**params).distinct('nome').order_by('nome') - - -class AutoresPossiveisFilterSet(FilterSet): - logger = logging.getLogger(__name__) - data_relativa = DateFilter(method='filter_data_relativa') - tipo = CharFilter(method='filter_tipo') - - class Meta: - model = Autor - fields = ['data_relativa', 'tipo', ] - - def filter_data_relativa(self, queryset, name, value): - return queryset - - def filter_tipo(self, queryset, name, value): - + fields = '__all__' + filter_overrides = { + FileField: { + 'filter_class': django_filters.CharFilter, + 'extra': lambda f: { + 'lookup_expr': 'exact', + }, + }, + } + + def filter_o(self, queryset, name, value): try: - self.logger.debug( - "Tentando obter TipoAutor correspondente à pk {}.".format(value)) - tipo = TipoAutor.objects.get(pk=value) + return queryset.order_by( + *map(str.strip, value.split(','))) except: - self.logger.error("TipoAutor(pk={}) inexistente.".format(value)) - raise serializers.ValidationError(_('Tipo de Autor inexistente.')) - - qs = queryset.filter(tipo=tipo) - - return qs - - @property - def qs(self): - qs = super().qs - - data_relativa = self.form.cleaned_data['data_relativa'] \ - if 'data_relativa' in self.form.cleaned_data else None - - tipo = self.form.cleaned_data['tipo'] \ - if 'tipo' in self.form.cleaned_data else None - - if not tipo: - return qs - - tipo = TipoAutor.objects.get(pk=tipo) - if not tipo.content_type: - return qs - - filter_for_model = 'filter_%s' % tipo.content_type.model - - if not hasattr(self, filter_for_model): - return qs - - if not data_relativa: - data_relativa = timezone.now() - - return getattr(self, filter_for_model)(qs, data_relativa).distinct() - - def filter_parlamentar(self, queryset, data_relativa): - # não leva em conta afastamentos - legislatura_relativa = Legislatura.objects.filter( - data_inicio__lte=data_relativa, - data_fim__gte=data_relativa).first() - - q = Q( - parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa, - parlamentar_set__mandato__data_fim_mandato__isnull=True) | Q( - parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa, - parlamentar_set__mandato__data_fim_mandato__gte=data_relativa) - - if legislatura_relativa.atual(): - q = q & Q(parlamentar_set__ativo=True) - - return queryset.filter(q) + return queryset + + @classmethod + def filter_for_field(cls, f, name, lookup_expr='exact'): + # Redefine método estático para ignorar filtro para + # fields que não possuam lookup_expr informado + f, lookup_type = resolve_field(f, lookup_expr) + + default = { + 'field_name': name, + 'label': capfirst(f.verbose_name), + 'lookup_expr': lookup_expr + } + + filter_class, params = cls.filter_for_lookup( + f, lookup_type) + default.update(params) + if filter_class is not None: + return filter_class(**default) + return None - def filter_comissao(self, queryset, data_relativa): - return queryset.filter( - Q(comissao_set__data_extincao__isnull=True, - comissao_set__data_fim_comissao__isnull=True) | - Q(comissao_set__data_extincao__gte=data_relativa, - comissao_set__data_fim_comissao__isnull=True) | - Q(comissao_set__data_extincao__gte=data_relativa, - comissao_set__data_fim_comissao__isnull=True) | - Q(comissao_set__data_extincao__isnull=True, - comissao_set__data_fim_comissao__gte=data_relativa) | - Q(comissao_set__data_extincao__gte=data_relativa, - comissao_set__data_fim_comissao__gte=data_relativa), - comissao_set__data_criacao__lte=data_relativa) - def filter_frente(self, queryset, data_relativa): - return queryset.filter( - Q(frente_set__data_extincao__isnull=True) | - Q(frente_set__data_extincao__gte=data_relativa), - frente_set__data_criacao__lte=data_relativa) +class SessaoPlenariaFilterSet(SaplFilterSetMixin): + year = NumberFilter(method='filter_year') + month = NumberFilter(method='filter_month') - def filter_bancada(self, queryset, data_relativa): - return queryset.filter( - Q(bancada_set__data_extincao__isnull=True) | - Q(bancada_set__data_extincao__gte=data_relativa), - bancada_set__data_criacao__lte=data_relativa) + class Meta(SaplFilterSetMixin.Meta): + model = SessaoPlenaria - def filter_bloco(self, queryset, data_relativa): - return queryset.filter( - Q(bloco_set__data_extincao__isnull=True) | - Q(bloco_set__data_extincao__gte=data_relativa), - bloco_set__data_criacao__lte=data_relativa) + def filter_year(self, queryset, name, value): + qs = queryset.filter(data_inicio__year=value) + return qs - def filter_orgao(self, queryset, data_relativa): - # na implementação, não havia regras a implementar para orgao - return queryset + def filter_month(self, queryset, name, value): + qs = queryset.filter(data_inicio__month=value) + return qs diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py index ac95cd146..a9ce737c1 100644 --- a/sapl/api/serializers.py +++ b/sapl/api/serializers.py @@ -1,9 +1,15 @@ from django.conf import settings from rest_framework import serializers +from rest_framework.relations import StringRelatedField from sapl.base.models import Autor, CasaLegislativa +class IntRelatedField(StringRelatedField): + def to_representation(self, value): + return int(value) + + class ChoiceSerializer(serializers.Serializer): value = serializers.SerializerMethodField() text = serializers.SerializerMethodField() diff --git a/sapl/api/views.py b/sapl/api/views.py index df47c4f0d..769196c76 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -18,11 +18,27 @@ from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet +from sapl.api.forms import SaplFilterSetMixin from sapl.api.permissions import SaplModelPermissions +from sapl.api.serializers import ChoiceSerializer from sapl.base.models import Autor, AppConfig, DOC_ADM_OSTENSIVO from sapl.materia.models import Proposicao from sapl.parlamentares.models import Parlamentar -from sapl.utils import models_with_gr_for_model +from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria + + +class BusinessRulesNotImplementedMixin: + def create(self, request, *args, **kwargs): + raise Exception(_("POST Create não implementado")) + + def put(self, request, *args, **kwargs): + raise Exception(_("PUT Update não implementado")) + + def patch(self, request, *args, **kwargs): + raise Exception(_("PATCH Partial Update não implementado")) + + def delete(self, request, *args, **kwargs): + raise Exception(_("DELETE Delete não implementado")) class SaplApiViewSetConstrutor(ModelViewSet): @@ -74,47 +90,9 @@ class SaplApiViewSetConstrutor(ModelViewSet): # Define uma classe padrão para filtro caso não tenha sido # criada a classe sapl.api.forms.{model}FilterSet - class SaplFilterSet(FilterSet): - - o = CharFilter(method='filter_o') - - class Meta: + class SaplFilterSet(SaplFilterSetMixin): + class Meta(SaplFilterSetMixin.Meta): model = _model - fields = '__all__' - filter_overrides = { - FileField: { - 'filter_class': django_filters.CharFilter, - 'extra': lambda f: { - 'lookup_expr': 'exact', - }, - }, - } - - def filter_o(self, queryset, name, value): - try: - return queryset.order_by( - *map(str.strip, value.split(','))) - except: - return queryset - - @classmethod - def filter_for_field(cls, f, name, lookup_expr='exact'): - # Redefine método estático para ignorar filtro para - # fields que não possuam lookup_expr informado - f, lookup_type = resolve_field(f, lookup_expr) - - default = { - 'field_name': name, - 'label': capfirst(f.verbose_name), - 'lookup_expr': lookup_expr - } - - filter_class, params = cls.filter_for_lookup( - f, lookup_type) - default.update(params) - if filter_class is not None: - return filter_class(**default) - return None # Define uma classe padrão ModelViewSet de DRF class ModelSaplViewSet(cls): @@ -420,7 +398,8 @@ class _DocumentoAcessorioAdministrativoViewSet( class _TramitacaoAdministrativoViewSet( - SaplSetViews['protocoloadm']['tramitacaoadministrativo']): + SaplSetViews['protocoloadm']['tramitacaoadministrativo'], + BusinessRulesNotImplementedMixin): # TODO: Implementar regras de manutenção das tramitações de docs adms permission_classes = ( @@ -433,17 +412,16 @@ class _TramitacaoAdministrativoViewSet( qs = qs.exclude(documento__restrito=True) return qs - def create(self, request, *args, **kwargs): - raise Exception(_("POST Create não implementado")) - def put(self, request, *args, **kwargs): - raise Exception(_("PUT Update não implementado")) +class _SessaoPlenariaViewSet( + SaplSetViews['sessao']['sessaoplenaria']): - def patch(self, request, *args, **kwargs): - raise Exception(_("PATCH Partial Update não implementado")) + @action(detail=False) + def years(self, request, *args, **kwargs): + years = choice_anos_com_sessaoplenaria() - def delete(self, request, *args, **kwargs): - raise Exception(_("DELETE Delete não implementado")) + serializer = ChoiceSerializer(years, many=True) + return Response(serializer.data) SaplSetViews['base']['autor'] = _AutorViewSet.build_class_with_actions() @@ -455,3 +433,5 @@ SaplSetViews['parlamentares']['parlamentar'] = _ParlamentarViewSet SaplSetViews['protocoloadm']['documentoadministrativo'] = _DocumentoAdministrativoViewSet SaplSetViews['protocoloadm']['documentoacessorioadministrativo'] = _DocumentoAcessorioAdministrativoViewSet SaplSetViews['protocoloadm']['tramitacaoadministrativo'] = _TramitacaoAdministrativoViewSet + +SaplSetViews['sessao']['sessaoplenaria'] = _SessaoPlenariaViewSet