From faceaea3b7ce7fd2d7b321b0c580fda193515612 Mon Sep 17 00:00:00 2001 From: Gustavo274 <51173319+Gustavo274@users.noreply.github.com> Date: Tue, 6 Jul 2021 15:39:57 -0300 Subject: [PATCH 01/22] =?UTF-8?q?Adicionado=20fun=C3=A7=C3=A3o=20resultado?= =?UTF-8?q?=5Fvotacao=20que=20retorna=20um=20resultadopara=20a=20vo?= =?UTF-8?q?=E2=80=A6=20(#3416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adicionado função resultado_votacao que retorna um resultadopara a votação da matéria, onde este será chamado no template materialegislativa_filter.html dentro de um condicional if. * Modifcações realizadas nas linha 116 até a 118 do sapl/base/templatetags/common_tags.py conforme solicitado. Co-authored-by: Gustavo274 --- sapl/base/templatetags/common_tags.py | 20 ++++++++++++++++++- .../materia/materialegislativa_filter.html | 3 +++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/sapl/base/templatetags/common_tags.py b/sapl/base/templatetags/common_tags.py index 56d7d356e..18758a612 100644 --- a/sapl/base/templatetags/common_tags.py +++ b/sapl/base/templatetags/common_tags.py @@ -51,6 +51,7 @@ def model_verbose_name_plural(class_name): model = get_class(class_name) return model._meta.verbose_name_plural + @register.filter def meta_model_value(instance, attr): try: @@ -103,6 +104,22 @@ def paginacao_limite_superior(pagina): return int(pagina) * 10 +@register.filter +def resultado_votacao(materia): + ra = materia.registrovotacao_set.first() + rb = materia.retiradapauta_set.first() + if ra: + resultado = ra.tipo_resultado_votacao.nome + elif rb: + resultado = rb.tipo_de_retirada.descricao + elif materia.expedientemateria_set.filter(tipo_votacao=4).exists() or \ + materia.ordemdia_set.filter(tipo_votacao=4).exists(): + resultado = "Matéria lida" + else: + resultado = "Matéria não votada" + return resultado + + @register.filter def lookup(d, key): return d[key] if key in d else [] @@ -245,6 +262,7 @@ def youtube_url(value): r = re.findall(youtube_pattern, value) return True if r else False + @register.filter def facebook_url(value): value = value.lower() @@ -252,6 +270,7 @@ def facebook_url(value): r = re.findall(facebook_pattern, value) return True if r else False + @register.filter def youtube_id(value): from urllib.parse import urlparse, parse_qs @@ -339,4 +358,3 @@ def dont_break_out(value): _safe = '
{}
'.format(value) _safe = mark_safe(_safe) return _safe - diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index 9b41ce621..d9e4e2ee3 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -89,6 +89,9 @@ Status:  {{m.tramitacao_set.first.status}}
Data Fim Prazo (Tramitação): {{m.tramitacao_set.first.data_fim_prazo|default_if_none:""}}
{% endif %} + {% if m|resultado_votacao %} + Resultado:  {{m|resultado_votacao}}
+ {% endif %} {% if m.registrovotacao_set.exists %} Data Votação: {% for rv in m.registrovotacao_set.all %} From 4216018cfdf90366d9a7feb2444698f0fc667098 Mon Sep 17 00:00:00 2001 From: Gustavo274 <51173319+Gustavo274@users.noreply.github.com> Date: Tue, 6 Jul 2021 15:46:07 -0300 Subject: [PATCH 02/22] Conserta issue #922080 do OsTicket (#3418) Co-authored-by: Gustavo274 --- sapl/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sapl/utils.py b/sapl/utils.py index 1acd472ae..53b76810a 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -550,7 +550,8 @@ FILTER_OVERRIDES_DATEFIELD = { class FilterOverridesMetaMixin: filter_overrides = { - models.DateField: FILTER_OVERRIDES_DATEFIELD + models.DateField: FILTER_OVERRIDES_DATEFIELD, + models.DateTimeField: FILTER_OVERRIDES_DATEFIELD } From a64bd254e8aa27ee95dbb8cdd2a6a7de9b424ad1 Mon Sep 17 00:00:00 2001 From: Leandro Roberto Silva Date: Tue, 6 Jul 2021 21:11:23 -0300 Subject: [PATCH 03/22] =?UTF-8?q?Isola=20core=20da=20api=20e=20suas=20cust?= =?UTF-8?q?omiza=C3=A7=C3=B5es=20(#3420)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * separa api automática das customizações * isola core em um package dentro de api --- sapl/api/core/__init__.py | 286 +++++++++++++++ sapl/api/urls.py | 3 +- sapl/api/views.py | 668 +----------------------------------- sapl/api/views_customize.py | 406 ++++++++++++++++++++++ 4 files changed, 695 insertions(+), 668 deletions(-) create mode 100644 sapl/api/core/__init__.py create mode 100644 sapl/api/views_customize.py diff --git a/sapl/api/core/__init__.py b/sapl/api/core/__init__.py new file mode 100644 index 000000000..b4785aa40 --- /dev/null +++ b/sapl/api/core/__init__.py @@ -0,0 +1,286 @@ +import logging + +from django import apps +from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Q +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.utils import timezone +from django.utils.decorators import classonlymethod +from django.utils.translation import ugettext_lazy as _ +from django_filters.rest_framework.backends import DjangoFilterBackend +from rest_framework import serializers as rest_serializers +from rest_framework.authtoken.models import Token +from rest_framework.decorators import action, api_view, permission_classes +from rest_framework.fields import SerializerMethodField +from rest_framework.permissions import IsAuthenticated, IsAdminUser +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.viewsets import ModelViewSet + +from sapl.api.forms import SaplFilterSetMixin +from sapl.api.permissions import SaplModelPermissions +from sapl.api.serializers import ChoiceSerializer, ParlamentarSerializer,\ + ParlamentarEditSerializer, ParlamentarResumeSerializer + + +class BusinessRulesNotImplementedMixin: + def create(self, request, *args, **kwargs): + raise Exception(_("POST Create não implementado")) + + def update(self, request, *args, **kwargs): + raise Exception(_("PUT and PATCH não implementado")) + + def delete(self, request, *args, **kwargs): + raise Exception(_("DELETE Delete não implementado")) + + +class SaplApiViewSetConstrutor(): + + class SaplApiViewSet(ModelViewSet): + filter_backends = (DjangoFilterBackend,) + + _built_sets = {} + + @classonlymethod + def get_class_for_model(cls, model): + return cls._built_sets[model._meta.app_config][model] + + @classonlymethod + def build_class(cls): + import inspect + from sapl.api import serializers + + # Carrega todas as classes de sapl.api.serializers que possuam + # "Serializer" como Sufixo. + serializers_classes = inspect.getmembers(serializers) + serializers_classes = {i[0]: i[1] for i in filter( + lambda x: x[0].endswith('Serializer'), + serializers_classes + )} + + # Carrega todas as classes de sapl.api.forms que possuam + # "FilterSet" como Sufixo. + from sapl.api import forms + filters_classes = inspect.getmembers(forms) + filters_classes = {i[0]: i[1] for i in filter( + lambda x: x[0].endswith('FilterSet'), + filters_classes + )} + + built_sets = {} + + def build(_model): + object_name = _model._meta.object_name + + # Caso Exista, pega a classe sapl.api.serializers.{model}Serializer + # ou utiliza a base do drf para gerar uma automática para o model + serializer_name = f'{object_name}Serializer' + _serializer_class = serializers_classes.get( + serializer_name, rest_serializers.ModelSerializer) + + # Caso Exista, pega a classe sapl.api.forms.{model}FilterSet + # ou utiliza a base definida em sapl.forms.SaplFilterSetMixin + filter_name = f'{object_name}FilterSet' + _filterset_class = filters_classes.get( + filter_name, SaplFilterSetMixin) + + def create_class(): + + _meta_serializer = object if not hasattr( + _serializer_class, 'Meta') else _serializer_class.Meta + + # Define uma classe padrão para serializer caso não tenha sido + # criada a classe sapl.api.serializers.{model}Serializer + class SaplSerializer(_serializer_class): + __str__ = SerializerMethodField() + + class Meta(_meta_serializer): + if not hasattr(_meta_serializer, 'ref_name'): + ref_name = None + + if not hasattr(_meta_serializer, 'model'): + model = _model + + if hasattr(_meta_serializer, 'exclude'): + exclude = _meta_serializer.exclude + else: + if not hasattr(_meta_serializer, 'fields'): + fields = '__all__' + elif _meta_serializer.fields != '__all__': + fields = list( + _meta_serializer.fields) + ['__str__', ] + else: + fields = _meta_serializer.fields + + def get___str__(self, obj): + return str(obj) + + _meta_filterset = object if not hasattr( + _filterset_class, 'Meta') else _filterset_class.Meta + + # Define uma classe padrão para filtro caso não tenha sido + # criada a classe sapl.api.forms.{model}FilterSet + class SaplFilterSet(_filterset_class): + class Meta(_meta_filterset): + if not hasattr(_meta_filterset, 'model'): + model = _model + + # Define uma classe padrão ModelViewSet de DRF + class ModelSaplViewSet(SaplApiViewSetConstrutor.SaplApiViewSet): + queryset = _model.objects.all() + + # Utiliza o filtro customizado pela classe + # sapl.api.forms.{model}FilterSet + # ou utiliza o trivial SaplFilterSet definido acima + filterset_class = SaplFilterSet + + # Utiliza o serializer customizado pela classe + # sapl.api.serializers.{model}Serializer + # ou utiliza o trivial SaplSerializer definido acima + serializer_class = SaplSerializer + + return ModelSaplViewSet + + viewset = create_class() + viewset.__name__ = '%sModelSaplViewSet' % _model.__name__ + return viewset + + apps_sapl = [apps.apps.get_app_config( + n[5:]) for n in settings.SAPL_APPS] + for app in apps_sapl: + cls._built_sets[app] = {} + for model in app.get_models(): + cls._built_sets[app][model] = build(model) + + return cls + + +""" +1. Constroi uma rest_framework.viewsets.ModelViewSet para + todos os models de todas as apps do sapl +2. Define DjangoFilterBackend como ferramenta de filtro dos campos +3. Define Serializer como a seguir: + 3.1 - Define um Serializer genérico para cada módel + 3.2 - Recupera Serializer customizado em sapl.api.serializers + 3.3 - Para todo model é opcional a existência de + sapl.api.serializers.{model}Serializer. + Caso não seja definido um Serializer customizado, utiliza-se o trivial +4. Define um FilterSet como a seguir: + 4.1 - Define um FilterSet genérico para cada módel + 4.2 - Recupera FilterSet customizado em sapl.api.forms + 4.3 - Para todo model é opcional a existência de + sapl.api.forms.{model}FilterSet. + Caso não seja definido um FilterSet customizado, utiliza-se o trivial + 4.4 - todos os campos que aceitam lookup 'exact' + podem ser filtrados por default + +5. SaplApiViewSetConstrutor não cria padrões e/ou exige conhecimento alem dos + exigidos pela DRF. + +6. As rotas são criadas seguindo nome da app e nome do model + http://localhost:9000/api/{applabel}/{model_name}/ + e seguem as variações definidas em: + https://www.django-rest-framework.org/api-guide/routers/#defaultrouter + +7. Todas as viewsets construídas por SaplApiViewSetConstrutor e suas rotas + (paginate list, detail, edit, create, delete) + bem como testes em ambiente de desenvolvimento podem ser conferidas em: + http://localhost:9000/api/ + desde que settings.DEBUG=True + +**SaplApiViewSetConstrutor._built_sets** é um dict de dicts de models conforme: + { + ... + + 'audiencia': { + 'tipoaudienciapublica': TipoAudienciaPublicaViewSet, + 'audienciapublica': AudienciaPublicaViewSet, + 'anexoaudienciapublica': AnexoAudienciaPublicaViewSet + + ... + + }, + + ... + + 'base': { + 'casalegislativa': CasaLegislativaViewSet, + 'appconfig': AppConfigViewSet, + + ... + + } + + ... + + } +""" + +# Toda Classe construida acima, pode ser redefinida e aplicado quaisquer +# das possibilidades para uma classe normal criada a partir de +# rest_framework.viewsets.ModelViewSet conforme exemplo para a classe autor + +# decorator que processa um endpoint detail trivial com base no model passado, +# Um endpoint detail geralmente é um conteúdo baseado numa FK com outros possíveis filtros +# e os passados pelo proprio cliente, além de o serializer e o filterset +# ser desse model passado + + +class wrapper_queryset_response_for_drf_action(object): + def __init__(self, model): + self.model = model + + def __call__(self, cls): + + def wrapper(instance_view, *args, **kwargs): + # recupera a viewset do model anotado + iv = instance_view + viewset_from_model = SaplApiViewSetConstrutor._built_sets[ + self.model._meta.app_config][self.model] + + # apossa da instancia da viewset mae do action + # em uma viewset que processa dados do model passado no decorator + iv.queryset = viewset_from_model.queryset + iv.serializer_class = viewset_from_model.serializer_class + iv.filterset_class = viewset_from_model.filterset_class + + iv.queryset = instance_view.filter_queryset( + iv.get_queryset()) + + # chama efetivamente o metodo anotado que deve devolver um queryset + # com os filtros específicos definido pelo programador customizador + qs = cls(instance_view, *args, **kwargs) + + page = iv.paginate_queryset(qs) + data = iv.get_serializer( + page if page is not None else qs, many=True).data + + return iv.get_paginated_response( + data) if page is not None else Response(data) + + return wrapper + + +# decorator para recuperar e transformar o default +class customize(object): + def __init__(self, model): + self.model = model + + def __call__(self, cls): + + class _SaplApiViewSet( + cls, + SaplApiViewSetConstrutor._built_sets[ + self.model._meta.app_config][self.model] + ): + pass + + if hasattr(_SaplApiViewSet, 'build'): + _SaplApiViewSet = _SaplApiViewSet.build() + + SaplApiViewSetConstrutor._built_sets[ + self.model._meta.app_config][self.model] = _SaplApiViewSet + return _SaplApiViewSet diff --git a/sapl/api/urls.py b/sapl/api/urls.py index 17fd432ab..2c3b1fd7f 100644 --- a/sapl/api/urls.py +++ b/sapl/api/urls.py @@ -6,7 +6,8 @@ from rest_framework.routers import DefaultRouter from sapl.api.deprecated import MateriaLegislativaViewSet, SessaoPlenariaViewSet,\ AutoresProvaveisListView, AutoresPossiveisListView, AutorListView,\ ModelChoiceView -from sapl.api.views import SaplApiViewSetConstrutor, AppVersionView, recria_token +from sapl.api.views import AppVersionView, recria_token +from sapl.api.views_customize import SaplApiViewSetConstrutor from .apps import AppConfig diff --git a/sapl/api/views.py b/sapl/api/views.py index f4e853ccd..1c2bc988e 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -1,39 +1,14 @@ import logging -from django import apps from django.conf import settings -from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ObjectDoesNotExist -from django.db.models import Q from django.db.models.signals import post_save from django.dispatch import receiver -from django.utils import timezone -from django.utils.decorators import classonlymethod from django.utils.translation import ugettext_lazy as _ -from django_filters.rest_framework.backends import DjangoFilterBackend -from rest_framework import serializers as rest_serializers from rest_framework.authtoken.models import Token -from rest_framework.decorators import action, api_view, permission_classes -from rest_framework.fields import SerializerMethodField +from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated, IsAdminUser from rest_framework.response import Response from rest_framework.views import APIView -from rest_framework.viewsets import ModelViewSet - -from sapl.api.forms import SaplFilterSetMixin -from sapl.api.permissions import SaplModelPermissions -from sapl.api.serializers import ChoiceSerializer, ParlamentarSerializer,\ - ParlamentarEditSerializer, ParlamentarResumeSerializer -from sapl.base.models import Autor, AppConfig, DOC_ADM_OSTENSIVO -from sapl.materia.models import Proposicao, TipoMateriaLegislativa,\ - MateriaLegislativa, Tramitacao -from sapl.norma.models import NormaJuridica -from sapl.parlamentares.models import Mandato, Legislatura -from sapl.parlamentares.models import Parlamentar -from sapl.protocoloadm.models import DocumentoAdministrativo,\ - DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado -from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao -from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria @receiver(post_save, sender=settings.AUTH_USER_MODEL) @@ -51,647 +26,6 @@ def recria_token(request, pk): return Response({"message": "Token recriado com sucesso!", "token": token.key}) -class BusinessRulesNotImplementedMixin: - def create(self, request, *args, **kwargs): - raise Exception(_("POST Create não implementado")) - - def update(self, request, *args, **kwargs): - raise Exception(_("PUT and PATCH não implementado")) - - def delete(self, request, *args, **kwargs): - raise Exception(_("DELETE Delete não implementado")) - - -class SaplApiViewSetConstrutor(): - - class SaplApiViewSet(ModelViewSet): - filter_backends = (DjangoFilterBackend,) - - _built_sets = {} - - @classonlymethod - def get_class_for_model(cls, model): - return cls._built_sets[model._meta.app_config][model] - - @classonlymethod - def build_class(cls): - import inspect - from sapl.api import serializers - - # Carrega todas as classes de sapl.api.serializers que possuam - # "Serializer" como Sufixo. - serializers_classes = inspect.getmembers(serializers) - serializers_classes = {i[0]: i[1] for i in filter( - lambda x: x[0].endswith('Serializer'), - serializers_classes - )} - - # Carrega todas as classes de sapl.api.forms que possuam - # "FilterSet" como Sufixo. - from sapl.api import forms - filters_classes = inspect.getmembers(forms) - filters_classes = {i[0]: i[1] for i in filter( - lambda x: x[0].endswith('FilterSet'), - filters_classes - )} - - built_sets = {} - - def build(_model): - object_name = _model._meta.object_name - - # Caso Exista, pega a classe sapl.api.serializers.{model}Serializer - # ou utiliza a base do drf para gerar uma automática para o model - serializer_name = f'{object_name}Serializer' - _serializer_class = serializers_classes.get( - serializer_name, rest_serializers.ModelSerializer) - - # Caso Exista, pega a classe sapl.api.forms.{model}FilterSet - # ou utiliza a base definida em sapl.forms.SaplFilterSetMixin - filter_name = f'{object_name}FilterSet' - _filterset_class = filters_classes.get( - filter_name, SaplFilterSetMixin) - - def create_class(): - - _meta_serializer = object if not hasattr( - _serializer_class, 'Meta') else _serializer_class.Meta - - # Define uma classe padrão para serializer caso não tenha sido - # criada a classe sapl.api.serializers.{model}Serializer - class SaplSerializer(_serializer_class): - __str__ = SerializerMethodField() - - class Meta(_meta_serializer): - if not hasattr(_meta_serializer, 'ref_name'): - ref_name = None - - if not hasattr(_meta_serializer, 'model'): - model = _model - - if hasattr(_meta_serializer, 'exclude'): - exclude = _meta_serializer.exclude - else: - if not hasattr(_meta_serializer, 'fields'): - fields = '__all__' - elif _meta_serializer.fields != '__all__': - fields = list( - _meta_serializer.fields) + ['__str__', ] - else: - fields = _meta_serializer.fields - - def get___str__(self, obj): - return str(obj) - - _meta_filterset = object if not hasattr( - _filterset_class, 'Meta') else _filterset_class.Meta - - # Define uma classe padrão para filtro caso não tenha sido - # criada a classe sapl.api.forms.{model}FilterSet - class SaplFilterSet(_filterset_class): - class Meta(_meta_filterset): - if not hasattr(_meta_filterset, 'model'): - model = _model - - # Define uma classe padrão ModelViewSet de DRF - class ModelSaplViewSet(SaplApiViewSetConstrutor.SaplApiViewSet): - queryset = _model.objects.all() - - # Utiliza o filtro customizado pela classe - # sapl.api.forms.{model}FilterSet - # ou utiliza o trivial SaplFilterSet definido acima - filterset_class = SaplFilterSet - - # Utiliza o serializer customizado pela classe - # sapl.api.serializers.{model}Serializer - # ou utiliza o trivial SaplSerializer definido acima - serializer_class = SaplSerializer - - return ModelSaplViewSet - - viewset = create_class() - viewset.__name__ = '%sModelSaplViewSet' % _model.__name__ - return viewset - - apps_sapl = [apps.apps.get_app_config( - n[5:]) for n in settings.SAPL_APPS] - for app in apps_sapl: - cls._built_sets[app] = {} - for model in app.get_models(): - cls._built_sets[app][model] = build(model) - - -SaplApiViewSetConstrutor.build_class() - -""" -1. Constroi uma rest_framework.viewsets.ModelViewSet para - todos os models de todas as apps do sapl -2. Define DjangoFilterBackend como ferramenta de filtro dos campos -3. Define Serializer como a seguir: - 3.1 - Define um Serializer genérico para cada módel - 3.2 - Recupera Serializer customizado em sapl.api.serializers - 3.3 - Para todo model é opcional a existência de - sapl.api.serializers.{model}Serializer. - Caso não seja definido um Serializer customizado, utiliza-se o trivial -4. Define um FilterSet como a seguir: - 4.1 - Define um FilterSet genérico para cada módel - 4.2 - Recupera FilterSet customizado em sapl.api.forms - 4.3 - Para todo model é opcional a existência de - sapl.api.forms.{model}FilterSet. - Caso não seja definido um FilterSet customizado, utiliza-se o trivial - 4.4 - todos os campos que aceitam lookup 'exact' - podem ser filtrados por default - -5. SaplApiViewSetConstrutor não cria padrões e/ou exige conhecimento alem dos - exigidos pela DRF. - -6. As rotas são criadas seguindo nome da app e nome do model - http://localhost:9000/api/{applabel}/{model_name}/ - e seguem as variações definidas em: - https://www.django-rest-framework.org/api-guide/routers/#defaultrouter - -7. Todas as viewsets construídas por SaplApiViewSetConstrutor e suas rotas - (paginate list, detail, edit, create, delete) - bem como testes em ambiente de desenvolvimento podem ser conferidas em: - http://localhost:9000/api/ - desde que settings.DEBUG=True - -**SaplApiViewSetConstrutor._built_sets** é um dict de dicts de models conforme: - { - ... - - 'audiencia': { - 'tipoaudienciapublica': TipoAudienciaPublicaViewSet, - 'audienciapublica': AudienciaPublicaViewSet, - 'anexoaudienciapublica': AnexoAudienciaPublicaViewSet - - ... - - }, - - ... - - 'base': { - 'casalegislativa': CasaLegislativaViewSet, - 'appconfig': AppConfigViewSet, - - ... - - } - - ... - - } -""" - -# Toda Classe construida acima, pode ser redefinida e aplicado quaisquer -# das possibilidades para uma classe normal criada a partir de -# rest_framework.viewsets.ModelViewSet conforme exemplo para a classe autor - -# decorator que processa um endpoint detail trivial com base no model passado, -# Um endpoint detail geralmente é um conteúdo baseado numa FK com outros possíveis filtros -# e os passados pelo proprio cliente, além de o serializer e o filterset -# ser desse model passado - - -class wrapper_queryset_response_for_drf_action(object): - def __init__(self, model): - self.model = model - - def __call__(self, cls): - - def wrapper(instance_view, *args, **kwargs): - # recupera a viewset do model anotado - iv = instance_view - viewset_from_model = SaplApiViewSetConstrutor._built_sets[ - self.model._meta.app_config][self.model] - - # apossa da instancia da viewset mae do action - # em uma viewset que processa dados do model passado no decorator - iv.queryset = viewset_from_model.queryset - iv.serializer_class = viewset_from_model.serializer_class - iv.filterset_class = viewset_from_model.filterset_class - - iv.queryset = instance_view.filter_queryset( - iv.get_queryset()) - - # chama efetivamente o metodo anotado que deve devolver um queryset - # com os filtros específicos definido pelo programador customizador - qs = cls(instance_view, *args, **kwargs) - - page = iv.paginate_queryset(qs) - data = iv.get_serializer( - page if page is not None else qs, many=True).data - - return iv.get_paginated_response( - data) if page is not None else Response(data) - - return wrapper - - -# decorator para recuperar e transformar o default -class customize(object): - def __init__(self, model): - self.model = model - - def __call__(self, cls): - - class _SaplApiViewSet( - cls, - SaplApiViewSetConstrutor._built_sets[ - self.model._meta.app_config][self.model] - ): - pass - - if hasattr(_SaplApiViewSet, 'build'): - _SaplApiViewSet = _SaplApiViewSet.build() - - SaplApiViewSetConstrutor._built_sets[ - self.model._meta.app_config][self.model] = _SaplApiViewSet - return _SaplApiViewSet - - -# Customização para AutorViewSet com implementação de actions específicas - - -@customize(Autor) -class _AutorViewSet: - """ - Neste exemplo de customização do que foi criado em - SaplApiViewSetConstrutor além do ofertado por - rest_framework.viewsets.ModelViewSet, dentre outras customizações - possíveis, foi adicionado as rotas referentes aos relacionamentos genéricos - - * padrão de ModelViewSet - /api/base/autor/ POST - create - /api/base/autor/ GET - list - /api/base/autor/{pk}/ GET - detail - /api/base/autor/{pk}/ PUT - update - /api/base/autor/{pk}/ PATCH - partial_update - /api/base/autor/{pk}/ DELETE - destroy - - * rotas desta classe local criadas pelo método build: - /api/base/autor/parlamentar - devolve apenas autores que são parlamentares - /api/base/autor/comissao - devolve apenas autores que são comissões - /api/base/autor/bloco - devolve apenas autores que são blocos parlamentares - /api/base/autor/bancada - devolve apenas autores que são bancadas parlamentares - /api/base/autor/frente - devolve apenas autores que são Frene parlamentares - /api/base/autor/orgao - devolve apenas autores que são Órgãos - """ - - def list_for_content_type(self, content_type): - qs = self.get_queryset() - qs = qs.filter(content_type=content_type) - - page = self.paginate_queryset(qs) - if page is not None: - serializer = self.serializer_class(page, many=True) - return self.get_paginated_response(serializer.data) - - serializer = self.get_serializer(page, many=True) - return Response(serializer.data) - - @classonlymethod - def build(cls): - - models_with_gr_for_autor = models_with_gr_for_model(Autor) - - for _model in models_with_gr_for_autor: - - @action(detail=False, name=_model._meta.model_name) - def actionclass(self, request, *args, **kwargs): - model = getattr(self, self.action)._AutorViewSet__model - - content_type = ContentType.objects.get_for_model(model) - return self.list_for_content_type(content_type) - - func = actionclass - func.mapping['get'] = func.kwargs['name'] - func.url_name = func.kwargs['name'] - func.url_path = func.kwargs['name'] - func.__model = _model - - setattr(cls, _model._meta.model_name, func) - return cls - - -@customize(Parlamentar) -class _ParlamentarViewSet: - class ParlamentarPermission(SaplModelPermissions): - def has_permission(self, request, view): - if request.method == 'GET': - return True - else: - perm = super().has_permission(request, view) - return perm - - permission_classes = (ParlamentarPermission, ) - - def get_serializer(self, *args, **kwargs): - if self.request.user.has_perm('parlamentares.add_parlamentar'): - self.serializer_class = ParlamentarEditSerializer - return super().get_serializer(*args, **kwargs) - - @action(detail=True) - def proposicoes(self, request, *args, **kwargs): - """ - Lista de proposições públicas de parlamentar específico - - :param int id: - Identificador do parlamentar que se quer recuperar as proposições - :return: uma lista de proposições - """ - # /api/parlamentares/parlamentar/{id}/proposicoes/ - # recupera proposições enviadas e incorporadas do parlamentar - # deve coincidir com - # /parlamentar/{pk}/proposicao - - return self.get_proposicoes(**kwargs) - - @wrapper_queryset_response_for_drf_action(model=Proposicao) - def get_proposicoes(self, **kwargs): - - return self.get_queryset().filter( - data_envio__isnull=False, - data_recebimento__isnull=False, - cancelado=False, - autor__object_id=kwargs['pk'], - autor__content_type=ContentType.objects.get_for_model(Parlamentar) - ) - - @action(detail=False, methods=['GET']) - def search_parlamentares(self, request, *args, **kwargs): - nome = request.query_params.get('nome_parlamentar', '') - parlamentares = Parlamentar.objects.filter( - nome_parlamentar__icontains=nome) - serializer_class = ParlamentarResumeSerializer( - parlamentares, many=True, context={'request': request}) - return Response(serializer_class.data) - - -@customize(Legislatura) -class _LegislaturaViewSet: - - @action(detail=True) - def parlamentares(self, request, *args, **kwargs): - - def get_serializer_context(): - return { - 'request': self.request, 'legislatura': kwargs['pk'] - } - - def get_serializer_class(): - return ParlamentarResumeSerializer - - self.get_serializer_context = get_serializer_context - self.get_serializer_class = get_serializer_class - - return self.get_parlamentares() - - @wrapper_queryset_response_for_drf_action(model=Parlamentar) - def get_parlamentares(self): - - try: - legislatura = Legislatura.objects.get(pk=self.kwargs['pk']) - except ObjectDoesNotExist: - return Response("") - - data_atual = timezone.localdate() - - filter_params = { - 'legislatura': legislatura, - 'data_inicio_mandato__gte': legislatura.data_inicio, - 'data_fim_mandato__lte': legislatura.data_fim, - } - - mandatos = Mandato.objects.filter( - **filter_params).order_by('-data_inicio_mandato') - - parlamentares = self.get_queryset().filter( - mandato__in=mandatos).distinct() - - return parlamentares - - -@customize(Proposicao) -class _ProposicaoViewSet: - """ - list: - Retorna lista de Proposições - - * Permissões: - - * Usuário Dono: - * Pode listar todas suas Proposições - - * Usuário Conectado ou Anônimo: - * Pode listar todas as Proposições incorporadas - - retrieve: - Retorna uma proposição passada pelo 'id' - - * Permissões: - - * Usuário Dono: - * Pode recuperar qualquer de suas Proposições - - * Usuário Conectado ou Anônimo: - * Pode recuperar qualquer das proposições incorporadas - - """ - class ProposicaoPermission(SaplModelPermissions): - def has_permission(self, request, view): - if request.method == 'GET': - return True - # se a solicitação é list ou detail, libera o teste de permissão - # e deixa o get_queryset filtrar de acordo com a regra de - # visibilidade das proposições, ou seja: - # 1. proposição incorporada é proposição pública - # 2. não incorporada só o autor pode ver - else: - perm = super().has_permission(request, view) - return perm - # não é list ou detail, então passa pelas regras de permissão e, - # depois disso ainda passa pelo filtro de get_queryset - - permission_classes = (ProposicaoPermission, ) - - def get_queryset(self): - qs = super().get_queryset() - - q = Q(data_recebimento__isnull=False, object_id__isnull=False) - if not self.request.user.is_anonymous: - - autor_do_usuario_logado = self.request.user.autor_set.first() - - # se usuário logado é operador de algum autor - if autor_do_usuario_logado: - q = Q(autor=autor_do_usuario_logado) - - # se é operador de protocolo, ve qualquer coisa enviada - if self.request.user.has_perm('protocoloadm.list_protocolo'): - q = Q(data_envio__isnull=False) | Q( - data_devolucao__isnull=False) - - qs = qs.filter(q) - return qs - - -@customize(MateriaLegislativa) -class _MateriaLegislativaViewSet: - class Meta: - ordering = ['-ano', 'tipo', 'numero'] - - @action(detail=True, methods=['GET']) - def ultima_tramitacao(self, request, *args, **kwargs): - - materia = self.get_object() - if not materia.tramitacao_set.exists(): - return Response({}) - - ultima_tramitacao = materia.tramitacao_set.order_by( - '-data_tramitacao', '-id').first() - - serializer_class = SaplApiViewSetConstrutor.get_class_for_model( - Tramitacao).serializer_class(ultima_tramitacao) - - return Response(serializer_class.data) - - @action(detail=True, methods=['GET']) - def anexadas(self, request, *args, **kwargs): - self.queryset = self.get_object().anexadas.all() - return self.list(request, *args, **kwargs) - - -@customize(TipoMateriaLegislativa) -class _TipoMateriaLegislativaViewSet: - - @action(detail=True, methods=['POST']) - def change_position(self, request, *args, **kwargs): - result = { - 'status': 200, - 'message': 'OK' - } - d = request.data - if 'pos_ini' in d and 'pos_fim' in d: - if d['pos_ini'] != d['pos_fim']: - pk = kwargs['pk'] - TipoMateriaLegislativa.objects.reposicione(pk, d['pos_fim']) - - return Response(result) - - -@customize(DocumentoAdministrativo) -class _DocumentoAdministrativoViewSet: - - class DocumentoAdministrativoPermission(SaplModelPermissions): - def has_permission(self, request, view): - if request.method == 'GET': - comportamento = AppConfig.attr('documentos_administrativos') - if comportamento == DOC_ADM_OSTENSIVO: - return True - """ - Diante da lógica implementada na manutenção de documentos - administrativos: - - Se o comportamento é doc adm ostensivo, deve passar pelo - teste de permissões sem avaliá-las - - se o comportamento é doc adm restritivo, deve passar pelo - teste de permissões avaliando-as - """ - return super().has_permission(request, view) - - permission_classes = (DocumentoAdministrativoPermission, ) - - def get_queryset(self): - """ - mesmo tendo passado pelo teste de permissões, deve ser filtrado, - pelo campo restrito. Sendo este igual a True, disponibilizar apenas - a um usuário conectado. Apenas isso, sem critérios outros de permissão, - conforme implementado em DocumentoAdministrativoCrud - """ - qs = super().get_queryset() - - if self.request.user.is_anonymous: - qs = qs.exclude(restrito=True) - return qs - - -@customize(DocumentoAcessorioAdministrativo) -class _DocumentoAcessorioAdministrativoViewSet: - - permission_classes = ( - _DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, ) - - def get_queryset(self): - qs = super().get_queryset() - - if self.request.user.is_anonymous: - qs = qs.exclude(documento__restrito=True) - return qs - - -@customize(TramitacaoAdministrativo) -class _TramitacaoAdministrativoViewSet(BusinessRulesNotImplementedMixin): - # TODO: Implementar regras de manutenção das tramitações de docs adms - - permission_classes = ( - _DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, ) - - def get_queryset(self): - qs = super().get_queryset() - - if self.request.user.is_anonymous: - qs = qs.exclude(documento__restrito=True) - return qs - - -@customize(Anexado) -class _AnexadoViewSet(BusinessRulesNotImplementedMixin): - - permission_classes = ( - _DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, ) - - def get_queryset(self): - qs = super().get_queryset() - - if self.request.user.is_anonymous: - qs = qs.exclude(documento__restrito=True) - return qs - - -@customize(SessaoPlenaria) -class _SessaoPlenariaViewSet: - - @action(detail=False) - def years(self, request, *args, **kwargs): - years = choice_anos_com_sessaoplenaria() - - serializer = ChoiceSerializer(years, many=True) - return Response(serializer.data) - - @action(detail=True) - def expedientes(self, request, *args, **kwargs): - return self.get_expedientes() - - @wrapper_queryset_response_for_drf_action(model=ExpedienteSessao) - def get_expedientes(self): - return self.get_queryset().filter(sessao_plenaria_id=self.kwargs['pk']) - - -@customize(NormaJuridica) -class _NormaJuridicaViewset: - - @action(detail=False, methods=['GET']) - def destaques(self, request, *args, **kwargs): - self.queryset = self.get_queryset().filter(norma_de_destaque=True) - return self.list(request, *args, **kwargs) - - class AppVersionView(APIView): permission_classes = (IsAuthenticated,) diff --git a/sapl/api/views_customize.py b/sapl/api/views_customize.py new file mode 100644 index 000000000..e8546ed05 --- /dev/null +++ b/sapl/api/views_customize.py @@ -0,0 +1,406 @@ +import logging + +from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Q +from django.utils.decorators import classonlymethod +from django.utils.translation import ugettext_lazy as _ +from rest_framework.decorators import action +from rest_framework.response import Response + +from sapl.api.core import customize, SaplApiViewSetConstrutor,\ + wrapper_queryset_response_for_drf_action,\ + BusinessRulesNotImplementedMixin +from sapl.api.permissions import SaplModelPermissions +from sapl.api.serializers import ChoiceSerializer, \ + ParlamentarEditSerializer, ParlamentarResumeSerializer +from sapl.base.models import Autor, AppConfig, DOC_ADM_OSTENSIVO +from sapl.materia.models import Proposicao, TipoMateriaLegislativa,\ + MateriaLegislativa, Tramitacao +from sapl.norma.models import NormaJuridica +from sapl.parlamentares.models import Mandato, Legislatura +from sapl.parlamentares.models import Parlamentar +from sapl.protocoloadm.models import DocumentoAdministrativo,\ + DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado +from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao +from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria + + +SaplApiViewSetConstrutor = SaplApiViewSetConstrutor.build_class() + + +@customize(Autor) +class _AutorViewSet: + # Customização para AutorViewSet com implementação de actions específicas + """ + Neste exemplo de customização do que foi criado em + SaplApiViewSetConstrutor além do ofertado por + rest_framework.viewsets.ModelViewSet, dentre outras customizações + possíveis, foi adicionado as rotas referentes aos relacionamentos genéricos + + * padrão de ModelViewSet + /api/base/autor/ POST - create + /api/base/autor/ GET - list + /api/base/autor/{pk}/ GET - detail + /api/base/autor/{pk}/ PUT - update + /api/base/autor/{pk}/ PATCH - partial_update + /api/base/autor/{pk}/ DELETE - destroy + + * rotas desta classe local criadas pelo método build: + /api/base/autor/parlamentar + devolve apenas autores que são parlamentares + /api/base/autor/comissao + devolve apenas autores que são comissões + /api/base/autor/bloco + devolve apenas autores que são blocos parlamentares + /api/base/autor/bancada + devolve apenas autores que são bancadas parlamentares + /api/base/autor/frente + devolve apenas autores que são Frene parlamentares + /api/base/autor/orgao + devolve apenas autores que são Órgãos + """ + + def list_for_content_type(self, content_type): + qs = self.get_queryset() + qs = qs.filter(content_type=content_type) + + page = self.paginate_queryset(qs) + if page is not None: + serializer = self.serializer_class(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(page, many=True) + return Response(serializer.data) + + @classonlymethod + def build(cls): + + models_with_gr_for_autor = models_with_gr_for_model(Autor) + + for _model in models_with_gr_for_autor: + + @action(detail=False, name=_model._meta.model_name) + def actionclass(self, request, *args, **kwargs): + model = getattr(self, self.action)._AutorViewSet__model + + content_type = ContentType.objects.get_for_model(model) + return self.list_for_content_type(content_type) + + func = actionclass + func.mapping['get'] = func.kwargs['name'] + func.url_name = func.kwargs['name'] + func.url_path = func.kwargs['name'] + func.__model = _model + + setattr(cls, _model._meta.model_name, func) + return cls + + +@customize(Parlamentar) +class _ParlamentarViewSet: + class ParlamentarPermission(SaplModelPermissions): + def has_permission(self, request, view): + if request.method == 'GET': + return True + else: + perm = super().has_permission(request, view) + return perm + + permission_classes = (ParlamentarPermission, ) + + def get_serializer(self, *args, **kwargs): + if self.request.user.has_perm('parlamentares.add_parlamentar'): + self.serializer_class = ParlamentarEditSerializer + return super().get_serializer(*args, **kwargs) + + @action(detail=True) + def proposicoes(self, request, *args, **kwargs): + """ + Lista de proposições públicas de parlamentar específico + + :param int id: - Identificador do parlamentar que se quer recuperar as proposições + :return: uma lista de proposições + """ + # /api/parlamentares/parlamentar/{id}/proposicoes/ + # recupera proposições enviadas e incorporadas do parlamentar + # deve coincidir com + # /parlamentar/{pk}/proposicao + + return self.get_proposicoes(**kwargs) + + @wrapper_queryset_response_for_drf_action(model=Proposicao) + def get_proposicoes(self, **kwargs): + + return self.get_queryset().filter( + data_envio__isnull=False, + data_recebimento__isnull=False, + cancelado=False, + autor__object_id=kwargs['pk'], + autor__content_type=ContentType.objects.get_for_model(Parlamentar) + ) + + @action(detail=False, methods=['GET']) + def search_parlamentares(self, request, *args, **kwargs): + nome = request.query_params.get('nome_parlamentar', '') + parlamentares = Parlamentar.objects.filter( + nome_parlamentar__icontains=nome) + serializer_class = ParlamentarResumeSerializer( + parlamentares, many=True, context={'request': request}) + return Response(serializer_class.data) + + +@customize(Legislatura) +class _LegislaturaViewSet: + + @action(detail=True) + def parlamentares(self, request, *args, **kwargs): + + def get_serializer_context(): + return { + 'request': self.request, 'legislatura': kwargs['pk'] + } + + def get_serializer_class(): + return ParlamentarResumeSerializer + + self.get_serializer_context = get_serializer_context + self.get_serializer_class = get_serializer_class + + return self.get_parlamentares() + + @wrapper_queryset_response_for_drf_action(model=Parlamentar) + def get_parlamentares(self): + + try: + legislatura = Legislatura.objects.get(pk=self.kwargs['pk']) + except ObjectDoesNotExist: + return Response("") + + filter_params = { + 'legislatura': legislatura, + 'data_inicio_mandato__gte': legislatura.data_inicio, + 'data_fim_mandato__lte': legislatura.data_fim, + } + + mandatos = Mandato.objects.filter( + **filter_params).order_by('-data_inicio_mandato') + + parlamentares = self.get_queryset().filter( + mandato__in=mandatos).distinct() + + return parlamentares + + +@customize(Proposicao) +class _ProposicaoViewSet: + """ + list: + Retorna lista de Proposições + + * Permissões: + + * Usuário Dono: + * Pode listar todas suas Proposições + + * Usuário Conectado ou Anônimo: + * Pode listar todas as Proposições incorporadas + + retrieve: + Retorna uma proposição passada pelo 'id' + + * Permissões: + + * Usuário Dono: + * Pode recuperar qualquer de suas Proposições + + * Usuário Conectado ou Anônimo: + * Pode recuperar qualquer das proposições incorporadas + + """ + class ProposicaoPermission(SaplModelPermissions): + def has_permission(self, request, view): + if request.method == 'GET': + return True + # se a solicitação é list ou detail, libera o teste de permissão + # e deixa o get_queryset filtrar de acordo com a regra de + # visibilidade das proposições, ou seja: + # 1. proposição incorporada é proposição pública + # 2. não incorporada só o autor pode ver + else: + perm = super().has_permission(request, view) + return perm + # não é list ou detail, então passa pelas regras de permissão e, + # depois disso ainda passa pelo filtro de get_queryset + + permission_classes = (ProposicaoPermission, ) + + def get_queryset(self): + qs = super().get_queryset() + + q = Q(data_recebimento__isnull=False, object_id__isnull=False) + if not self.request.user.is_anonymous: + + autor_do_usuario_logado = self.request.user.autor_set.first() + + # se usuário logado é operador de algum autor + if autor_do_usuario_logado: + q = Q(autor=autor_do_usuario_logado) + + # se é operador de protocolo, ve qualquer coisa enviada + if self.request.user.has_perm('protocoloadm.list_protocolo'): + q = Q(data_envio__isnull=False) | Q( + data_devolucao__isnull=False) + + qs = qs.filter(q) + return qs + + +@customize(MateriaLegislativa) +class _MateriaLegislativaViewSet: + class Meta: + ordering = ['-ano', 'tipo', 'numero'] + + @action(detail=True, methods=['GET']) + def ultima_tramitacao(self, request, *args, **kwargs): + + materia = self.get_object() + if not materia.tramitacao_set.exists(): + return Response({}) + + ultima_tramitacao = materia.tramitacao_set.order_by( + '-data_tramitacao', '-id').first() + + serializer_class = SaplApiViewSetConstrutor.get_class_for_model( + Tramitacao).serializer_class(ultima_tramitacao) + + return Response(serializer_class.data) + + @action(detail=True, methods=['GET']) + def anexadas(self, request, *args, **kwargs): + self.queryset = self.get_object().anexadas.all() + return self.list(request, *args, **kwargs) + + +@customize(TipoMateriaLegislativa) +class _TipoMateriaLegislativaViewSet: + + @action(detail=True, methods=['POST']) + def change_position(self, request, *args, **kwargs): + result = { + 'status': 200, + 'message': 'OK' + } + d = request.data + if 'pos_ini' in d and 'pos_fim' in d: + if d['pos_ini'] != d['pos_fim']: + pk = kwargs['pk'] + TipoMateriaLegislativa.objects.reposicione(pk, d['pos_fim']) + + return Response(result) + + +@customize(DocumentoAdministrativo) +class _DocumentoAdministrativoViewSet: + + class DocumentoAdministrativoPermission(SaplModelPermissions): + def has_permission(self, request, view): + if request.method == 'GET': + comportamento = AppConfig.attr('documentos_administrativos') + if comportamento == DOC_ADM_OSTENSIVO: + return True + """ + Diante da lógica implementada na manutenção de documentos + administrativos: + - Se o comportamento é doc adm ostensivo, deve passar pelo + teste de permissões sem avaliá-las + - se o comportamento é doc adm restritivo, deve passar pelo + teste de permissões avaliando-as + """ + return super().has_permission(request, view) + + permission_classes = (DocumentoAdministrativoPermission, ) + + def get_queryset(self): + """ + mesmo tendo passado pelo teste de permissões, deve ser filtrado, + pelo campo restrito. Sendo este igual a True, disponibilizar apenas + a um usuário conectado. Apenas isso, sem critérios outros de permissão, + conforme implementado em DocumentoAdministrativoCrud + """ + qs = super().get_queryset() + + if self.request.user.is_anonymous: + qs = qs.exclude(restrito=True) + return qs + + +@customize(DocumentoAcessorioAdministrativo) +class _DocumentoAcessorioAdministrativoViewSet: + + permission_classes = ( + _DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, ) + + def get_queryset(self): + qs = super().get_queryset() + + if self.request.user.is_anonymous: + qs = qs.exclude(documento__restrito=True) + return qs + + +@customize(TramitacaoAdministrativo) +class _TramitacaoAdministrativoViewSet(BusinessRulesNotImplementedMixin): + # TODO: Implementar regras de manutenção das tramitações de docs adms + + permission_classes = ( + _DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, ) + + def get_queryset(self): + qs = super().get_queryset() + + if self.request.user.is_anonymous: + qs = qs.exclude(documento__restrito=True) + return qs + + +@customize(Anexado) +class _AnexadoViewSet(BusinessRulesNotImplementedMixin): + + permission_classes = ( + _DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, ) + + def get_queryset(self): + qs = super().get_queryset() + + if self.request.user.is_anonymous: + qs = qs.exclude(documento__restrito=True) + return qs + + +@customize(SessaoPlenaria) +class _SessaoPlenariaViewSet: + + @action(detail=False) + def years(self, request, *args, **kwargs): + years = choice_anos_com_sessaoplenaria() + + serializer = ChoiceSerializer(years, many=True) + return Response(serializer.data) + + @action(detail=True) + def expedientes(self, request, *args, **kwargs): + return self.get_expedientes() + + @wrapper_queryset_response_for_drf_action(model=ExpedienteSessao) + def get_expedientes(self): + return self.get_queryset().filter(sessao_plenaria_id=self.kwargs['pk']) + + +@customize(NormaJuridica) +class _NormaJuridicaViewset: + + @action(detail=False, methods=['GET']) + def destaques(self, request, *args, **kwargs): + self.queryset = self.get_queryset().filter(norma_de_destaque=True) + return self.list(request, *args, **kwargs) From 12847db6dc247fc9dbb7c705320d85b704e964e2 Mon Sep 17 00:00:00 2001 From: Gustavo274 <51173319+Gustavo274@users.noreply.github.com> Date: Fri, 9 Jul 2021 15:48:01 -0300 Subject: [PATCH 04/22] Conserto do OsTicket #986795 (#3424) Co-authored-by: Gustavo274 --- sapl/relatorios/views.py | 182 ++++++++++++++++++++++++--------------- sapl/sessao/views.py | 86 +++++++++++------- 2 files changed, 168 insertions(+), 100 deletions(-) diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 6b1f5b114..2cf849071 100755 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -112,7 +112,7 @@ def get_materias(mats): for materia in mats: dic = {} dic['titulo'] = materia.tipo.sigla + " " + materia.tipo.descricao \ - + " " + str(materia.numero) + "/" + str(materia.ano) + + " " + str(materia.numero) + "/" + str(materia.ano) dic['txt_ementa'] = materia.ementa dic['nom_autor'] = ', '.join( @@ -535,7 +535,8 @@ def get_sessao_plenaria(sessao, casa): for composicao in IntegranteMesa.objects.select_related('parlamentar', 'cargo')\ .filter(sessao_plenaria=sessao)\ .order_by('cargo_id'): - partido_sigla = Filiacao.objects.filter(parlamentar=composicao.parlamentar).first() + partido_sigla = Filiacao.objects.filter( + parlamentar=composicao.parlamentar).first() sigla = '' if not partido_sigla else partido_sigla.partido.sigla lst_mesa.append({ 'nom_parlamentar': composicao.parlamentar.nome_parlamentar, @@ -545,7 +546,8 @@ def get_sessao_plenaria(sessao, casa): # Lista de presença na sessão lst_presenca_sessao = [] - presenca = SessaoPlenariaPresenca.objects.filter(sessao_plenaria=sessao).order_by('parlamentar__nome_parlamentar') + presenca = SessaoPlenariaPresenca.objects.filter( + sessao_plenaria=sessao).order_by('parlamentar__nome_parlamentar') for parlamentar in [p.parlamentar for p in presenca]: lst_presenca_sessao.append({ "nom_parlamentar": parlamentar.nome_parlamentar, @@ -554,7 +556,8 @@ def get_sessao_plenaria(sessao, casa): # Lista de ausencias na sessão lst_ausencia_sessao = [] - ausencia = JustificativaAusencia.objects.filter(sessao_plenaria=sessao).order_by('parlamentar__nome_parlamentar') + ausencia = JustificativaAusencia.objects.filter( + sessao_plenaria=sessao).order_by('parlamentar__nome_parlamentar') for ausente in ausencia: lst_ausencia_sessao.append({ "parlamentar": ausente.parlamentar, @@ -564,7 +567,8 @@ def get_sessao_plenaria(sessao, casa): # Exibe os Expedientes lst_expedientes = [] - expedientes = ExpedienteSessao.objects.filter(sessao_plenaria=sessao).order_by('tipo__nome') + expedientes = ExpedienteSessao.objects.filter( + sessao_plenaria=sessao).order_by('tipo__nome') for e in expedientes: conteudo = e.conteudo if not is_empty(conteudo): @@ -572,9 +576,11 @@ def get_sessao_plenaria(sessao, casa): # https://github.com/interlegis/sapl/issues/1046 conteudo = re.sub('style=".*?"', '', conteudo) conteudo = re.sub('class=".*?"', '', conteudo) - conteudo = re.sub('align=".*?"', '', conteudo) # OSTicket Ticket #796450 + # OSTicket Ticket #796450 + conteudo = re.sub('align=".*?"', '', conteudo) conteudo = re.sub('', '

', conteudo) - conteudo = re.sub('', '
', conteudo) # OSTicket Ticket #796450 + # OSTicket Ticket #796450 + conteudo = re.sub('', '
', conteudo) conteudo = html.unescape(conteudo) # escape special character '&' @@ -612,12 +618,15 @@ def get_sessao_plenaria(sessao, casa): "votacao_observacao": ' ' } - numeracao = Numeracao.objects.filter(materia=expediente_materia.materia).first() + numeracao = Numeracao.objects.filter( + materia=expediente_materia.materia).first() if numeracao: - dic_expediente_materia["des_numeracao"] = (str(numeracao.numero_materia) + '/' + str(numeracao.ano_materia)) + dic_expediente_materia["des_numeracao"] = ( + str(numeracao.numero_materia) + '/' + str(numeracao.ano_materia)) autoria = materia.autoria_set.all() - dic_expediente_materia['num_autores'] = 'Autores' if len(autoria) > 1 else 'Autor' + dic_expediente_materia['num_autores'] = 'Autores' if len( + autoria) > 1 else 'Autor' if autoria: for a in autoria: if a.autor.nome: @@ -630,14 +639,19 @@ def get_sessao_plenaria(sessao, casa): materia=expediente_materia.materia).first() rp = expediente_materia.retiradapauta_set.filter( materia=expediente_materia.materia).first() + rl = expediente_materia.registroleitura_set.filter( + materia=expediente_materia.materia).first() if rv: resultado = rv.tipo_resultado_votacao.nome resultado_observacao = rv.observacao elif rp: resultado = rp.tipo_de_retirada.descricao resultado_observacao = rp.observacao + elif rl: + resultado = _('Matéria lida') + resultado_observacao = rl.observacao else: - resultado = _('Matéria lida') \ + resultado = _('Matéria não lida') \ if expediente_materia.tipo_votacao == 4 \ else _('Matéria não votada') resultado_observacao = _(' ') @@ -652,7 +666,7 @@ def get_sessao_plenaria(sessao, casa): # Lista dos votos nominais das matérias do Expediente lst_expediente_materia_vot_nom = [] - materias_expediente_votacao_nominal = ExpedienteMateria.objects.filter(sessao_plenaria=sessao,tipo_votacao=2)\ + materias_expediente_votacao_nominal = ExpedienteMateria.objects.filter(sessao_plenaria=sessao, tipo_votacao=2)\ .order_by('-materia') for mevn in materias_expediente_votacao_nominal: @@ -672,8 +686,10 @@ def get_sessao_plenaria(sessao, casa): # Lista dos oradores do Expediente lst_oradores_expediente = [] for orador_expediente in OradorExpediente.objects.filter(sessao_plenaria=sessao).order_by('numero_ordem'): - parlamentar = Parlamentar.objects.get(id=orador_expediente.parlamentar.id) - partido_sigla = Filiacao.objects.filter(parlamentar=parlamentar).first() + parlamentar = Parlamentar.objects.get( + id=orador_expediente.parlamentar.id) + partido_sigla = Filiacao.objects.filter( + parlamentar=parlamentar).first() lst_oradores_expediente.append({ "num_ordem": orador_expediente.numero_ordem, "nom_parlamentar": parlamentar.nome_parlamentar, @@ -709,7 +725,8 @@ def get_sessao_plenaria(sessao, casa): numeracao = materia.numeracao_set.first() if numeracao: - dic_votacao["des_numeracao"] = (str(numeracao.numero_materia) + '/' + str(numeracao.ano_materia)) + dic_votacao["des_numeracao"] = ( + str(numeracao.numero_materia) + '/' + str(numeracao.ano_materia)) materia_em_tramitacao = materia.materiaemtramitacao_set.first() dic_votacao.update({ @@ -736,14 +753,19 @@ def get_sessao_plenaria(sessao, casa): materia=votacao.materia).first() rp = votacao.retiradapauta_set.filter( materia=votacao.materia).first() + rl = votacao.registroleitura_set.filter( + materia=votacao.materia).first() if rv: resultado = rv.tipo_resultado_votacao.nome resultado_observacao = rv.observacao elif rp: resultado = rp.tipo_de_retirada.descricao resultado_observacao = rp.observacao + elif rl: + resultado = _('Matéria lida') + resultado_observacao = rl.observacao else: - resultado = _('Matéria lida') if \ + resultado = _('Matéria não lida') if \ votacao.tipo_votacao == 4 else _('Matéria não votada') resultado_observacao = _(' ') @@ -777,11 +799,14 @@ def get_sessao_plenaria(sessao, casa): # Lista dos oradores da Ordem do Dia lst_oradores_ordemdia = [] - oradores_ordem_dia = OradorOrdemDia.objects.filter(sessao_plenaria=sessao).order_by('numero_ordem') + oradores_ordem_dia = OradorOrdemDia.objects.filter( + sessao_plenaria=sessao).order_by('numero_ordem') for orador_ordemdia in oradores_ordem_dia: - parlamentar_orador = Parlamentar.objects.get(id=orador_ordemdia.parlamentar.id) - sigla_partido = Filiacao.objects.filter(parlamentar=parlamentar_orador).first() + parlamentar_orador = Parlamentar.objects.get( + id=orador_ordemdia.parlamentar.id) + sigla_partido = Filiacao.objects.filter( + parlamentar=parlamentar_orador).first() lst_oradores_ordemdia.append({ "num_ordem": orador_ordemdia.numero_ordem, @@ -794,7 +819,8 @@ def get_sessao_plenaria(sessao, casa): lst_oradores = [] for orador in Orador.objects.select_related('parlamentar').filter(sessao_plenaria=sessao).order_by('numero_ordem'): parlamentar = orador.parlamentar - partido_sigla = orador.parlamentar.filiacao_set.select_related('partido', 'parlamentar').first() + partido_sigla = orador.parlamentar.filiacao_set.select_related( + 'partido', 'parlamentar').first() lst_oradores.append({ "num_ordem": orador.numero_ordem, "nom_parlamentar": parlamentar.nome_parlamentar, @@ -841,7 +867,8 @@ def get_sessao_plenaria(sessao, casa): def get_turno(materia): descricao_turno = '' descricao_tramitacao = '' - tramitacoes = materia.tramitacao_set.order_by('-data_tramitacao', '-id').all() + tramitacoes = materia.tramitacao_set.order_by( + '-data_tramitacao', '-id').all() tramitacoes_turno = tramitacoes.exclude(turno="") if tramitacoes: @@ -850,7 +877,8 @@ def get_turno(materia): if t[0] == tramitacoes_turno.first().turno: descricao_turno = str(t[1]) break - descricao_tramitacao = tramitacoes.first().status.descricao if tramitacoes.first().status else 'Não informada' + descricao_tramitacao = tramitacoes.first( + ).status.descricao if tramitacoes.first().status else 'Não informada' return descricao_turno, descricao_tramitacao @@ -936,10 +964,10 @@ def get_protocolos(prots): ts = timezone.localtime(protocolo.timestamp) if protocolo.timestamp: dic['data'] = ts.strftime("%d/%m/%Y") + ' - Horário:' + \ - ts.strftime("%H:%m") + ts.strftime("%H:%m") else: dic['data'] = protocolo.data.strftime("%d/%m/%Y") + ' - Horário:' \ - + protocolo.hora.strftime("%H:%m") + + protocolo.hora.strftime("%H:%m") dic['txt_assunto'] = protocolo.assunto_ementa @@ -1030,7 +1058,7 @@ def relatorio_etiqueta_protocolo(request, nro, ano): protocolo = Protocolo.objects.filter(numero=nro, ano=ano) - m = MateriaLegislativa.objects.filter(numero_protocolo=nro,ano=ano) + m = MateriaLegislativa.objects.filter(numero_protocolo=nro, ano=ano) protocolo_data = get_etiqueta_protocolos(protocolo) @@ -1067,7 +1095,7 @@ def get_etiqueta_protocolos(prots): for materia in MateriaLegislativa.objects.filter( numero_protocolo=p.numero, ano=p.ano): dic['num_materia'] = materia.tipo.sigla + ' ' + \ - str(materia.numero) + '/' + str(materia.ano) + str(materia.numero) + '/' + str(materia.ano) dic['natureza'] = '' if p.tipo_processo == 0: @@ -1079,7 +1107,7 @@ def get_etiqueta_protocolos(prots): for documento in DocumentoAdministrativo.objects.filter( protocolo=p): dic['num_documento'] = documento.tipo.sigla + ' ' + \ - str(documento.numero) + '/' + str(documento.ano) + str(documento.numero) + '/' + str(documento.ano) dic['ident_processo'] = dic['num_materia'] or dic['num_documento'] @@ -1145,7 +1173,7 @@ def get_pauta_sessao(sessao, casa): dic_expediente_materia = {} dic_expediente_materia["tipo_materia"] = materia.tipo.sigla + \ - ' - ' + materia.tipo.descricao + ' - ' + materia.tipo.descricao dic_expediente_materia["num_ordem"] = str( expediente_materia.numero_ordem) dic_expediente_materia["id_materia"] = str( @@ -1189,7 +1217,7 @@ def get_pauta_sessao(sessao, casa): id=votacao.materia.id).first() dic_votacao = {} dic_votacao["tipo_materia"] = materia.tipo.sigla + \ - ' - ' + materia.tipo.descricao + ' - ' + materia.tipo.descricao dic_votacao["num_ordem"] = votacao.numero_ordem dic_votacao["id_materia"] = str( materia.numero) + "/" + str(materia.ano) @@ -1231,9 +1259,11 @@ def get_pauta_sessao(sessao, casa): # https://github.com/interlegis/sapl/issues/1046 conteudo = re.sub('style=".*?"', '', conteudo) conteudo = re.sub('class=".*?"', '', conteudo) - conteudo = re.sub('align=".*?"', '', conteudo) # OSTicket Ticket #796450 + # OSTicket Ticket #796450 + conteudo = re.sub('align=".*?"', '', conteudo) conteudo = re.sub('', '

', conteudo) - conteudo = re.sub('', '
', conteudo) # OSTicket Ticket #796450 + # OSTicket Ticket #796450 + conteudo = re.sub('', '
', conteudo) conteudo = html.unescape(conteudo) # escape special character '&' @@ -1263,7 +1293,8 @@ def make_pdf(base_url, main_template, header_template, main_css='', header_css=' # Template of header html = HTML(base_url=base_url, string=header_template) - header = html.render(stylesheets=[CSS(string='@page {size:A4; margin:1cm;}')]) + header = html.render( + stylesheets=[CSS(string='@page {size:A4; margin:1cm;}')]) header_page = header.pages[0] header_body = get_page_body(header_page._page_box.all_children()) @@ -1301,12 +1332,15 @@ def resumo_ata_pdf(request, pk): context.update({'object': sessao_plenaria}) context.update({'data': dt.today().strftime('%d/%m/%Y')}) context.update({'rodape': rodape}) - header_context = {"casa": casa, 'logotipo': casa.logotipo, 'MEDIA_URL': MEDIA_URL} + header_context = {"casa": casa, + 'logotipo': casa.logotipo, 'MEDIA_URL': MEDIA_URL} html_template = render_to_string('relatorios/relatorio_ata.html', context) - html_header = render_to_string('relatorios/header_ata.html', header_context) + html_header = render_to_string( + 'relatorios/header_ata.html', header_context) - pdf_file = make_pdf(base_url=base_url, main_template=html_template, header_template=html_header) + pdf_file = make_pdf( + base_url=base_url, main_template=html_template, header_template=html_header) response = HttpResponse(content_type='application/pdf;') response['Content-Disposition'] = 'inline; filename=relatorio.pdf' @@ -1324,12 +1358,15 @@ def cria_relatorio(request, context, html_string, header_info=""): context.update({'data': dt.today().strftime('%d/%m/%Y')}) context.update({'rodape': rodape}) - header_context = {"casa": casa, 'logotipo': casa.logotipo, 'MEDIA_URL': MEDIA_URL, 'info': header_info} + header_context = {"casa": casa, 'logotipo': casa.logotipo, + 'MEDIA_URL': MEDIA_URL, 'info': header_info} html_template = render_to_string(html_string, context) - html_header = render_to_string('relatorios/header_ata.html', header_context) + html_header = render_to_string( + 'relatorios/header_ata.html', header_context) - pdf_file = make_pdf(base_url=base_url, main_template=html_template, header_template=html_header) + pdf_file = make_pdf( + base_url=base_url, main_template=html_template, header_template=html_header) response = HttpResponse(content_type='application/pdf;') response['Content-Disposition'] = 'inline; filename=relatorio.pdf' @@ -1409,7 +1446,7 @@ def relatorio_pauta_sessao_weasy(obj, request, context): return cria_relatorio(request, context, 'relatorios/relatorio_pauta_sessao.html', info) -def relatorio_sessao_plenaria_pdf(request, pk): +def relatorio_sessao_plenaria_pdf(request, pk): base_url = request.build_absolute_uri() logger = logging.getLogger(__name__) username = request.user.username @@ -1463,24 +1500,24 @@ def relatorio_sessao_plenaria_pdf(request, pk): } context = { - "inf_basicas_dic": inf_basicas_dic, - "cont_mult_dic": cont_mult_dic, - "lst_mesa": lst_mesa, - "lst_expediente_materia_vot_nom": lst_expediente_materia_vot_nom, - "lst_presenca_sessao": lst_presenca_sessao, - "lst_ausencia_sessao": lst_ausencia_sessao, - "lst_expedientes": lst_expedientes, - "lst_expediente_materia": lst_expediente_materia, - "lst_oradores_expediente": lst_oradores_expediente, - "lst_presenca_ordem_dia": lst_presenca_ordem_dia, - "lst_votacao": lst_votacao, - "lst_oradores_ordemdia": lst_oradores_ordemdia, - "lst_votacao_vot_nom": lst_votacao_vot_nom, - "lst_oradores": lst_oradores, - "lst_ocorrencias": lst_ocorrencias, - "rodape": rodape, - "data": dt.today().strftime('%d/%m/%Y') - } + "inf_basicas_dic": inf_basicas_dic, + "cont_mult_dic": cont_mult_dic, + "lst_mesa": lst_mesa, + "lst_expediente_materia_vot_nom": lst_expediente_materia_vot_nom, + "lst_presenca_sessao": lst_presenca_sessao, + "lst_ausencia_sessao": lst_ausencia_sessao, + "lst_expedientes": lst_expedientes, + "lst_expediente_materia": lst_expediente_materia, + "lst_oradores_expediente": lst_oradores_expediente, + "lst_presenca_ordem_dia": lst_presenca_ordem_dia, + "lst_votacao": lst_votacao, + "lst_oradores_ordemdia": lst_oradores_ordemdia, + "lst_votacao_vot_nom": lst_votacao_vot_nom, + "lst_oradores": lst_oradores, + "lst_ocorrencias": lst_ocorrencias, + "rodape": rodape, + "data": dt.today().strftime('%d/%m/%Y') + } ordenacao = ResumoOrdenacao.objects.get_or_create()[0] try: @@ -1520,7 +1557,8 @@ def relatorio_sessao_plenaria_pdf(request, pk): 'decimo_quarto_ordenacao': 'ocorrencias_sessao.html' }) - html_template = render_to_string('relatorios/relatorio_sessao_plenaria.html', context) + html_template = render_to_string( + 'relatorios/relatorio_sessao_plenaria.html', context) info = "Resumo da {}ª Reunião {} \ da {}ª Sessão Legislativa da {} \ @@ -1535,7 +1573,8 @@ def relatorio_sessao_plenaria_pdf(request, pk): "logotipo": casa.logotipo, "info": info}) - pdf_file = make_pdf(base_url=base_url, main_template=html_template, header_template=html_header) + pdf_file = make_pdf( + base_url=base_url, main_template=html_template, header_template=html_header) response = HttpResponse(content_type='application/pdf;') response['Content-Disposition'] = 'inline; filename=relatorio.pdf' @@ -1548,32 +1587,35 @@ def relatorio_sessao_plenaria_pdf(request, pk): def gera_etiqueta_ml(materia_legislativa, base_url): confg = ConfigEtiquetaMateriaLegislativa.objects.first() - ml_info = unidecode.unidecode("{}/{}-{}".format(materia_legislativa.numero, - materia_legislativa.ano, - materia_legislativa.tipo.sigla)) + ml_info = unidecode.unidecode("{}/{}-{}".format(materia_legislativa.numero, + materia_legislativa.ano, + materia_legislativa.tipo.sigla)) base64_data = create_barcode(ml_info, 100, 500) barcode = 'data:image/png;base64,{0}'.format(base64_data) max_ementa_size = 240 ementa = materia_legislativa.ementa - ementa = ementa if len(ementa) < max_ementa_size else ementa[:max_ementa_size]+"..." + ementa = ementa if len( + ementa) < max_ementa_size else ementa[:max_ementa_size]+"..." context = { 'numero': materia_legislativa.numero, 'ano': materia_legislativa.ano, 'tipo': materia_legislativa.tipo, - 'data_apresentacao':materia_legislativa.data_apresentacao, + 'data_apresentacao': materia_legislativa.data_apresentacao, 'autores': materia_legislativa.autores.all(), - 'ementa':ementa, + 'ementa': ementa, 'largura': confg.largura, - 'altura':confg.largura, + 'altura': confg.largura, 'barcode': barcode } - main_template = render_to_string('relatorios/etiqueta_materia_legislativa.html', context) + main_template = render_to_string( + 'relatorios/etiqueta_materia_legislativa.html', context) html = HTML(base_url=base_url, string=main_template) - main_doc = html.render(stylesheets=[CSS(string="@page {{size: {}cm {}cm;}}".format(confg.largura,confg.altura))]) + main_doc = html.render(stylesheets=[CSS( + string="@page {{size: {}cm {}cm;}}".format(confg.largura, confg.altura))]) pdf_file = main_doc.write_pdf() return pdf_file @@ -1582,7 +1624,7 @@ def gera_etiqueta_ml(materia_legislativa, base_url): def etiqueta_materia_legislativa(request, pk): base_url = request.build_absolute_uri() materia_legislativa = MateriaLegislativa.objects.get(pk=pk) - + pdf_file = gera_etiqueta_ml(materia_legislativa, base_url) response = HttpResponse(content_type='application/pdf;') @@ -1590,4 +1632,4 @@ def etiqueta_materia_legislativa(request, pk): response['Content-Transfer-Encoding'] = 'binary' response.write(pdf_file) - return response \ No newline at end of file + return response diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 32c5a0b6c..5284bb591 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -86,7 +86,8 @@ def reordena_materias(request, pk, tipo, ordenacao): "ordemdia": "sapl.sessao:ordemdia_list" } - materias = TIPOS_MATERIAS[tipo].objects.filter(sessao_plenaria_id=pk).order_by(*TIPOS_ORDENACAO[ordenacao]) + materias = TIPOS_MATERIAS[tipo].objects.filter( + sessao_plenaria_id=pk).order_by(*TIPOS_ORDENACAO[ordenacao]) update_list = [] for numero, materia in enumerate(materias, 1): @@ -102,11 +103,12 @@ def verifica_presenca(request, model, spk, is_leitura=False): if not model.objects.filter(sessao_plenaria_id=spk).exists(): username = request.user.username if is_leitura: - text = 'Leitura não pode ser feita sem presenças' + text = 'Leitura não pode ser feita sem presenças' else: text = 'Votação não pode ser aberta sem presenças' - - logger.error("user={}. {} (sessao_plenaria_id={}).".format(username,text, spk)) + + logger.error("user={}. {} (sessao_plenaria_id={}).".format( + username, text, spk)) msg = _(text) messages.add_message(request, messages.ERROR, msg) return False @@ -182,7 +184,7 @@ def abrir_votacao(request, pk, spk): is_leitura = materia_votacao.tipo_votacao == 4 if (verifica_presenca(request, presenca_model, spk, is_leitura) and verifica_votacoes_abertas(request) and - verifica_sessao_iniciada(request, spk, is_leitura)): + verifica_sessao_iniciada(request, spk, is_leitura)): materia_votacao.votacao_aberta = True sessao = SessaoPlenaria.objects.get(id=spk) sessao.painel_aberto = True @@ -204,7 +206,8 @@ def customize_link_materia(context, pk, has_permission, is_expediente): for i, row in enumerate(context['rows']): materia = context['object_list'][i].materia obj = context['object_list'][i] - url_materia = reverse('sapl.materia:materialegislativa_detail', kwargs={'pk': materia.id}) + url_materia = reverse( + 'sapl.materia:materialegislativa_detail', kwargs={'pk': materia.id}) numeracao = materia.numeracao_set.first() if materia.numeracao_set.first() else "-" autoria = materia.autoria_set.filter(primeiro_autor=True) autor = ', '.join([str(a.autor) for a in autoria]) if autoria else "-" @@ -239,9 +242,12 @@ def customize_link_materia(context, pk, has_permission, is_expediente): # url em toda a string de title_materia context['rows'][i][1] = (title_materia, None) - exist_resultado = obj.registrovotacao_set.filter(materia=obj.materia).exists() - exist_retirada = obj.retiradapauta_set.filter(materia=obj.materia).exists() - exist_leitura = obj.registroleitura_set.filter(materia=obj.materia).exists() + exist_resultado = obj.registrovotacao_set.filter( + materia=obj.materia).exists() + exist_retirada = obj.retiradapauta_set.filter( + materia=obj.materia).exists() + exist_leitura = obj.registroleitura_set.filter( + materia=obj.materia).exists() if (obj.tipo_votacao != 4 and not exist_resultado and not exist_retirada) or\ (obj.tipo_votacao == 4 and not exist_leitura): @@ -559,7 +565,7 @@ def filtra_materias_copia_sessao_ajax(request): } for opcao in lista_materias_disponiveis_copia ] - return JsonResponse({ 'materias': lista_materias }) + return JsonResponse({'materias': lista_materias}) class TransferenciaMateriasSessaoAbstract(PermissionRequiredMixin, ListView): @@ -571,7 +577,8 @@ class TransferenciaMateriasSessaoAbstract(PermissionRequiredMixin, ListView): TransferenciaMateriasSessaoAbstract, self ).get_context_data(**kwargs) - sessao_plenaria_atual = SessaoPlenaria.objects.get(pk=self.kwargs['pk']) + sessao_plenaria_atual = SessaoPlenaria.objects.get( + pk=self.kwargs['pk']) context['subnav_template_name'] = 'sessao/subnav.yaml' context['root_pk'] = self.kwargs['pk'] @@ -627,7 +634,7 @@ class TransferenciaMateriasSessaoAbstract(PermissionRequiredMixin, ListView): messages.add_message(request, messages.ERROR, msg) msg_c = _( - 'Se o problema persistir, entre em contato com o suporte do ' \ + 'Se o problema persistir, entre em contato com o suporte do ' 'Interlegis.' ) messages.add_message(request, messages.WARNING, msg_c) @@ -925,7 +932,6 @@ class OradorCrud(MasterDetailCrud): form_class = OradorForm template_name = 'sessao/oradores_create.html' - def get_initial(self): return {'id_sessao': self.kwargs['pk']} @@ -937,7 +943,8 @@ class OradorCrud(MasterDetailCrud): if tipo_sessao.nome == "Solene": context.update( {'subnav_template_name': 'sessao/subnav-solene.yaml'}) - ultimo_orador = Orador.objects.filter(sessao_plenaria=kwargs['root_pk']).order_by("-numero_ordem").first() + ultimo_orador = Orador.objects.filter( + sessao_plenaria=kwargs['root_pk']).order_by("-numero_ordem").first() context["ultima_ordem"] = ultimo_orador.numero_ordem if ultimo_orador else 0 return context @@ -1009,7 +1016,8 @@ class OradorExpedienteCrud(OradorCrud): if tipo_sessao.nome == "Solene": context.update( {'subnav_template_name': 'sessao/subnav-solene.yaml'}) - ultimo_orador = OradorExpediente.objects.filter(sessao_plenaria=kwargs['root_pk']).order_by("-numero_ordem").first() + ultimo_orador = OradorExpediente.objects.filter( + sessao_plenaria=kwargs['root_pk']).order_by("-numero_ordem").first() context["ultima_ordem"] = ultimo_orador.numero_ordem if ultimo_orador else 0 return context @@ -1074,6 +1082,7 @@ class OradorExpedienteCrud(OradorCrud): class OradorOrdemDiaCrud(OradorCrud): model = OradorOrdemDia + class CreateView(MasterDetailCrud.CreateView): form_class = OradorOrdemDiaForm template_name = 'sessao/oradores_create.html' @@ -1087,7 +1096,8 @@ class OradorOrdemDiaCrud(OradorCrud): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - ultimo_orador = OradorOrdemDia.objects.filter(sessao_plenaria=kwargs['root_pk']).order_by("-numero_ordem").first() + ultimo_orador = OradorOrdemDia.objects.filter( + sessao_plenaria=kwargs['root_pk']).order_by("-numero_ordem").first() context["ultima_ordem"] = ultimo_orador.numero_ordem if ultimo_orador else 0 return context @@ -1936,20 +1946,26 @@ def get_materias_expediente(sessao_plenaria): rv = m.registrovotacao_set.filter(materia=m.materia).first() rp = m.retiradapauta_set.filter(materia=m.materia).first() + rl = m.registroleitura_set.filter(materia=m.materia).first() if rv: resultado = rv.tipo_resultado_votacao.nome resultado_observacao = rv.observacao elif rp: resultado = rp.tipo_de_retirada.descricao resultado_observacao = rp.observacao + elif rl: + resultado = _('Matéria lida') + resultado_observacao = rl.observacao else: - resultado = _('Matéria lida') if m.tipo_votacao == 4 else _('Matéria não votada') + resultado = _('Matéria não lida') if m.tipo_votacao == 4 else _( + 'Matéria não votada') resultado_observacao = '' voto_nominal = [] if m.tipo_votacao == 2: for voto in VotoParlamentar.objects.filter(expediente=m.id): - voto_nominal.append((voto.parlamentar.nome_completo, voto.voto)) + voto_nominal.append( + (voto.parlamentar.nome_completo, voto.voto)) voto = RegistroVotacao.objects.filter(expediente=m.id).last() if voto: @@ -1978,7 +1994,7 @@ def get_materias_expediente(sessao_plenaria): 'voto_nao': voto_nao, 'voto_abstencoes': voto_abstencoes, 'voto_nominal': voto_nominal, - 'observacao_materia': m.materia.observacao, + 'observacao_materia': m.materia.observacao, 'observacao': m.observacao }) @@ -2061,20 +2077,26 @@ def get_materias_ordem_do_dia(sessao_plenaria): # Verificar resultado rv = o.registrovotacao_set.filter(materia=o.materia).first() rp = o.retiradapauta_set.filter(materia=o.materia).first() + rl = o.registroleitura_set.filter(materia=o.materia).first() if rv: resultado = rv.tipo_resultado_votacao.nome resultado_observacao = rv.observacao elif rp: resultado = rp.tipo_de_retirada.descricao resultado_observacao = rp.observacao + elif rl: + resultado = _('Matéria lida') + resultado_observacao = rl.observacao else: - resultado = _('Matéria lida') if o.tipo_votacao == 4 else _('Matéria não votada') + resultado = _('Matéria não lida') if o.tipo_votacao == 4 else _( + 'Matéria não votada') resultado_observacao = '' voto_nominal = [] if o.tipo_votacao == 2: for voto in VotoParlamentar.objects.filter(ordem=o.id): - voto_nominal.append((voto.parlamentar.nome_completo, voto.voto)) + voto_nominal.append( + (voto.parlamentar.nome_completo, voto.voto)) voto = RegistroVotacao.objects.filter(ordem=o.id).last() if voto: @@ -2104,7 +2126,7 @@ def get_materias_ordem_do_dia(sessao_plenaria): 'voto_nao': voto_nao, 'voto_abstencoes': voto_abstencoes, 'voto_nominal': voto_nominal, - 'observacao': o.observacao + 'observacao': o.observacao }) return {'materias_ordem': materias_ordem} @@ -3435,7 +3457,8 @@ class VotacaoExpedienteView(SessaoPermissionMixin): self.logger.error("user=" + username + ". " + str(e)) return self.form_invalid(form) else: - expediente = ExpedienteMateria.objects.get(id=expediente_id) + expediente = ExpedienteMateria.objects.get( + id=expediente_id) resultado = TipoResultadoVotacao.objects.get( id=request.POST['resultado_votacao']) expediente.resultado = resultado.nome @@ -3586,7 +3609,8 @@ class PautaSessaoDetailView(DetailView): # ===================================================================== # Identificação Básica abertura = self.object.data_inicio.strftime('%d/%m/%Y') - encerramento = self.object.data_fim.strftime('%d/%m/%Y') if self.object.data_fim else "" + encerramento = self.object.data_fim.strftime( + '%d/%m/%Y') if self.object.data_fim else "" hora_inicio = self.object.hora_inicio hora_fim = self.object.hora_fim @@ -3612,7 +3636,8 @@ class PautaSessaoDetailView(DetailView): resultado = _('Matéria não votada') resultado_observacao = _(' ') - ultima_tramitacao = m.materia.tramitacao_set.order_by('-data_tramitacao', '-id').first() + ultima_tramitacao = m.materia.tramitacao_set.order_by( + '-data_tramitacao', '-id').first() numeracao = m.materia.numeracao_set.first() materias_expediente.append({ @@ -3662,7 +3687,8 @@ class PautaSessaoDetailView(DetailView): resultado = _('Matéria não votada') resultado_observacao = _(' ') - ultima_tramitacao = o.materia.tramitacao_set.order_by('-data_tramitacao', '-id').first() + ultima_tramitacao = o.materia.tramitacao_set.order_by( + '-data_tramitacao', '-id').first() numeracao = o.materia.numeracao_set.first() materias_ordem.append({ @@ -3792,7 +3818,7 @@ def verifica_materia_sessao_plenaria_ajax(request): sessao_plenaria=pk_sessao_plenaria, materia=id_materia_selecionada ).exists() - return JsonResponse({ 'is_materia_presente': is_materia_presente }) + return JsonResponse({'is_materia_presente': is_materia_presente}) class AdicionarVariasMateriasExpediente(PermissionRequiredForAppCrudMixin, @@ -4727,7 +4753,7 @@ class AbstractLeituraView(FormView): page = '' if 'page' in self.request.GET: page = '?page={}'.format(self.request.GET['page']) - + pk = self.kwargs['pk'] if self.expediente: url = reverse('sapl.sessao:expedientemateria_list', @@ -4746,8 +4772,8 @@ class AbstractLeituraView(FormView): 'pk': self.kwargs['pk'], 'iso': 1 if not self.expediente else 0, 'oid': self.kwargs['oid'], - }, - ) + page + }, + ) + page return url From 101f499d7ce186ee12716b3ba452b24f24a5a5fb Mon Sep 17 00:00:00 2001 From: eribeiro Date: Mon, 12 Jul 2021 18:12:06 -0300 Subject: [PATCH 05/22] Melhorias em Painel --- frontend/src/__apps/painel/main.js | 53 +++++++++++++ sapl/templates/painel/index.html | 123 +++++++++++++---------------- 2 files changed, 110 insertions(+), 66 deletions(-) diff --git a/frontend/src/__apps/painel/main.js b/frontend/src/__apps/painel/main.js index fdf4e5dbd..6c7d53b5d 100644 --- a/frontend/src/__apps/painel/main.js +++ b/frontend/src/__apps/painel/main.js @@ -1 +1,54 @@ import './scss/painel.scss' +import Vue from 'vue' +import axios from 'axios' + +axios.defaults.xsrfCookieName = 'csrftoken' +axios.defaults.xsrfHeaderName = 'X-CSRFToken' + +const v = new Vue({ // eslint-disable-line + delimiters: ['[[', ']]'], + el: '#app-painel', + data () { + return { + message: 'Hello VueJUS', // TODO: remove when porting to VueJS is done + polling: null, + painel_aberto: false, + sessao_plenaria: '', + sessao_plenaria_data: '', + sessao_plenaria_hora_inicio: '', + brasao: '', + sessao_solene: false, + sessao_solene_tema: '' + } + }, + methods: { + fetchData () { + // TODO: how to get no hardcoded URL? + $.get('/painel/704/dados', function (response) { + this.brasao = response.brasao + this.painel_aberto = response.status_painel + this.sessao_plenaria = response.sessao_plenaria + this.sessao_plenaria_data = 'Data Início: ' + response.sessao_plenaria_data + this.sessao_plenaria_hora_inicio = 'Hora Início: ' + response.sessao_plenaria_hora_inicio + this.sessao_solene = response.sessao_solene + this.sessao_solene_tema = response.sessao_solene_tema + }.bind(this)) + }, + pollData () { + this.fetchData() + + this.polling = setInterval(() => { + console.info('Fetching data from backend') + this.fetchData() + }, 5000) + } + }, + beforeDestroy () { + console.info('Destroying polling.') + clearInterval(this.polling) + }, + created () { + console.info('Start polling data...') + this.pollData() + } +}) diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html index 1cb2e2c83..ad30a11f3 100644 --- a/sapl/templates/painel/index.html +++ b/sapl/templates/painel/index.html @@ -35,28 +35,29 @@ +

-

+

[[ sessao_plenaria ]]

-
+
- + [[ sessao_plenaria_data ]]
- + [[ sessao_plenaria_hora_inicio ]]
-
+
- + Brasão
-

+

PAINEL ENCONTRA-SE FECHADO

@@ -64,19 +65,32 @@
-
-
-

Parlamentares

- +
+

Parlamentares

+
+ +
+
+ +
A listagem de parlamentares só aparecerá quando o painel estiver aberto.
+
+
-

Oradores

- +

Oradores

+
+ +
+
+ +
A listagem de oradores só aparecerá quando o painel estiver aberto.
+
+
@@ -88,30 +102,42 @@ Considerações Finais:
- -
-

Resultado

- -

+
+
+

Tema da Sessão Solene

+ [[ sessao_solene_tema ]] +
- -
-

Matéria em Votação

- -
- -
- -
- -

+
{% block webpack_loader_js %} @@ -249,25 +275,6 @@ url: "{% url 'sapl.painel:dados_painel' sessao_id %}", type: "GET", success: function(data) { - $("#sessao_plenaria").text(data["sessao_plenaria"]) - $("#sessao_plenaria_data").text("Data Início: " + data["sessao_plenaria_data"]) - $("#sessao_plenaria_hora_inicio").text("Hora Início: " + data["sessao_plenaria_hora_inicio"]) - $("#sessao_solene_tema").text(data["tema_solene"]) - if (data["status_painel"] == false) { - $("#message").text("PAINEL ENCONTRA-SE FECHADO"); - } - else { - $("#message").text(""); - } - - if (data["sessao_solene"]){ - $("#resultado_votacao_div").hide(); - $("#obs_materia_div").hide(); - $('#tema_solene_div').show(); - } - - if (data["brasao"] != null) - $("#logo-painel").attr("src", data["brasao"]); var presentes = $("#parlamentares"); var votacao = $("#votacao"); @@ -313,21 +320,6 @@ $('#aparecer_oradores').hide(); } } - else{ - presentes.append(''); - $('#parlamentares_list').append( - '
A listagem de parlamentares só aparecerá quando o painel estiver aberto.
') - presentes.append('
'); - - oradores.append(''); - $('#oradores_list').append( - '
A listagem de oradores só aparecerá quando o painel estiver aberto.
') - oradores.append('
'); - - votacao.append(''); - $("#votacao").append('
A votação só aparecerá quando o painel estiver aberto
'); - votacao.append('
'); - } if(data["status_painel"]){ if (data['materia_legislativa_texto']){ @@ -489,7 +481,6 @@ `${parlamentar.partido} ` + `${parlamentar.voto} `) } - From f2c84eda4cb37c6dd3ca245f9118444449be983d Mon Sep 17 00:00:00 2001 From: AlGouvea Date: Tue, 13 Jul 2021 10:19:40 -0300 Subject: [PATCH 06/22] Desabilitado polling na funcao principal --- sapl/templates/painel/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html index ad30a11f3..c02ba9bae 100644 --- a/sapl/templates/painel/index.html +++ b/sapl/templates/painel/index.html @@ -461,7 +461,7 @@ console.error(err); }, dataType: "json", - complete: setTimeout(function() {poll()}, 500), + complete: null, // setTimeout(function() {poll()}, 500), timeout: 20000 // TODO: decrease }) })(); From 6d5af39d50b311746e7422ecea4c69993af6834e Mon Sep 17 00:00:00 2001 From: AlGouvea Date: Tue, 13 Jul 2021 10:56:00 -0300 Subject: [PATCH 07/22] Alteracoes na listagem de parlamentar --- frontend/src/__apps/painel/main.js | 6 ++++-- sapl/templates/painel/index.html | 20 +++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/frontend/src/__apps/painel/main.js b/frontend/src/__apps/painel/main.js index 6c7d53b5d..9b647b582 100644 --- a/frontend/src/__apps/painel/main.js +++ b/frontend/src/__apps/painel/main.js @@ -18,7 +18,8 @@ const v = new Vue({ // eslint-disable-line sessao_plenaria_hora_inicio: '', brasao: '', sessao_solene: false, - sessao_solene_tema: '' + sessao_solene_tema: '', + presentes:[] } }, methods: { @@ -32,6 +33,7 @@ const v = new Vue({ // eslint-disable-line this.sessao_plenaria_hora_inicio = 'Hora Início: ' + response.sessao_plenaria_hora_inicio this.sessao_solene = response.sessao_solene this.sessao_solene_tema = response.sessao_solene_tema + this.presentes = response.presentes }.bind(this)) }, pollData () { @@ -40,7 +42,7 @@ const v = new Vue({ // eslint-disable-line this.polling = setInterval(() => { console.info('Fetching data from backend') this.fetchData() - }, 5000) + }, 500) } }, beforeDestroy () { diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html index c02ba9bae..f86e54ca0 100644 --- a/sapl/templates/painel/index.html +++ b/sapl/templates/painel/index.html @@ -71,7 +71,25 @@

Parlamentares

- + + + + + + + + + + + + + + +
From 173b6c7a1d74009a8bcedbc7b1f0dc9e220916e8 Mon Sep 17 00:00:00 2001 From: AlGouvea Date: Tue, 13 Jul 2021 11:50:14 -0300 Subject: [PATCH 08/22] Alteracoes na cor de palramentares --- frontend/src/__apps/painel/main.js | 26 ++++++++++++++++++++--- sapl/templates/painel/index.html | 33 +++++++++++++++--------------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/frontend/src/__apps/painel/main.js b/frontend/src/__apps/painel/main.js index 9b647b582..9f654eb23 100644 --- a/frontend/src/__apps/painel/main.js +++ b/frontend/src/__apps/painel/main.js @@ -12,17 +12,35 @@ const v = new Vue({ // eslint-disable-line return { message: 'Hello VueJUS', // TODO: remove when porting to VueJS is done polling: null, - painel_aberto: false, + painel_aberto: true, sessao_plenaria: '', sessao_plenaria_data: '', sessao_plenaria_hora_inicio: '', brasao: '', sessao_solene: false, sessao_solene_tema: '', - presentes:[] + presentes: [], + oradores: [] } }, methods: { + + atribuiColor (parlamentares) { + return parlamentares.map(parlamentar => { + var color = 'white' + if (parlamentar.voto === 'Voto Informado') { + color = 'yellow' + } else { + if (parlamentar.voto === 'Sim') { + color = 'green' + } else if (parlamentar.voto === 'Não') { + color = 'red' + } + } + parlamentar.color = color + console.info(parlamentares) + }) + }, fetchData () { // TODO: how to get no hardcoded URL? $.get('/painel/704/dados', function (response) { @@ -33,7 +51,9 @@ const v = new Vue({ // eslint-disable-line this.sessao_plenaria_hora_inicio = 'Hora Início: ' + response.sessao_plenaria_hora_inicio this.sessao_solene = response.sessao_solene this.sessao_solene_tema = response.sessao_solene_tema - this.presentes = response.presentes + this.presentes = this.atribuiColor(response.presentes) + console.info(this.presentes) + this.oradores = response.oradores }.bind(this)) }, pollData () { diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html index f86e54ca0..b6240ff9b 100644 --- a/sapl/templates/painel/index.html +++ b/sapl/templates/painel/index.html @@ -71,22 +71,15 @@

Parlamentares

- - + + - - - - - - - - - + +
ParlamentarPartidoAtivo?
- [[ parlamentar.nome]] - - [[ parlamentar.partido ]] - + [[ p.nome]] + + [[ p.partido ]] +
@@ -102,7 +95,15 @@

Oradores

- + + + + + + +
+ [[ o.numero]]º  [[o.nome]] +
From 5d4f775347a0b34ef7ab1ecfbef453c70563ce96 Mon Sep 17 00:00:00 2001 From: AlGouvea Date: Tue, 13 Jul 2021 11:50:14 -0300 Subject: [PATCH 09/22] Alteracoes na cor de palramentares --- frontend/src/__apps/painel/main.js | 25 ++++++++++++++++++++-- sapl/templates/painel/index.html | 33 +++++++++++++++--------------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/frontend/src/__apps/painel/main.js b/frontend/src/__apps/painel/main.js index 9b647b582..dbf140a64 100644 --- a/frontend/src/__apps/painel/main.js +++ b/frontend/src/__apps/painel/main.js @@ -12,17 +12,32 @@ const v = new Vue({ // eslint-disable-line return { message: 'Hello VueJUS', // TODO: remove when porting to VueJS is done polling: null, - painel_aberto: false, + painel_aberto: true, sessao_plenaria: '', sessao_plenaria_data: '', sessao_plenaria_hora_inicio: '', brasao: '', sessao_solene: false, sessao_solene_tema: '', - presentes:[] + presentes: [], + oradores: [] } }, methods: { + + atribuiColor (parlamentar) { + var color = 'white' + if (parlamentar.voto === 'Voto Informado') { + color = 'yellow' + } else { + if (parlamentar.voto === 'Sim') { + color = 'green' + } else if (parlamentar.voto === 'Não') { + color = 'red' + } + } + parlamentar.color = color + }, fetchData () { // TODO: how to get no hardcoded URL? $.get('/painel/704/dados', function (response) { @@ -33,7 +48,13 @@ const v = new Vue({ // eslint-disable-line this.sessao_plenaria_hora_inicio = 'Hora Início: ' + response.sessao_plenaria_hora_inicio this.sessao_solene = response.sessao_solene this.sessao_solene_tema = response.sessao_solene_tema + this.presentes = response.presentes + this.presentes.forEach(parlamentar => { + this.atribuiColor(parlamentar) + }) + + this.oradores = response.oradores }.bind(this)) }, pollData () { diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html index f86e54ca0..b6240ff9b 100644 --- a/sapl/templates/painel/index.html +++ b/sapl/templates/painel/index.html @@ -71,22 +71,15 @@

Parlamentares

- - + + - - - - - - - - - + +
ParlamentarPartidoAtivo?
- [[ parlamentar.nome]] - - [[ parlamentar.partido ]] - + [[ p.nome]] + + [[ p.partido ]] +
@@ -102,7 +95,15 @@

Oradores

- + + + + + + +
+ [[ o.numero]]º  [[o.nome]] +
From 30c6aa5923faa37158a73474b14f2ec2a43e8f30 Mon Sep 17 00:00:00 2001 From: eribeiro Date: Tue, 13 Jul 2021 13:22:56 -0300 Subject: [PATCH 10/22] WIP --- frontend/src/__apps/painel/main.js | 20 ++++++++++++-- sapl/templates/painel/index.html | 42 ++++++++++++------------------ 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/frontend/src/__apps/painel/main.js b/frontend/src/__apps/painel/main.js index dbf140a64..7e0555adc 100644 --- a/frontend/src/__apps/painel/main.js +++ b/frontend/src/__apps/painel/main.js @@ -20,7 +20,14 @@ const v = new Vue({ // eslint-disable-line sessao_solene: false, sessao_solene_tema: '', presentes: [], - oradores: [] + oradores: [], + has_votos: false, + materia_legislativa_texto: '', + numero_votos_sim: '', + numero_votos_nao: '', + numero_abstencoes: '', + num_presentes: '', + total_votos: '' } }, methods: { @@ -29,12 +36,14 @@ const v = new Vue({ // eslint-disable-line var color = 'white' if (parlamentar.voto === 'Voto Informado') { color = 'yellow' + this.has_votos = false } else { if (parlamentar.voto === 'Sim') { color = 'green' } else if (parlamentar.voto === 'Não') { color = 'red' } + this.has_votos = true } parlamentar.color = color }, @@ -55,6 +64,13 @@ const v = new Vue({ // eslint-disable-line }) this.oradores = response.oradores + + this.materia_legislativa_texto = response.materia_legislativa_texto + this.numero_votos_sim = response.numero_votos_sim + this.numero_votos_sim = response.numero_votos_sim + this.numero_abstencoes = response.numero_abstencoes + this.num_presentes = response.num_presentes + this.total_votos = response.total_votos }.bind(this)) }, pollData () { @@ -63,7 +79,7 @@ const v = new Vue({ // eslint-disable-line this.polling = setInterval(() => { console.info('Fetching data from backend') this.fetchData() - }, 500) + }, 1000) } }, beforeDestroy () { diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html index b91aa130d..80c8d1b42 100644 --- a/sapl/templates/painel/index.html +++ b/sapl/templates/painel/index.html @@ -21,7 +21,6 @@ {% render_bundle 'painel' 'css' %} {% endblock webpack_loader_css %} - - - -
- - -
- Logo - -
-
-

- {% if nome %}{{ nome }}{% else %}{% trans 'Câmara/Assembléia não configurada'%}{% endif %} -

-
- -
-

[[ sessao_plenaria ]]

-
-
-
- [[ sessao_plenaria_data ]] -
-
- [[ sessao_plenaria_hora_inicio ]] + + + + + + {% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %} + + {% block webpack_loader_css %} + {% render_chunk_vendors 'css' %} + {% render_bundle 'global' 'css' %} + {% render_bundle 'painel' 'css' %} + {% endblock webpack_loader_css %} + + + + +
+ + +
+ Logo +
+ +
+

[[ sessao_plenaria ]]

+
+ +
+
+ [[ sessao_plenaria_data ]] +
+
+ [[ sessao_plenaria_hora_inicio ]] +
-
- -
-

PAINEL ENCONTRA-SE FECHADO

-
-
-
-
-
+
+

PAINEL ENCONTRA-SE FECHADO

+
+ +
+
+
+
-
-
-
-
-
-

Parlamentares

-
- +
+
+
+
+
+

Parlamentares

+
+
- - + - - - + -
- [[ p.nome]] +
+ [[p.nome]] - [[ p.partido ]] + + [[p.partido]] - [[p.voto]] + + [[p.voto]]
-
-
+ +
+
-
A listagem de parlamentares só aparecerá quando o painel estiver aberto.
+
A listagem de parlamentares só aparecerá quando o painel estiver aberto.
+
-
-
-
-
-

Oradores

-
- - - - - - -
- [[ o.numero]]º  [[o.nome]] -
-
-
+
+
+
+

Oradores

+
+ + + + + + +
+ [[ o.numero]]º  [[o.nome]] +
+
+
A listagem de oradores só aparecerá quando o painel estiver aberto.
+
-
-
-
-
-
-

Cronômetros

-
- Discurso:
- Aparte:
- Questão de Ordem:
- Considerações Finais: -
-
-
-
-

Tema da Sessão Solene

- [[ sessao_solene_tema ]] +
+
+
+
+

Cronômetros

+
+ Discurso:
+ Aparte:
+ Questão de Ordem:
+ Considerações Finais: +
-
-
-
-
-

Resultado

-
- -
  • Sim: [[ numero_votos_sim ]]
  • -
  • Não: [[ numero_votos_nao ]]
  • -
  • Abstenções: [[ numero_abstencoes ]]
  • -
  • Presentes: [[ num_presentes ]]
  • -
  • Total votos: [[ total_votos ]]
  • -
    -

    [[ tipo_resultado ]]

    -
    -
    -
    Não há votação, pois não há nenhuma matéria aberta ou já votada.
    -
    -
    -
    -
    -

    [[ mat_em_votacao ]]

    -
    - [[ materia_legislativa_texto ]] -
    - [[ materia_legislativa_ementa ]] -
    - [[ observacao_materia ]] -
    -
    - Não há nenhuma matéria votada ou para votação. -
    -
    -
    -
    - A Matéria em votação só aparecerá quando o painel estiver aberto -
    +
    +
    +

    Tema da Sessão Solene

    + [[ sessao_solene_tema ]] +
    -
    -
    -

    Resultado

    - -

    A votação só aparecerá quando o painel estiver aberto

    -
    -
    +
    +
    +
    +

    Resultado

    +
    + +
  • Sim: [[ numero_votos_sim ]]
  • +
  • Não: [[ numero_votos_nao ]]
  • +
  • Abstenções: [[ numero_abstencoes ]]
  • +
  • Presentes: [[ num_presentes ]]
  • +
  • Total votos: [[ total_votos ]]
  • +
    +

    [[ tipo_resultado ]]

    +
    +
    +
    Não há votação, pois não há nenhuma matéria aberta ou já votada.
    +
    +
    +
    +
    +

    [[ mat_em_votacao ]]

    +
    + [[ materia_legislativa_texto ]] +
    + [[ materia_legislativa_ementa ]] +
    + [[ observacao_materia ]] +
    +
    + Não há nenhuma matéria votada ou para votação. +
    +
    +
    +
    + A Matéria em votação só aparecerá quando o painel estiver aberto +
    -
    -
    - - - {% block webpack_loader_js %} - {% render_chunk_vendors 'js' %} - {% render_bundle 'global' 'js' %} - {% render_bundle 'painel' 'js' %} - {% endblock webpack_loader_js %} +
    +
    +

    Resultado

    + +

    A votação só aparecerá quando o painel estiver aberto

    +
    +
    +
    +
    +
    +
    + - {% block webpack_loader_chunks_js %} - {% endblock webpack_loader_chunks_js %} + {% block webpack_loader_js %} + {% render_chunk_vendors 'js' %} + {% render_bundle 'global' 'js' %} + {% render_bundle 'painel' 'js' %} + {% endblock webpack_loader_js %} - - - + + \ No newline at end of file From 541506439d416f4fd2fd34349d53fde3246e05eb Mon Sep 17 00:00:00 2001 From: AlGouvea Date: Thu, 26 Aug 2021 11:45:26 -0300 Subject: [PATCH 21/22] Iniciado os cronometros em VueJS --- frontend/src/__apps/painel/main.js | 45 ++++++++++++++++++++++++++++++ sapl/templates/painel/index.html | 6 ++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/frontend/src/__apps/painel/main.js b/frontend/src/__apps/painel/main.js index b06d64b5e..ea6ed1dbf 100644 --- a/frontend/src/__apps/painel/main.js +++ b/frontend/src/__apps/painel/main.js @@ -5,6 +5,11 @@ import axios from 'axios' axios.defaults.xsrfCookieName = 'csrftoken' axios.defaults.xsrfHeaderName = 'X-CSRFToken' +// Variaveis dos cronometros +var timeBegan = null +var running = false +var timeExpected = null + const v = new Vue({ // eslint-disable-line delimiters: ['[[', ']]'], el: '#app-painel', @@ -17,6 +22,7 @@ const v = new Vue({ // eslint-disable-line sessao_plenaria_data: '', sessao_plenaria_hora_inicio: '', brasao: '', + cronometro: '00:10:00', sessao_solene: false, sessao_solene_tema: '', presentes: [], @@ -129,6 +135,45 @@ const v = new Vue({ // eslint-disable-line this.mat_em_votacao = this.msgMateria() }.bind(this)) }, + stop: function stop () { + running = false + timeBegan = null + timeExpected = null + + clearInterval(this.clockRunning) + this.cronometro = '00:10:00' + }, + clockRunning () { + if (running) { + var currentTime = new Date() + var timeRemaining = new Date(timeExpected - currentTime) + + if (timeRemaining > 0) { + this.cronometro = '00:' + timeRemaining.getMinutes().toLocaleString('en-US', { minimumIntegerDigits: 2, useGrouping: false }) + + ':' + timeRemaining.getSeconds().toLocaleString('en-US', { minimumIntegerDigits: 2, useGrouping: false }) + } else { + this.cronometro.style.color = 'red' + this.stop() + } + } else { + this.stop() + } + }, + start: function startStopWatch (time) { + time *= 60 + if (running) return + + if (timeBegan === null) { + timeBegan = new Date() + timeExpected = timeBegan + timeExpected.setSeconds(timeExpected.getSeconds() + time) + } + + this.started = setInterval(() => { + this.clockRunning() + }, 100) + running = true + }, pollData () { this.fetchData() diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html index c5bc219ea..fc4638709 100644 --- a/sapl/templates/painel/index.html +++ b/sapl/templates/painel/index.html @@ -59,7 +59,7 @@
    -
    +
    @@ -120,7 +120,7 @@

    Cronômetros

    - Discurso:
    + Discurso: [[ cronometro ]]
    Aparte:
    Questão de Ordem:
    Considerações Finais: @@ -135,7 +135,7 @@
    -

    Resultado

    +

    Resultado

  • Sim: [[ numero_votos_sim ]]
  • From bb1de80a88b1973c4898c148c74b971fb3962295 Mon Sep 17 00:00:00 2001 From: AlGouvea Date: Tue, 31 Aug 2021 13:02:16 -0300 Subject: [PATCH 22/22] Cronometros do painel convertidos para VueJS --- frontend/src/__apps/painel/main.js | 141 ++++++++++++++++----- sapl/painel/views.py | 20 ++- sapl/templates/painel/index.html | 197 +++-------------------------- 3 files changed, 144 insertions(+), 214 deletions(-) diff --git a/frontend/src/__apps/painel/main.js b/frontend/src/__apps/painel/main.js index ea6ed1dbf..2328573fa 100644 --- a/frontend/src/__apps/painel/main.js +++ b/frontend/src/__apps/painel/main.js @@ -6,9 +6,11 @@ axios.defaults.xsrfCookieName = 'csrftoken' axios.defaults.xsrfHeaderName = 'X-CSRFToken' // Variaveis dos cronometros -var timeBegan = null -var running = false -var timeExpected = null +var crono = 0 +var time = null +var timeEnd = null +var audioAlertFinish = document.getElementById('audio') +var cronometroStart = [] const v = new Vue({ // eslint-disable-line delimiters: ['[[', ']]'], @@ -22,7 +24,10 @@ const v = new Vue({ // eslint-disable-line sessao_plenaria_data: '', sessao_plenaria_hora_inicio: '', brasao: '', - cronometro: '00:10:00', + cronometro_discurso: '', + cronometro_aparte: '', + cronometro_ordem: '', + cronometro_consideracoes: '', sessao_solene: false, sessao_solene_tema: '', presentes: [], @@ -39,7 +44,8 @@ const v = new Vue({ // eslint-disable-line mat_em_votacao: '', resultado_votacao_css: '', tipo_resultado: '', - tipo_votacao: '' + tipo_votacao: '', + running: 0 } }, methods: { @@ -97,7 +103,6 @@ const v = new Vue({ // eslint-disable-line converterUrl (url) { url = url.slice(-(url.length - url.lastIndexOf('/'))) url = '/painel' + url + '/dados' - console.log(url) return url }, fetchData () { @@ -133,46 +138,114 @@ const v = new Vue({ // eslint-disable-line this.tipo_resultado = response.tipo_resultado this.tipo_votacao = response.tipo_votacao this.mat_em_votacao = this.msgMateria() + + // Cronometros + cronometroStart[0] = response.cronometro_discurso + cronometroStart[1] = response.cronometro_aparte + cronometroStart[2] = response.cronometro_ordem + cronometroStart[3] = response.cronometro_consideracoes + + if (time === null) { + // Pegar data atual + this.cronometro_discurso = new Date() + this.cronometro_aparte = this.cronometro_discurso + this.cronometro_ordem = this.cronometro_discurso + this.cronometro_consideracoes = this.cronometro_discurso + + // Setar cada Cronometro + var temp = new Date() + temp.setSeconds(this.cronometro_discurso.getSeconds() + cronometroStart[0]) + var res = new Date(temp - this.cronometro_discurso) + this.cronometro_discurso = this.formatTime(res) + + temp = new Date() + temp.setSeconds(this.cronometro_aparte.getSeconds() + cronometroStart[1]) + res = new Date(temp - this.cronometro_aparte) + this.cronometro_aparte = this.formatTime(res) + + temp = new Date() + temp.setSeconds(this.cronometro_ordem.getSeconds() + cronometroStart[2]) + res = new Date(temp - this.cronometro_ordem) + this.cronometro_ordem = this.formatTime(res) + + temp = new Date() + temp.setSeconds(this.cronometro_consideracoes.getSeconds() + cronometroStart[3]) + res = new Date(temp - this.cronometro_consideracoes) + this.cronometro_consideracoes = this.formatTime(res) + } }.bind(this)) }, - stop: function stop () { - running = false - timeBegan = null - timeExpected = null + formatTime (time) { + var tempo = '00:' + time.getMinutes().toLocaleString('en-US', { + minimumIntegerDigits: 2, + useGrouping: false + }) + ':' + time.getSeconds().toLocaleString('en-US', { + minimumIntegerDigits: 2, + useGrouping: false + }) + return tempo + }, + stop: function stop (crono) { + if (crono === 5) { + audioAlertFinish.play() + } - clearInterval(this.clockRunning) - this.cronometro = '00:10:00' + this.running = 0 + clearInterval(this.started) + this.stopped = setInterval(() => { + this.timeStopped() + }, 100) + }, + timeStopped () { + timeEnd.setMilliseconds(timeEnd.getMilliseconds() + 100) }, - clockRunning () { - if (running) { - var currentTime = new Date() - var timeRemaining = new Date(timeExpected - currentTime) - - if (timeRemaining > 0) { - this.cronometro = '00:' + timeRemaining.getMinutes().toLocaleString('en-US', { minimumIntegerDigits: 2, useGrouping: false }) + - ':' + timeRemaining.getSeconds().toLocaleString('en-US', { minimumIntegerDigits: 2, useGrouping: false }) + reset: function reset () { + this.running = 0 + time = null + clearInterval(this.started) + clearInterval(this.stopped) + }, + clockRunning (crono) { + var now = new Date() + time = new Date(timeEnd - now) + + // Definir propriamento o tempo + time.setHours(timeEnd.getHours() - now.getHours()) + + if (timeEnd > now) { + if (crono === 1) { + this.cronometro_discurso = this.formatTime(time) + } else if (crono === 2) { + this.cronometro_aparte = this.formatTime(time) + } else if (crono === 3) { + this.cronometro_ordem = this.formatTime(time) } else { - this.cronometro.style.color = 'red' - this.stop() + this.cronometro_consideracoes = this.formatTime(time) } } else { - this.stop() + audioAlertFinish.play() + this.alert = setTimeout(() => { + this.reset() + }, 5000) } }, - start: function startStopWatch (time) { - time *= 60 - if (running) return - - if (timeBegan === null) { - timeBegan = new Date() - timeExpected = timeBegan - timeExpected.setSeconds(timeExpected.getSeconds() + time) + start: function startStopWatch (temp_crono) { + if (this.running !== 0) return + + crono = temp_crono + if (time === null) { + time = cronometroStart[crono - 1] + console.log(time) + timeEnd = new Date() + timeEnd.setSeconds(timeEnd.getSeconds() + time) + } else { + clearInterval(this.stopped) } + this.running = crono this.started = setInterval(() => { - this.clockRunning() + this.clockRunning(crono) }, 100) - running = true }, pollData () { this.fetchData() @@ -180,7 +253,7 @@ const v = new Vue({ // eslint-disable-line this.polling = setInterval(() => { console.info('Fetching data from backend') this.fetchData() - }, 5000) + }, 100) } }, beforeDestroy () { diff --git a/sapl/painel/views.py b/sapl/painel/views.py index 75f5a1b97..6718683cd 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -352,6 +352,18 @@ def get_cronometro_status(request, name): cronometro = '' return cronometro +def get_cronometro_value(request, name): + if name == 'discurso': + result = ConfiguracoesAplicacao.objects.first().cronometro_discurso + if name == 'aparte': + result = ConfiguracoesAplicacao.objects.first().cronometro_aparte + if name == 'ordem': + result = ConfiguracoesAplicacao.objects.first().cronometro_ordem + if name == 'consideracoes': + result = ConfiguracoesAplicacao.objects.first().cronometro_consideracoes + + return result.total_seconds() + def get_materia_aberta(pk): return OrdemDia.objects.filter( @@ -558,10 +570,10 @@ def get_dados_painel(request, pk): 'sessao_solene': sessao.tipo.nome == "Solene", 'sessao_finalizada': sessao.finalizada, 'tema_solene': sessao.tema_solene, - 'cronometro_aparte': get_cronometro_status(request, 'aparte'), - 'cronometro_discurso': get_cronometro_status(request, 'discurso'), - 'cronometro_ordem': get_cronometro_status(request, 'ordem'), - 'cronometro_consideracoes': get_cronometro_status(request, 'consideracoes'), + 'cronometro_aparte': get_cronometro_value(request, 'aparte'), + 'cronometro_discurso': get_cronometro_value(request, 'discurso'), + 'cronometro_ordem': get_cronometro_value(request, 'ordem'), + 'cronometro_consideracoes': get_cronometro_value(request, 'consideracoes'), 'status_painel': sessao.painel_aberto, 'brasao': brasao } diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html index fc4638709..35f8f3430 100644 --- a/sapl/templates/painel/index.html +++ b/sapl/templates/painel/index.html @@ -1,5 +1,6 @@ {% load i18n %} {% load common_tags %} +{% load staticfiles %} {% load render_bundle from webpack_loader %} {% load webpack_static from webpack_loader %} @@ -120,10 +121,25 @@

    Cronômetros

    - Discurso: [[ cronometro ]]
    - Aparte:
    - Questão de Ordem:
    - Considerações Finais: + Discurso:[[ cronometro_discurso ]] + + +
    + + Aparte:[[ cronometro_aparte ]] + + +
    + + Questão de Ordem: [[ cronometro_ordem ]] + + +
    + + Considerações Finais: [[ cronometro_consideracoes ]] + + +
    @@ -132,7 +148,7 @@ [[ sessao_solene_tema ]]
    -
    +

    Resultado

    @@ -210,177 +226,6 @@ $("#date").append(moment().format("DD/MM/YY")); - var offset = parseFloat({{ utc_offset }}); - - //TODO: replace by a fancy jQuery clock - function checkTime(i) { - if (i<10) {i = "0" + i}; // add zero in front of numbers < 10 - return i; - } - - function startTime() { - var today = moment.utc().utcOffset(offset).format("HH:mm:ss"); - $("#relogio").text(today) - var t = setTimeout(function(){ - startTime() - }, 500); - } - startTime(); - - var audioAlertFinish = document.getElementById("audio"); - - $('#cronometro_discurso').runner({ - autostart: false, - countdown: true, - startAt: {{ 'discurso'|cronometro_to_seconds }} * 1000, - stopAt: 0, - milliseconds: false, - format: function(value) { - let h = Math.floor((value/1000) / 3600); - h = checkTime(h); - let m = Math.floor((value/1000) % 3600 / 60); - m = checkTime(m); - let s = Math.floor((value/1000) % 3600 % 60); - s = checkTime(s); - return h.toString() + ":" + m.toString() + ":" + s.toString(); - } - }).on('runnerFinish', function(eventObject, info){ - audioAlertFinish.play(); - }); - - $('#cronometro_aparte').runner({ - autostart: false, - countdown: true, - startAt: {{ 'aparte'|cronometro_to_seconds }} * 1000, - stopAt: 0, - milliseconds: false, - format: function(value) { - let h = Math.floor((value/1000) / 3600); - h = checkTime(h); - let m = Math.floor((value/1000) % 3600 / 60); - m = checkTime(m); - let s = Math.floor((value/1000) % 3600 % 60); - s = checkTime(s); - return h.toString() + ":" + m.toString() + ":" + s.toString(); - } - }).on('runnerFinish', function(eventObject, info){ - audioAlertFinish.play(); - }); - - $('#cronometro_ordem').runner({ - autostart: false, - countdown: true, - startAt: {{ 'ordem'|cronometro_to_seconds }} * 1000, - stopAt: 0, - milliseconds: false, - format: function(value) { - let h = Math.floor((value/1000) / 3600); - h = checkTime(h); - let m = Math.floor((value/1000) % 3600 / 60); - m = checkTime(m); - let s = Math.floor((value/1000) % 3600 % 60); - s = checkTime(s); - return h.toString() + ":" + m.toString() + ":" + s.toString(); - } - }).on('runnerFinish', function(eventObject, info){ - audioAlertFinish.play(); - }); - - $('#cronometro_consideracoes').runner({ - autostart: false, - countdown: true, - startAt: {{ 'consideracoes'|cronometro_to_seconds }} * 1000, - stopAt: 0, - milliseconds: false, - format: function(value) { - let h = Math.floor((value/1000) / 3600); - h = checkTime(h); - let m = Math.floor((value/1000) % 3600 / 60); - m = checkTime(m); - let s = Math.floor((value/1000) % 3600 % 60); - s = checkTime(s); - return h.toString() + ":" + m.toString() + ":" + s.toString(); - } - }).on('runnerFinish', function(eventObject, info){ - audioAlertFinish.play(); - }); - - var discurso_previous; - var ordem_previous; - var aparte_previous; - var consideracoes_previous; - - var counter = 1; - (function poll() { - $.ajax({ - url: "{% url 'sapl.painel:dados_painel' sessao_id %}", - type: "GET", - success: function(data) { - - var discurso_current = data["cronometro_discurso"]; - if (!discurso_previous){ - discurso_previous = '' - } - - if (discurso_current != discurso_previous) { - $('#cronometro_discurso').runner(discurso_current); - discurso_previous = discurso_current; - } - - var aparte_current = data["cronometro_aparte"]; - if (!aparte_previous){ - aparte_previous = '' - } - - if (aparte_current != aparte_previous) { - $('#cronometro_aparte').runner(aparte_current); - aparte_previous = aparte_current; - } - - var ordem_current = data["cronometro_ordem"]; - if (!ordem_previous){ - ordem_previous = '' - } - - if (ordem_current != ordem_previous) { - $('#cronometro_ordem').runner(ordem_current); - ordem_previous = ordem_current; - } - - var consideracoes_current = data["cronometro_consideracoes"]; - if (!consideracoes_previous){ - consideracoes_previous = '' - } - - if (consideracoes_current != consideracoes_previous) { - $('#cronometro_consideracoes').runner(consideracoes_current); - consideracoes_previous = consideracoes_current; - } - - if($('#cronometro_discurso').runner('info').formattedTime == "00:00:30") { - audioAlertFinish.play(); - } - - if($('#cronometro_aparte').runner('info').formattedTime == "00:00:30") { - audioAlertFinish.play(); - } - - if($('#cronometro_ordem').runner('info').formattedTime == "00:00:30") { - audioAlertFinish.play(); - } - - if($('#cronometro_consideracoes').runner('info').formattedTime == "00:00:30") { - audioAlertFinish.play(); - } - }, - error: function(err) { - console.error(err); - }, - dataType: "json", - complete: null, // setTimeout(function() {poll()}, 500), - timeout: 20000 // TODO: decrease }) - })(); - }); \ No newline at end of file