diff --git a/sapl/api/base/__init__.py b/sapl/api/base/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sapl/api/base/forms.py b/sapl/api/base/forms.py new file mode 100644 index 000000000..128b13257 --- /dev/null +++ b/sapl/api/base/forms.py @@ -0,0 +1,147 @@ +from django.db.models import Q +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ +from django_filters.filters import DateFilter, MethodFilter, ModelChoiceFilter +from rest_framework import serializers +from rest_framework.filters import FilterSet + +from sapl.api.forms import SaplGenericRelationSearchFilterSet,\ + SearchForFieldFilter +from sapl.base.models import Autor, TipoAutor +from sapl.parlamentares.models import Legislatura + + +class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet): + q = MethodFilter() + tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) + + class Meta: + model = Autor + fields = ['q', + 'tipo', + 'nome', ] + + def filter_q(self, queryset, value): + return SaplGenericRelationSearchFilterSet.filter_q( + self, queryset, value).distinct('nome').order_by('nome') + + +class AutorSearchForFieldFilterSet(AutorChoiceFilterSet): + q = SearchForFieldFilter() + + class Meta(AutorChoiceFilterSet.Meta): + pass + + def filter_q(self, queryset, 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): + data_relativa = DateFilter(method='filter_data_relativa') + tipo = MethodFilter(required=True) + + class Meta: + model = Autor + fields = ['data_relativa', 'tipo', ] + + def filter_data_relativa(self, queryset, name, value): + return queryset + + def filter_tipo(self, queryset, value): + try: + tipo = TipoAutor.objects.get(pk=value) + except: + 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 + else: + 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 diff --git a/sapl/api/base/serializers.py b/sapl/api/base/serializers.py new file mode 100644 index 000000000..c669e8807 --- /dev/null +++ b/sapl/api/base/serializers.py @@ -0,0 +1,23 @@ +from rest_framework import serializers + +from sapl.api.serializers import ModelChoiceSerializer,\ + ModelChoiceObjectRelatedField +from sapl.base.models import Autor + + +class AutorChoiceSerializer(ModelChoiceSerializer): + + def get_text(self, obj): + return obj.nome + + class Meta: + model = Autor + fields = ['id', 'nome'] + + +class AutorSerializer(serializers.ModelSerializer): + autor_related = ModelChoiceObjectRelatedField(read_only=True) + + class Meta: + model = Autor + fields = '__all__' diff --git a/sapl/api/base/urls.py b/sapl/api/base/urls.py new file mode 100644 index 000000000..b376b86cc --- /dev/null +++ b/sapl/api/base/urls.py @@ -0,0 +1,15 @@ +from django.conf.urls import url +from sapl.api.base.views import AutorListView, AutoresPossiveisListView,\ + AutoresProvaveisListView + +# Não adicione app_name +# app_name = AppConfig.name + + +urlpatterns = [ + url(r'^autor/$', AutorListView.as_view(), name='autor_list'), + url(r'^autor/provaveis', + AutoresProvaveisListView.as_view(), name='autores_provaveis_list'), + url(r'^autor/possiveis', + AutoresPossiveisListView.as_view(), name='autores_possiveis_list'), +] diff --git a/sapl/api/base/views.py b/sapl/api/base/views.py new file mode 100644 index 000000000..37e750dd8 --- /dev/null +++ b/sapl/api/base/views.py @@ -0,0 +1,223 @@ +from django.db.models import Q +from django.http import Http404 +from django.utils.translation import ugettext_lazy as _ +from rest_framework.filters import DjangoFilterBackend +from rest_framework.generics import ListAPIView +from rest_framework.permissions import (IsAuthenticated, + IsAuthenticatedOrReadOnly) + +from sapl.api.base.forms import AutoresPossiveisFilterSet,\ + AutorSearchForFieldFilterSet, AutorChoiceFilterSet +from sapl.api.base.serializers import AutorChoiceSerializer, AutorSerializer +from sapl.api.serializers import (ChoiceSerializer) +from sapl.base.models import Autor, TipoAutor +from sapl.utils import SaplGenericRelation, sapl_logger + + +class AutorListView(ListAPIView): + """ + Listagem de Autores com filtro para autores já cadastrados + e/ou possíveis autores. + + - tr - tipo do resultado + Prepera Lista de Autores para 3 cenários distintos + + - default = 1 + + = 1 -> para (value, text) usados geralmente + em combobox, radiobox, checkbox, etc com pesquisa básica + de Autores feita pelo django-filter + -> processo usado nas pesquisas, o mais usado. + + + = 3 -> Devolve instancias da classe Autor filtradas pelo + django-filter + + - tipo - chave primária do Tipo de Autor a ser filtrado + + - q - busca textual no nome do Autor ou em fields_search + declarados no field SaplGenericRelation das GenericFks + A busca textual acontece via django-filter com a + variável `tr` igual 1 ou 3. Em caso contrário, + o django-filter é desativado e a busca é feita + no model do ContentType associado ao tipo. + + - q_0 / q_1 - q_0 é opcional e quando usado, faz o código ignorar "q"... + + q_0 -> campos lookup a serem filtrados em qualquer Model + que implemente SaplGenericRelation + q_1 -> o valor que será pesquisado no lookup de q_0 + + q_0 e q_1 podem ser separados por ","... isso dará a + possibilidade de filtrar mais de um campo. + + + http://localhost:8000 + /api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=False + /api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=True + /api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=False + /api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=True + + http://localhost:8000 + /api/autor?tr=1 + &q_0=parlamentar_set__nome_parlamentar__icontains, + parlamentar_set__ativo + &q_1=Carvalho,False + /api/autor?tr=1 + &q_0=parlamentar_set__nome_parlamentar__icontains, + parlamentar_set__ativo + &q_1=Carvalho,True + /api/autor?tr=3 + &q_0=parlamentar_set__nome_parlamentar__icontains, + parlamentar_set__ativo + &q_1=Carvalho,False + /api/autor?tr=3 + &q_0=parlamentar_set__nome_parlamentar__icontains, + parlamentar_set__ativo + &q_1=Carvalho,True + + + não importa o campo que vc passe de qualquer dos Models + ligados... é possível ver que models são esses, + na ocasião do commit deste texto, executando: + In [6]: from sapl.utils import models_with_gr_for_model + + In [7]: models_with_gr_for_model(Autor) + Out[7]: + [sapl.parlamentares.models.Parlamentar, + sapl.parlamentares.models.Frente, + sapl.comissoes.models.Comissao, + sapl.materia.models.Orgao, + sapl.sessao.models.Bancada, + sapl.sessao.models.Bloco] + + qualquer atributo destes models podem ser passados + para busca + """ + + TR_AUTOR_CHOICE_SERIALIZER = 1 + TR_AUTOR_SERIALIZER = 3 + + permission_classes = (IsAuthenticatedOrReadOnly,) + queryset = Autor.objects.all() + model = Autor + + filter_class = AutorChoiceFilterSet + filter_backends = (DjangoFilterBackend, ) + serializer_class = AutorChoiceSerializer + + @property + def tr(self): + try: + tr = int(self.request.GET.get + ('tr', AutorListView.TR_AUTOR_CHOICE_SERIALIZER)) + + assert tr in ( + AutorListView.TR_AUTOR_CHOICE_SERIALIZER, + AutorListView.TR_AUTOR_SERIALIZER), sapl_logger.info( + _("Tipo do Resultado a ser fornecido não existe!")) + except: + return AutorListView.TR_AUTOR_CHOICE_SERIALIZER + else: + return tr + + def get(self, request, *args, **kwargs): + + if self.tr == AutorListView.TR_AUTOR_SERIALIZER: + self.serializer_class = AutorSerializer + self.permission_classes = (IsAuthenticated,) + + if self.filter_class and 'q_0' in request.GET: + self.filter_class = AutorSearchForFieldFilterSet + + return ListAPIView.get(self, request, *args, **kwargs) + + +class AutoresProvaveisListView(ListAPIView): + + permission_classes = (IsAuthenticatedOrReadOnly,) + queryset = Autor.objects.all() + model = Autor + + filter_class = None + filter_backends = [] + serializer_class = ChoiceSerializer + + def get_queryset(self): + params = {'content_type__isnull': False} + + tipo = '' + try: + tipo = int(self.request.GET.get('tipo', '')) + if tipo: + params['id'] = tipo + except: + pass + + tipos = TipoAutor.objects.filter(**params) + + if not tipos.exists() and tipo: + raise Http404() + + r = [] + for tipo in tipos: + q = self.request.GET.get('q', '').strip() + + 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 - é um array de SaplGenericRelation que deve possuir o + atributo fields_search. Verifique na documentação da classe + a estrutura de fields_search. + """ + + assert len(fields) >= 1, (_( + 'Não foi encontrado em %(model)s um atributo do tipo ' + 'SaplGenericRelation que use o model %(model_autor)s') % { + 'model': model_class._meta.verbose_name, + 'model_autor': Autor._meta.verbose_name}) + + qs = model_class.objects.all() + + q_filter = 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]).order_by( + fields[0].fields_search[0][0]) + else: + qs = qs.order_by(fields[0].fields_search[0][0]) + + qs = qs.values_list( + 'id', fields[0].fields_search[0][0]) + r += list(qs) + + if tipos.count() > 1: + r.sort(key=lambda x: x[1].upper()) + return r + + +class AutoresPossiveisListView(ListAPIView): + + permission_classes = (IsAuthenticatedOrReadOnly,) + queryset = Autor.objects.all() + model = Autor + + pagination_class = None + + filter_class = AutoresPossiveisFilterSet + serializer_class = AutorChoiceSerializer diff --git a/sapl/api/forms.py b/sapl/api/forms.py index 94bc80de4..542601f90 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -1,15 +1,10 @@ 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 DateFilter, MethodFilter, ModelChoiceFilter -from rest_framework import serializers +from django_filters.filters import MethodFilter from rest_framework.compat import django_filters from rest_framework.filters import FilterSet -from sapl.base.models import Autor, TipoAutor -from sapl.parlamentares.models import Legislatura from sapl.utils import generic_relations_for_model @@ -87,141 +82,3 @@ class SearchForFieldField(MultiValueField): class SearchForFieldFilter(django_filters.filters.MethodFilter): field_class = SearchForFieldField - - -class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet): - q = MethodFilter() - tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) - - class Meta: - model = Autor - fields = ['q', - 'tipo', - 'nome', ] - - def filter_q(self, queryset, value): - return SaplGenericRelationSearchFilterSet.filter_q( - self, queryset, value).distinct('nome').order_by('nome') - - -class AutorSearchForFieldFilterSet(AutorChoiceFilterSet): - q = SearchForFieldFilter() - - class Meta(AutorChoiceFilterSet.Meta): - pass - - def filter_q(self, queryset, 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): - data_relativa = DateFilter(method='filter_data_relativa') - tipo = MethodFilter() - - class Meta: - model = Autor - fields = ['data_relativa', 'tipo', ] - - def filter_data_relativa(self, queryset, name, value): - return queryset - - def filter_tipo(self, queryset, value): - try: - tipo = TipoAutor.objects.get(pk=value) - except: - 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 and not data_relativa: - return qs - - if tipo: - # não precisa de try except, já foi validado em filter_tipo - 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 diff --git a/sapl/api/materia/__init__.py b/sapl/api/materia/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sapl/api/materia/forms.py b/sapl/api/materia/forms.py new file mode 100644 index 000000000..e69de29bb diff --git a/sapl/api/materia/serializers.py b/sapl/api/materia/serializers.py new file mode 100644 index 000000000..796d64a11 --- /dev/null +++ b/sapl/api/materia/serializers.py @@ -0,0 +1,19 @@ + +from rest_framework.filters import DjangoFilterBackend +from rest_framework.mixins import ListModelMixin, RetrieveModelMixin +from rest_framework.permissions import IsAuthenticated +from rest_framework.viewsets import GenericViewSet + +from sapl.api.materia.views import MateriaLegislativaSerializer +from sapl.materia.models import MateriaLegislativa + + +class MateriaLegislativaViewSet(ListModelMixin, + RetrieveModelMixin, + GenericViewSet): + + permission_classes = (IsAuthenticated,) + serializer_class = MateriaLegislativaSerializer + queryset = MateriaLegislativa.objects.all() + filter_backends = (DjangoFilterBackend,) + filter_fields = ('numero', 'ano', 'tipo', ) diff --git a/sapl/api/materia/urls.py b/sapl/api/materia/urls.py new file mode 100644 index 000000000..feea5bf60 --- /dev/null +++ b/sapl/api/materia/urls.py @@ -0,0 +1,13 @@ +from rest_framework.routers import DefaultRouter +from sapl.api.materia.serializers import MateriaLegislativaViewSet + +# Não adicione app_name +# app_name = AppConfig.name + + +router = DefaultRouter() +router.register(r'materia', MateriaLegislativaViewSet) + +urlpatterns = [ + +] diff --git a/sapl/api/materia/views.py b/sapl/api/materia/views.py new file mode 100644 index 000000000..632e25c26 --- /dev/null +++ b/sapl/api/materia/views.py @@ -0,0 +1,9 @@ +from rest_framework import serializers +from sapl.materia.models import MateriaLegislativa + + +class MateriaLegislativaSerializer(serializers.ModelSerializer): + + class Meta: + model = MateriaLegislativa + fields = '__all__' diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py index 9219bf0e4..35758ffb7 100644 --- a/sapl/api/serializers.py +++ b/sapl/api/serializers.py @@ -1,9 +1,5 @@ from rest_framework import serializers -from sapl.base.models import Autor, CasaLegislativa -from sapl.materia.models import MateriaLegislativa -from sapl.sessao.models import OrdemDia, SessaoPlenaria - class ChoiceSerializer(serializers.Serializer): value = serializers.SerializerMethodField() @@ -29,146 +25,3 @@ class ModelChoiceObjectRelatedField(serializers.RelatedField): def to_representation(self, value): return ModelChoiceSerializer(value).data - - -class AutorChoiceSerializer(ModelChoiceSerializer): - - def get_text(self, obj): - return obj.nome - - class Meta: - model = Autor - fields = ['id', 'nome'] - - -class AutorSerializer(serializers.ModelSerializer): - autor_related = ModelChoiceObjectRelatedField(read_only=True) - - class Meta: - model = Autor - fields = '__all__' - - -class MateriaLegislativaSerializer(serializers.ModelSerializer): - - class Meta: - model = MateriaLegislativa - fields = '__all__' - - -class SessaoPlenariaSerializer(serializers.ModelSerializer): - - codReuniao = serializers.SerializerMethodField('get_pk_sessao') - codReuniaoPrincipal = serializers.SerializerMethodField('get_pk_sessao') - txtTituloReuniao = serializers.SerializerMethodField('get_name') - txtSiglaOrgao = serializers.SerializerMethodField('get_sigla_orgao') - txtApelido = serializers.SerializerMethodField('get_name') - txtNomeOrgao = serializers.SerializerMethodField('get_nome_orgao') - codEstadoReuniao = serializers.SerializerMethodField( - 'get_estadoSessaoPlenaria') - txtTipoReuniao = serializers.SerializerMethodField('get_tipo_sessao') - txtObjeto = serializers.SerializerMethodField('get_assunto_sessao') - txtLocal = serializers.SerializerMethodField('get_endereco_orgao') - bolReuniaoConjunta = serializers.SerializerMethodField( - 'get_reuniao_conjunta') - bolHabilitarEventoInterativo = serializers.SerializerMethodField( - 'get_iterativo') - idYoutube = serializers.SerializerMethodField('get_url') - codEstadoTransmissaoYoutube = serializers.SerializerMethodField( - 'get_estadoTransmissaoYoutube') - datReuniaoString = serializers.SerializerMethodField('get_date') - - # Constantes SessaoPlenaria (de 1-9) (apenas 3 serão usados) - SESSAO_FINALIZADA = 4 - SESSAO_EM_ANDAMENTO = 3 - SESSAO_CONVOCADA = 2 - - # Constantes EstadoTranmissaoYoutube (de 0 a 2) - TRANSMISSAO_ENCERRADA = 2 - TRANSMISSAO_EM_ANDAMENTO = 1 - SEM_TRANSMISSAO = 0 - - class Meta: - model = SessaoPlenaria - fields = ( - 'codReuniao', - 'codReuniaoPrincipal', - 'txtTituloReuniao', - 'txtSiglaOrgao', - 'txtApelido', - 'txtNomeOrgao', - 'codEstadoReuniao', - 'txtTipoReuniao', - 'txtObjeto', - 'txtLocal', - 'bolReuniaoConjunta', - 'bolHabilitarEventoInterativo', - 'idYoutube', - 'codEstadoTransmissaoYoutube', - 'datReuniaoString' - ) - - def __init__(self, *args, **kwargs): - super(SessaoPlenariaSerializer, self).__init__(args, kwargs) - - def get_pk_sessao(self, obj): - return obj.pk - - def get_name(self, obj): - return obj.__str__() - - def get_estadoSessaoPlenaria(self, obj): - if obj.finalizada: - return self.SESSAO_FINALIZADA - elif obj.iniciada: - return self.SESSAO_EM_ANDAMENTO - else: - return self.SESSAO_CONVOCADA - - def get_tipo_sessao(self, obj): - return obj.tipo.__str__() - - def get_url(self, obj): - return obj.url_video if obj.url_video else None - - def get_iterativo(self, obj): - return obj.interativa if obj.interativa else False - - def get_date(self, obj): - return "{} {}{}".format( - obj.data_inicio.strftime("%d/%m/%Y"), - obj.hora_inicio, - ":00" - ) - - def get_estadoTransmissaoYoutube(self, obj): - if obj.url_video: - if obj.finalizada: - return self.TRANSMISSAO_ENCERRADA - else: - return self.TRANSMISSAO_EM_ANDAMENTO - else: - return self.SEM_TRANSMISSAO - - def get_assunto_sessao(self, obj): - pauta_sessao = '' - ordem_dia = OrdemDia.objects.filter(sessao_plenaria=obj.pk) - pauta_sessao = ', '.join([i.materia.__str__() for i in ordem_dia]) - - return str(pauta_sessao) - - def get_endereco_orgao(self, obj): - return self.casa().endereco - - def get_reuniao_conjunta(self, obj): - return False - - def get_sigla_orgao(self, obj): - return self.casa().sigla - - def get_nome_orgao(self, obj): - return self.casa().nome - - def casa(self): - casa = CasaLegislativa.objects.first() - return casa diff --git a/sapl/api/sessao/__init__.py b/sapl/api/sessao/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sapl/api/sessao/forms.py b/sapl/api/sessao/forms.py new file mode 100644 index 000000000..e69de29bb diff --git a/sapl/api/sessao/serializers.py b/sapl/api/sessao/serializers.py new file mode 100644 index 000000000..fc183d19a --- /dev/null +++ b/sapl/api/sessao/serializers.py @@ -0,0 +1,138 @@ +from rest_framework import serializers + +from sapl.base.models import CasaLegislativa +from sapl.sessao.models import SessaoPlenaria, OrdemDia + + +class SessaoPlenariaOldSerializer(serializers.ModelSerializer): + + codReuniao = serializers.SerializerMethodField('get_pk_sessao') + codReuniaoPrincipal = serializers.SerializerMethodField('get_pk_sessao') + txtTituloReuniao = serializers.SerializerMethodField('get_name') + txtSiglaOrgao = serializers.SerializerMethodField('get_sigla_orgao') + txtApelido = serializers.SerializerMethodField('get_name') + txtNomeOrgao = serializers.SerializerMethodField('get_nome_orgao') + codEstadoReuniao = serializers.SerializerMethodField( + 'get_estadoSessaoPlenaria') + txtTipoReuniao = serializers.SerializerMethodField('get_tipo_sessao') + txtObjeto = serializers.SerializerMethodField('get_assunto_sessao') + txtLocal = serializers.SerializerMethodField('get_endereco_orgao') + bolReuniaoConjunta = serializers.SerializerMethodField( + 'get_reuniao_conjunta') + bolHabilitarEventoInterativo = serializers.SerializerMethodField( + 'get_iterativo') + idYoutube = serializers.SerializerMethodField('get_url') + codEstadoTransmissaoYoutube = serializers.SerializerMethodField( + 'get_estadoTransmissaoYoutube') + datReuniaoString = serializers.SerializerMethodField('get_date') + + # Constantes SessaoPlenaria (de 1-9) (apenas 3 serão usados) + SESSAO_FINALIZADA = 4 + SESSAO_EM_ANDAMENTO = 3 + SESSAO_CONVOCADA = 2 + + # Constantes EstadoTranmissaoYoutube (de 0 a 2) + TRANSMISSAO_ENCERRADA = 2 + TRANSMISSAO_EM_ANDAMENTO = 1 + SEM_TRANSMISSAO = 0 + + class Meta: + model = SessaoPlenaria + fields = ( + 'codReuniao', + 'codReuniaoPrincipal', + 'txtTituloReuniao', + 'txtSiglaOrgao', + 'txtApelido', + 'txtNomeOrgao', + 'codEstadoReuniao', + 'txtTipoReuniao', + 'txtObjeto', + 'txtLocal', + 'bolReuniaoConjunta', + 'bolHabilitarEventoInterativo', + 'idYoutube', + 'codEstadoTransmissaoYoutube', + 'datReuniaoString' + ) + + def __init__(self, *args, **kwargs): + super(SessaoPlenariaOldSerializer, self).__init__(args, kwargs) + + def get_pk_sessao(self, obj): + return obj.pk + + def get_name(self, obj): + return obj.__str__() + + def get_estadoSessaoPlenaria(self, obj): + if obj.finalizada: + return self.SESSAO_FINALIZADA + elif obj.iniciada: + return self.SESSAO_EM_ANDAMENTO + else: + return self.SESSAO_CONVOCADA + + def get_tipo_sessao(self, obj): + return obj.tipo.__str__() + + def get_url(self, obj): + return obj.url_video if obj.url_video else None + + def get_iterativo(self, obj): + return obj.interativa if obj.interativa else False + + def get_date(self, obj): + return "{} {}{}".format( + obj.data_inicio.strftime("%d/%m/%Y"), + obj.hora_inicio, + ":00" + ) + + def get_estadoTransmissaoYoutube(self, obj): + if obj.url_video: + if obj.finalizada: + return self.TRANSMISSAO_ENCERRADA + else: + return self.TRANSMISSAO_EM_ANDAMENTO + else: + return self.SEM_TRANSMISSAO + + def get_assunto_sessao(self, obj): + pauta_sessao = '' + ordem_dia = OrdemDia.objects.filter(sessao_plenaria=obj.pk) + pauta_sessao = ', '.join([i.materia.__str__() for i in ordem_dia]) + + return str(pauta_sessao) + + def get_endereco_orgao(self, obj): + return self.casa().endereco + + def get_reuniao_conjunta(self, obj): + return False + + def get_sigla_orgao(self, obj): + return self.casa().sigla + + def get_nome_orgao(self, obj): + return self.casa().nome + + def casa(self): + casa = CasaLegislativa.objects.first() + return casa + + +class SessaoPlenariaSerializer(serializers.ModelSerializer): + + class Meta: + model = SessaoPlenaria + fields = ('tipo', + 'sessao_legislativa', + 'legislatura', + 'data_inicio', + 'hora_inicio', + 'hora_fim', + 'url_video', + 'iniciada', + 'finalizada' + ) diff --git a/sapl/api/sessao/urls.py b/sapl/api/sessao/urls.py new file mode 100644 index 000000000..a8fd6e56f --- /dev/null +++ b/sapl/api/sessao/urls.py @@ -0,0 +1,15 @@ +from django.conf.urls import include, url +from rest_framework.routers import DefaultRouter +from sapl.api.sessao.views import SessaoPlenariaViewSet,\ + SessaoPlenariaOldViewSet + +# Não adicione app_name +# app_name = AppConfig.name + +router = DefaultRouter() +router.register(r'sessao-plenaria-old', SessaoPlenariaOldViewSet, + base_name='sessao-plenaria-old') +router.register(r'sessao-plenaria', SessaoPlenariaViewSet) + +urlpatterns = [ +] diff --git a/sapl/api/sessao/views.py b/sapl/api/sessao/views.py new file mode 100644 index 000000000..9dcdd2423 --- /dev/null +++ b/sapl/api/sessao/views.py @@ -0,0 +1,23 @@ +from rest_framework.filters import DjangoFilterBackend +from rest_framework.permissions import AllowAny +from rest_framework.viewsets import ReadOnlyModelViewSet + +from sapl.api.sessao.serializers import SessaoPlenariaOldSerializer,\ + SessaoPlenariaSerializer +from sapl.sessao.models import SessaoPlenaria + + +class SessaoPlenariaOldViewSet(ReadOnlyModelViewSet): + + permission_classes = (AllowAny,) + serializer_class = SessaoPlenariaOldSerializer + queryset = SessaoPlenaria.objects.all() + filter_backends = (DjangoFilterBackend,) + filter_fields = ('data_inicio', 'data_fim', 'interativa') + + +class SessaoPlenariaViewSet(ReadOnlyModelViewSet): + + permission_classes = (AllowAny,) + serializer_class = SessaoPlenariaSerializer + queryset = SessaoPlenaria.objects.all() diff --git a/sapl/api/urls.py b/sapl/api/urls.py index 1f19e9d1f..e2ba3ccfa 100644 --- a/sapl/api/urls.py +++ b/sapl/api/urls.py @@ -2,32 +2,28 @@ from django.conf import settings from django.conf.urls import include, url from rest_framework.routers import DefaultRouter -from sapl.api.views import (AutoresPossiveisListView, AutoresProvaveisListView, - AutorListView, MateriaLegislativaViewSet, - ModelChoiceView, SessaoPlenariaViewSet) +import sapl.api.base.urls +import sapl.api.materia.urls +import sapl.api.sessao.urls +from sapl.api.views import ModelChoiceView, TimeRefreshDatabaseView from .apps import AppConfig + app_name = AppConfig.name router = DefaultRouter() -router.register(r'materia', MateriaLegislativaViewSet) -router.register(r'sessao-plenaria', SessaoPlenariaViewSet) +router.registry += sapl.api.materia.urls.router.registry + \ + sapl.api.sessao.urls.router.registry + urlpatterns_router = router.urls urlpatterns_api = [ - - url(r'^autor/provaveis', - AutoresProvaveisListView.as_view(), name='autores_provaveis_list'), - url(r'^autor/possiveis', - AutoresPossiveisListView.as_view(), name='autores_possiveis_list'), - - url(r'^autor', AutorListView.as_view(), name='autor_list'), - url(r'^model/(?P\d+)/(?P\d*)$', ModelChoiceView.as_view(), name='model_list'), - + url(r'time_refresh$', + TimeRefreshDatabaseView.as_view(), name="time_refresh") ] if settings.DEBUG: @@ -35,6 +31,9 @@ if settings.DEBUG: url(r'^docs', include('rest_framework_docs.urls')), ] urlpatterns = [ + url(r'^api/', include(sapl.api.materia.urls)), + url(r'^api/', include(sapl.api.sessao.urls)), + url(r'^api/', include(sapl.api.base.urls)), url(r'^api/', include(urlpatterns_api)), url(r'^api/', include(urlpatterns_router)) ] diff --git a/sapl/api/views.py b/sapl/api/views.py index 9853e6af0..4b1a151f0 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -1,25 +1,12 @@ +from django.apps import apps from django.contrib.contenttypes.models import ContentType -from django.db.models import Q -from django.http import Http404 -from django.utils.translation import ugettext_lazy as _ -from rest_framework.filters import DjangoFilterBackend from rest_framework.generics import ListAPIView -from rest_framework.mixins import ListModelMixin, RetrieveModelMixin -from rest_framework.permissions import (AllowAny, IsAuthenticated, - IsAuthenticatedOrReadOnly) -from rest_framework.viewsets import GenericViewSet +from rest_framework.permissions import (IsAuthenticated, AllowAny) +from rest_framework.response import Response +from rest_framework.views import APIView -from sapl.api.forms import (AutorChoiceFilterSet, AutoresPossiveisFilterSet, - AutorSearchForFieldFilterSet) -from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer, - ChoiceSerializer, - MateriaLegislativaSerializer, - ModelChoiceSerializer, - SessaoPlenariaSerializer) -from sapl.base.models import Autor, TipoAutor -from sapl.materia.models import MateriaLegislativa -from sapl.sessao.models import SessaoPlenaria -from sapl.utils import SaplGenericRelation, sapl_logger +from sapl.api.serializers import ModelChoiceSerializer +from sapl.rules.apps import AppConfig class ModelChoiceView(ListAPIView): @@ -43,232 +30,9 @@ class ModelChoiceView(ListAPIView): return self.model.objects.all() -class AutorListView(ListAPIView): - """ - Listagem de Autores com filtro para autores já cadastrados - e/ou possíveis autores. +class TimeRefreshDatabaseView(APIView): - - tr - tipo do resultado - Prepera Lista de Autores para 3 cenários distintos - - - default = 1 - - = 1 -> para (value, text) usados geralmente - em combobox, radiobox, checkbox, etc com pesquisa básica - de Autores feita pelo django-filter - -> processo usado nas pesquisas, o mais usado. - - - = 3 -> Devolve instancias da classe Autor filtradas pelo - django-filter - - - tipo - chave primária do Tipo de Autor a ser filtrado - - - q - busca textual no nome do Autor ou em fields_search - declarados no field SaplGenericRelation das GenericFks - A busca textual acontece via django-filter com a - variável `tr` igual 1 ou 3. Em caso contrário, - o django-filter é desativado e a busca é feita - no model do ContentType associado ao tipo. - - - q_0 / q_1 - q_0 é opcional e quando usado, faz o código ignorar "q"... - - q_0 -> campos lookup a serem filtrados em qualquer Model - que implemente SaplGenericRelation - q_1 -> o valor que será pesquisado no lookup de q_0 - - q_0 e q_1 podem ser separados por ","... isso dará a - possibilidade de filtrar mais de um campo. - - - http://localhost:8000 - /api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=False - /api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=True - /api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=False - /api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=True - - http://localhost:8000 - /api/autor?tr=1 - &q_0=parlamentar_set__nome_parlamentar__icontains, - parlamentar_set__ativo - &q_1=Carvalho,False - /api/autor?tr=1 - &q_0=parlamentar_set__nome_parlamentar__icontains, - parlamentar_set__ativo - &q_1=Carvalho,True - /api/autor?tr=3 - &q_0=parlamentar_set__nome_parlamentar__icontains, - parlamentar_set__ativo - &q_1=Carvalho,False - /api/autor?tr=3 - &q_0=parlamentar_set__nome_parlamentar__icontains, - parlamentar_set__ativo - &q_1=Carvalho,True - - - não importa o campo que vc passe de qualquer dos Models - ligados... é possível ver que models são esses, - na ocasião do commit deste texto, executando: - In [6]: from sapl.utils import models_with_gr_for_model - - In [7]: models_with_gr_for_model(Autor) - Out[7]: - [sapl.parlamentares.models.Parlamentar, - sapl.parlamentares.models.Frente, - sapl.comissoes.models.Comissao, - sapl.materia.models.Orgao, - sapl.sessao.models.Bancada, - sapl.sessao.models.Bloco] - - qualquer atributo destes models podem ser passados - para busca - """ - - TR_AUTOR_CHOICE_SERIALIZER = 1 - TR_AUTOR_SERIALIZER = 3 - - permission_classes = (IsAuthenticatedOrReadOnly,) - queryset = Autor.objects.all() - model = Autor - - filter_class = AutorChoiceFilterSet - filter_backends = (DjangoFilterBackend, ) - serializer_class = AutorChoiceSerializer - - @property - def tr(self): - try: - tr = int(self.request.GET.get - ('tr', AutorListView.TR_AUTOR_CHOICE_SERIALIZER)) - - assert tr in ( - AutorListView.TR_AUTOR_CHOICE_SERIALIZER, - AutorListView.TR_AUTOR_SERIALIZER), sapl_logger.info( - _("Tipo do Resultado a ser fornecido não existe!")) - except: - return AutorListView.TR_AUTOR_CHOICE_SERIALIZER - else: - return tr + permission_classes = (AllowAny,) def get(self, request, *args, **kwargs): - - if self.tr == AutorListView.TR_AUTOR_SERIALIZER: - self.serializer_class = AutorSerializer - self.permission_classes = (IsAuthenticated,) - - if self.filter_class and 'q_0' in request.GET: - self.filter_class = AutorSearchForFieldFilterSet - - return ListAPIView.get(self, request, *args, **kwargs) - - -class AutoresProvaveisListView(ListAPIView): - - permission_classes = (IsAuthenticatedOrReadOnly,) - queryset = Autor.objects.all() - model = Autor - - filter_class = None - filter_backends = [] - serializer_class = ChoiceSerializer - - def get_queryset(self): - params = {'content_type__isnull': False} - - tipo = '' - try: - tipo = int(self.request.GET.get('tipo', '')) - if tipo: - params['id'] = tipo - except: - pass - - tipos = TipoAutor.objects.filter(**params) - - if not tipos.exists() and tipo: - raise Http404() - - r = [] - for tipo in tipos: - q = self.request.GET.get('q', '').strip() - - 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 - é um array de SaplGenericRelation que deve possuir o - atributo fields_search. Verifique na documentação da classe - a estrutura de fields_search. - """ - - assert len(fields) >= 1, (_( - 'Não foi encontrado em %(model)s um atributo do tipo ' - 'SaplGenericRelation que use o model %(model_autor)s') % { - 'model': model_class._meta.verbose_name, - 'model_autor': Autor._meta.verbose_name}) - - qs = model_class.objects.all() - - q_filter = 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]).order_by( - fields[0].fields_search[0][0]) - else: - qs = qs.order_by(fields[0].fields_search[0][0]) - - qs = qs.values_list( - 'id', fields[0].fields_search[0][0]) - r += list(qs) - - if tipos.count() > 1: - r.sort(key=lambda x: x[1].upper()) - return r - - -class AutoresPossiveisListView(ListAPIView): - - permission_classes = (IsAuthenticatedOrReadOnly,) - queryset = Autor.objects.all() - model = Autor - - pagination_class = None - - filter_class = AutoresPossiveisFilterSet - serializer_class = AutorChoiceSerializer - - -class MateriaLegislativaViewSet(ListModelMixin, - RetrieveModelMixin, - GenericViewSet): - - permission_classes = (IsAuthenticated,) - serializer_class = MateriaLegislativaSerializer - queryset = MateriaLegislativa.objects.all() - filter_backends = (DjangoFilterBackend,) - filter_fields = ('numero', 'ano', 'tipo', ) - - -class SessaoPlenariaViewSet(ListModelMixin, - RetrieveModelMixin, - GenericViewSet): - - permission_classes = (AllowAny,) - serializer_class = SessaoPlenariaSerializer - queryset = SessaoPlenaria.objects.all() - filter_backends = (DjangoFilterBackend,) - filter_fields = ('data_inicio', 'data_fim', 'interativa') + return Response({'last_global_refresh_time': apps.get_app_config('rules').time_refresh}) diff --git a/sapl/crud/base.py b/sapl/crud/base.py index 739fc4f37..77462a5f8 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -17,8 +17,8 @@ from django.http.response import Http404 from django.shortcuts import redirect from django.utils.decorators import classonlymethod from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ from django.views.generic import (CreateView, DeleteView, DetailView, ListView, UpdateView) from django.views.generic.base import ContextMixin @@ -30,6 +30,7 @@ from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL, from sapl.settings import BASE_DIR from sapl.utils import normalize + logger = logging.getLogger(BASE_DIR.name) ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ @@ -411,10 +412,13 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView): m = self.model fn = fn.split('__') for f in fn: + if not f: + continue f = m._meta.get_field(f) if hasattr(f, 'related_model') and f.related_model: m = f.related_model - s.append(force_text(f.verbose_name)) + if f: + s.append(force_text(f.verbose_name)) s = ' / '.join(s) r.append(s) return r @@ -440,6 +444,9 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView): if isinstance(name, tuple): s = '' for j, n in enumerate(name): + if not n: + s += '
' + continue m = obj n = n.split('__') for f in n[:-1]: diff --git a/sapl/rules/apps.py b/sapl/rules/apps.py index dbdfce8ce..994c2b887 100644 --- a/sapl/rules/apps.py +++ b/sapl/rules/apps.py @@ -6,7 +6,10 @@ from django.contrib.auth import get_user_model from django.contrib.auth.management import _get_all_permissions from django.core import exceptions from django.db import models, router +from django.db.models.signals import post_save, post_delete from django.db.utils import DEFAULT_DB_ALIAS +from django.dispatch.dispatcher import receiver +from django.utils import timezone from django.utils.translation import string_concat from django.utils.translation import ugettext_lazy as _ import reversion @@ -21,6 +24,7 @@ class AppConfig(django.apps.AppConfig): name = 'sapl.rules' label = 'rules' verbose_name = _('Regras de Acesso') + time_refresh = timezone.now() def create_proxy_permissions( @@ -254,3 +258,9 @@ models.signals.post_migrate.connect( models.signals.pre_delete.connect( receiver=revision_pre_delete_signal, dispatch_uid="pre_delete_signal") + + +@receiver([post_save, post_delete]) +def refresh_time_update_base(sender, instance, **kwargs): + rule_app = apps.get_app_config('rules') + rule_app.time_refresh = timezone.now() diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index 0166e848e..f7308f62e 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -1,6 +1,5 @@ from datetime import datetime -import django_filters from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button, Fieldset, Layout from django import forms @@ -9,6 +8,7 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import transaction from django.forms import ModelForm from django.utils.translation import ugettext_lazy as _ +import django_filters from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import form_actions, to_row @@ -98,7 +98,6 @@ class SessaoPlenariaForm(ModelForm): else: # create raise error - # Condições da verificação abertura_entre_leg = leg.data_inicio <= abertura <= leg.data_fim abertura_entre_sl = sl.data_inicio <= abertura <= sl.data_fim @@ -112,7 +111,8 @@ class SessaoPlenariaForm(ModelForm): if encerramento < abertura: raise ValidationError("A data de encerramento não pode ser " "anterior a data de abertura.") - # Verifica se a data de abertura está entre a data de início e fim da legislatura + # Verifica se a data de abertura está entre a data de início e fim + # da legislatura if abertura_entre_leg and encerramento_entre_leg: if abertura_entre_sl and encerramento_entre_sl: pass @@ -164,7 +164,6 @@ class SessaoPlenariaForm(ModelForm): "datas de início e fim tanto Legislatura " "quanto da Sessão Legislativa.") - # Verificações com a data de encerramento vazia else: if abertura_entre_leg: @@ -454,7 +453,6 @@ class SessaoPlenariaFilterSet(django_filters.FilterSet): # pré-popula o campo do formulário com o ano corrente self.form.fields['data_inicio__year'].initial = timezone.now().year - row1 = to_row( [('data_inicio__year', 3), ('data_inicio__month', 3), @@ -569,13 +567,14 @@ class OradorExpedienteForm(ModelForm): def __init__(self, *args, **kwargs): super(OradorExpedienteForm, self).__init__(*args, **kwargs) - legislatura_vigente = SessaoPlenaria.objects.get(pk=kwargs['initial']['id_sessao']).legislatura + legislatura_vigente = SessaoPlenaria.objects.get( + pk=kwargs['initial']['id_sessao']).legislatura if legislatura_vigente: self.fields['parlamentar'].queryset = \ Parlamentar.objects.filter(ativo=True, mandato__legislatura=legislatura_vigente - ).order_by('nome_parlamentar') + ).order_by('nome_parlamentar') def clean(self): super(OradorExpedienteForm, self).clean() @@ -585,11 +584,11 @@ class OradorExpedienteForm(ModelForm): return self.cleaned_data sessao_id = self.initial['id_sessao'] - numero = self.initial.get('numero') # Retorna None se inexistente + numero = self.initial.get('numero') # Retorna None se inexistente ordem = OradorExpediente.objects.filter( - sessao_plenaria_id=sessao_id, - numero_ordem=cleaned_data['numero_ordem'] - ).exists() + sessao_plenaria_id=sessao_id, + numero_ordem=cleaned_data['numero_ordem'] + ).exists() if ordem and (cleaned_data['numero_ordem'] != numero): raise ValidationError(_( @@ -597,7 +596,6 @@ class OradorExpedienteForm(ModelForm): return self.cleaned_data - class Meta: model = OradorExpediente exclude = ['sessao_plenaria'] diff --git a/sapl/sessao/migrations/0023_auto_20180914_1315.py b/sapl/sessao/migrations/0023_auto_20180914_1315.py new file mode 100644 index 000000000..cd2d076ee --- /dev/null +++ b/sapl/sessao/migrations/0023_auto_20180914_1315.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-09-14 16:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +def limpa_observacao_igual_ementa(apps, schema_editor): + + ExpedienteMateria = apps.get_model('sessao', 'ExpedienteMateria') + OrdemDia = apps.get_model('sessao', 'OrdemDia') + + q = models.Q(observacao__iexact=models.F('materia__ementa')) + + ExpedienteMateria.objects.filter(q).update(observacao='') + OrdemDia.objects.filter(q).update(observacao='') + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0022_auto_20180618_1625'), + ] + + operations = [ + migrations.AlterField( + model_name='expedientemateria', + name='observacao', + field=models.TextField(blank=True, verbose_name='Observação'), + ), + migrations.AlterField( + model_name='ordemdia', + name='observacao', + field=models.TextField(blank=True, verbose_name='Observação'), + ), + migrations.RunPython(limpa_observacao_igual_ementa), + ] diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index 6af5f490f..11047ed9e 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -1,10 +1,10 @@ from operator import xor -import reversion from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import ugettext_lazy as _ from model_utils import Choices +import reversion from sapl.base.models import Autor from sapl.materia.models import MateriaLegislativa @@ -237,7 +237,7 @@ class AbstractOrdemDia(models.Model): verbose_name=_('Matéria')) data_ordem = models.DateField(verbose_name=_('Data da Sessão')) observacao = models.TextField( - blank=True, verbose_name=_('Ementa')) + blank=True, verbose_name=_('Observação')) numero_ordem = models.PositiveIntegerField(verbose_name=_('Nº Ordem')) resultado = models.TextField(blank=True, verbose_name=_('Resultado')) tipo_votacao = models.PositiveIntegerField( @@ -254,6 +254,10 @@ class AbstractOrdemDia(models.Model): class Meta: abstract = True + @property + def ementa(self): + return self.materia.ementa + def __str__(self): return 'Ordem do Dia/Expediente: %s - %s em %s' % ( self.numero_ordem, self.materia, self.sessao_plenaria) diff --git a/sapl/sessao/serializers.py b/sapl/sessao/serializers.py index b8e64358e..b7d93a4c5 100644 --- a/sapl/sessao/serializers.py +++ b/sapl/sessao/serializers.py @@ -1,19 +1,3 @@ from rest_framework import serializers from .models import SessaoPlenaria - - -class SessaoPlenariaSerializer(serializers.Serializer): - - class Meta: - model = SessaoPlenaria - fields = ('tipo', - 'sessao_legislativa', - 'legislatura', - 'data_inicio', - 'hora_inicio', - 'hora_fim', - 'url_video', - 'iniciada', - 'finalizada' - ) diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 5a2b0785a..781aebe5c 100644 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -1,5 +1,5 @@ -from re import sub from operator import itemgetter +from re import sub from django.contrib import messages from django.contrib.auth.decorators import permission_required @@ -49,6 +49,7 @@ from .models import (Bancada, Bloco, CargoBancada, CargoMesa, SessaoPlenaria, SessaoPlenariaPresenca, TipoExpediente, TipoResultadoVotacao, TipoSessaoPlenaria, VotoParlamentar) + TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria') TipoExpedienteCrud = CrudAux.build(TipoExpediente, 'tipo_expediente') CargoBancadaCrud = CrudAux.build(CargoBancada, '') @@ -325,9 +326,9 @@ def customize_link_materia(context, pk, has_permission, is_expediente): 'mid': obj.materia_id}) resultado = ('%s
%s
' % - (url, - resultado_descricao, - resultado_observacao)) + (url, + resultado_descricao, + resultado_observacao)) else: if obj.tipo_votacao == 2: @@ -338,7 +339,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente): 'pk': obj.sessao_plenaria_id, 'oid': obj.pk, 'mid': obj.materia_id}) + \ - '?&materia=expediente' + '?&materia=expediente' else: url = reverse( 'sapl.sessao:votacao_nominal_transparencia', @@ -346,12 +347,12 @@ def customize_link_materia(context, pk, has_permission, is_expediente): 'pk': obj.sessao_plenaria_id, 'oid': obj.pk, 'mid': obj.materia_id}) + \ - '?&materia=ordem' + '?&materia=ordem' resultado = ('%s
%s
' % - (url, - resultado_descricao, - resultado_observacao)) + (url, + resultado_descricao, + resultado_observacao)) elif obj.tipo_votacao == 1: if is_expediente: @@ -361,7 +362,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente): 'pk': obj.sessao_plenaria_id, 'oid': obj.pk, 'mid': obj.materia_id}) + \ - '?&materia=expediente' + '?&materia=expediente' else: url = reverse( 'sapl.sessao:votacao_simbolica_transparencia', @@ -369,7 +370,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente): 'pk': obj.sessao_plenaria_id, 'oid': obj.pk, 'mid': obj.materia_id}) + \ - '?&materia=ordem' + '?&materia=ordem' resultado = ('%s
%s
' % (url, @@ -377,8 +378,8 @@ def customize_link_materia(context, pk, has_permission, is_expediente): resultado_observacao)) else: resultado = ('%s
%s' % - (resultado_descricao, - resultado_observacao)) + (resultado_descricao, + resultado_observacao)) context['rows'][i][3] = (resultado, None) return context @@ -389,7 +390,8 @@ def get_presencas_generic(model, sessao, legislatura): presentes = [p.parlamentar for p in presencas] - presentes = sorted(presentes, key=lambda x: remover_acentos(x.nome_parlamentar)) + presentes = sorted( + presentes, key=lambda x: remover_acentos(x.nome_parlamentar)) mandato = Mandato.objects.filter( legislatura=legislatura).order_by('parlamentar__nome_parlamentar') @@ -408,7 +410,8 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): public = [RP_LIST, RP_DETAIL] class BaseMixin(MasterDetailCrud.BaseMixin): - list_field_names = ['numero_ordem', 'materia', 'materia__ementa', + list_field_names = ['numero_ordem', 'materia', + ('materia__ementa', '', 'observacao'), 'resultado'] class CreateView(MasterDetailCrud.CreateView): @@ -439,17 +442,18 @@ class MateriaOrdemDiaCrud(MasterDetailCrud): return initial class DetailView(MasterDetailCrud.DetailView): - layout_key = 'OrdemDiaDetail' class ListView(MasterDetailCrud.ListView): paginate_by = None ordering = ['numero_ordem', 'materia', 'resultado'] + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) has_permition = self.request.user.has_module_perms(AppConfig.label) return customize_link_materia(context, self.kwargs['pk'], has_permition, False) + def recuperar_materia(request): tipo = TipoMateriaLegislativa.objects.get(pk=request.GET['tipo_materia']) numero = request.GET['numero_materia'] @@ -463,7 +467,7 @@ def recuperar_materia(request): 'id': materia.id, 'indexacao': materia.indexacao}) except ObjectDoesNotExist: - response = JsonResponse({'ementa': '', 'id': 0, 'indexacao':''}) + response = JsonResponse({'ementa': '', 'id': 0, 'indexacao': ''}) return response @@ -476,7 +480,8 @@ class ExpedienteMateriaCrud(MasterDetailCrud): class BaseMixin(MasterDetailCrud.BaseMixin): list_field_names = ['numero_ordem', 'materia', - 'materia__ementa', 'resultado'] + ('materia__ementa', '', 'observacao'), + 'resultado'] class ListView(MasterDetailCrud.ListView): paginate_by = None @@ -544,7 +549,6 @@ class OradorExpedienteCrud(OradorCrud): return reverse('sapl.sessao:oradorexpediente_list', kwargs={'pk': self.kwargs['pk']}) - class UpdateView(MasterDetailCrud.UpdateView): form_class = OradorExpedienteForm @@ -669,10 +673,10 @@ class SessaoCrud(Crud): return { 'legislatura': legislatura, 'sessao_legislativa': legislatura.sessaolegislativa_set.filter( - legislatura_id=legislatura.id, - data_inicio__year=timezone.now().year - ).first() - } + legislatura_id=legislatura.id, + data_inicio__year=timezone.now().year + ).first() + } else: msg = _('Cadastre alguma legislatura antes de adicionar ' + 'uma sessão plenária!') @@ -778,7 +782,8 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView): cronometro_discurso = AppsAppConfig.attr('cronometro_discurso') cronometro_aparte = AppsAppConfig.attr('cronometro_aparte') cronometro_ordem = AppsAppConfig.attr('cronometro_ordem') - cronometro_consideracoes = AppsAppConfig.attr('cronometro_consideracoes') + cronometro_consideracoes = AppsAppConfig.attr( + 'cronometro_consideracoes') if (not cronometro_discurso or not cronometro_aparte or not cronometro_ordem or not cronometro_consideracoes): @@ -999,15 +1004,18 @@ class MesaView(FormMixin, DetailView): cargos_vagos = list(set(cargos) - set(cargos_ocupados)) # FIX-ME: tem formas melhores de fazer isso, poupando linhas. - parlamentares = Legislatura.objects.get(id=sessao.legislatura_id).mandato_set.all() + parlamentares = Legislatura.objects.get( + id=sessao.legislatura_id).mandato_set.all() parlamentares_ocupados = [m.parlamentar for m in mesa] parlamentares_vagos = list( set( [p.parlamentar for p in parlamentares]) - set( parlamentares_ocupados)) org_parlamentares_vagos = parlamentares_vagos - org_parlamentares_vagos.sort(key=lambda x: remover_acentos(x.nome_parlamentar)) - org_parlamentares_vagos = [p for p in org_parlamentares_vagos if p.ativo] + org_parlamentares_vagos.sort( + key=lambda x: remover_acentos(x.nome_parlamentar)) + org_parlamentares_vagos = [ + p for p in org_parlamentares_vagos if p.ativo] # Se todos os cargos estiverem ocupados, a listagem de parlamentares # deve ser renderizada vazia if not cargos_vagos: @@ -1159,15 +1167,15 @@ class ResumoOrdenacaoView(PermissionRequiredMixin, FormView): ordenacao = ResumoOrdenacao.objects.first() if ordenacao: initial.update({'primeiro': ordenacao.primeiro, - 'segundo': ordenacao.segundo, - 'terceiro': ordenacao.terceiro, - 'quarto': ordenacao.quarto, - 'quinto': ordenacao.quinto, - 'sexto': ordenacao.sexto, - 'setimo': ordenacao.setimo, - 'oitavo': ordenacao.oitavo, - 'nono': ordenacao.nono, - 'decimo': ordenacao.decimo}) + 'segundo': ordenacao.segundo, + 'terceiro': ordenacao.terceiro, + 'quarto': ordenacao.quarto, + 'quinto': ordenacao.quinto, + 'sexto': ordenacao.sexto, + 'setimo': ordenacao.setimo, + 'oitavo': ordenacao.oitavo, + 'nono': ordenacao.nono, + 'decimo': ordenacao.decimo}) return initial def form_valid(self, form): @@ -1328,7 +1336,7 @@ class ResumoView(DetailView): ora = {'numero_ordem': numero_ordem, 'url_discurso': url_discurso, 'parlamentar': parlamentar, - 'observacao' : observacao + 'observacao': observacao } oradores.append(ora) @@ -1478,8 +1486,8 @@ class ExpedienteView(FormMixin, DetailView): for tipo, conteudo in zip(list_tipo, list_conteudo): ExpedienteSessao.objects.filter( - sessao_plenaria_id=self.object.id, - tipo_id=tipo).delete() + sessao_plenaria_id=self.object.id, + tipo_id=tipo).delete() expediente = ExpedienteSessao() expediente.sessao_plenaria_id = self.object.id @@ -1825,7 +1833,8 @@ class VotacaoNominalAbstract(SessaoPermissionMixin): elif self.expediente: expediente_id = kwargs['oid'] try: - materia_votacao = ExpedienteMateria.objects.get(id=expediente_id) + materia_votacao = ExpedienteMateria.objects.get( + id=expediente_id) except ObjectDoesNotExist: raise Http404() @@ -1923,7 +1932,8 @@ class VotacaoNominalAbstract(SessaoPermissionMixin): return self.form_invalid(form) def form_invalid(self, form): - errors_tuple = [(form[e].label, form.errors[e]) for e in form.errors if e in form.fields] + errors_tuple = [(form[e].label, form.errors[e]) + for e in form.errors if e in form.fields] error_message = '''