diff --git a/frontend/src/__apps/painel/main.js b/frontend/src/__apps/painel/main.js index fdf4e5dbd..2328573fa 100644 --- a/frontend/src/__apps/painel/main.js +++ b/frontend/src/__apps/painel/main.js @@ -1 +1,267 @@ import './scss/painel.scss' +import Vue from 'vue' +import axios from 'axios' + +axios.defaults.xsrfCookieName = 'csrftoken' +axios.defaults.xsrfHeaderName = 'X-CSRFToken' + +// Variaveis dos cronometros +var crono = 0 +var time = null +var timeEnd = null +var audioAlertFinish = document.getElementById('audio') +var cronometroStart = [] + +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: true, + sessao_plenaria: '', + sessao_plenaria_data: '', + sessao_plenaria_hora_inicio: '', + brasao: '', + cronometro_discurso: '', + cronometro_aparte: '', + cronometro_ordem: '', + cronometro_consideracoes: '', + sessao_solene: false, + sessao_solene_tema: '', + presentes: [], + oradores: [], + numero_votos_sim: 0, + numero_votos_nao: 0, + numero_abstencoes: 0, + num_presentes: 0, + total_votos: 0, + sessao_finalizada: true, + materia_legislativa_texto: '', + materia_legislativa_ementa: '', + observacao_materia: '', + mat_em_votacao: '', + resultado_votacao_css: '', + tipo_resultado: '', + tipo_votacao: '', + running: 0 + } + }, + methods: { + msgMateria () { + if (this.tipo_resultado && this.painel_aberto) { + if (this.tipo_votacao !== 'Leitura' && !this.sessao_finalizada && !this.sessao_solene) { + this.resultado_votacao_css = 'color: #45919D' + this.mat_em_votacao = 'Matéria em Votação' + } else { + this.resultado_votacao_css = 'color: #45919D' + this.mat_em_votacao = 'Matéria em Leitura' + } + + this.resultado_votacao = this.tipo_resultado + + var resultado_votacao_upper = this.resultado_votacao.toUpperCase() + + if (resultado_votacao_upper.search('APROV') !== -1) { + this.resultado_votacao_css = 'color: #7CFC00' + this.mat_em_votacao = 'Matéria Votada' + } else if (resultado_votacao_upper.search('REJEIT') !== -1) { + this.resultado_votacao_css = 'color: red' + this.mat_em_votacao = 'Matéria Votada' + } else if (resultado_votacao_upper.search('LIDA') !== -1) { + this.mat_em_votacao = 'Matéria Lida' + } + } else { + this.resultado_votacao = '' + if (this.tipo_votacao !== 'Leitura') { + this.mat_em_votacao = 'Matéria em Votação' + } else { + this.mat_em_votacao = 'Matéria em Leitura' + } + } + }, + atribuiColor (parlamentar) { + var color = 'white' + if (parlamentar.voto === 'Voto Informado' || parlamentar.voto === '') { + color = 'yellow' + } else { + if (parlamentar.voto === 'Sim') { + color = 'green' + } else if (parlamentar.voto === 'Não') { + color = 'red' + } + } + parlamentar.color = color + }, + capObservacao (texto) { + if (texto && texto.length > 151) { + return texto.substr(0, 145).concat('(...)') + } + return texto + }, + converterUrl (url) { + url = url.slice(-(url.length - url.lastIndexOf('/'))) + url = '/painel' + url + '/dados' + return url + }, + fetchData () { + // TODO: how to get no hardcoded URL? + $.get(this.converterUrl(window.location.pathname), function (response) { + this.brasao = response.brasao + this.painel_aberto = response.status_painel + this.sessao_finalizada = response.sessao_finalizada + 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 + + this.presentes = response.presentes + this.presentes.forEach(parlamentar => { + this.atribuiColor(parlamentar) + }) + + this.oradores = response.oradores + + this.materia_legislativa_texto = response.materia_legislativa_texto + this.numero_votos_sim = response.numero_votos_sim + this.numero_votos_nao = response.numero_votos_nao + this.numero_abstencoes = response.numero_abstencoes + this.num_presentes = response.num_presentes + this.total_votos = response.total_votos + + this.materia_legislativa_texto = response.materia_legislativa_texto + this.materia_legislativa_ementa = response.materia_legislativa_ementa + this.observacao_materia = this.capObservacao(response.observacao_materia) + + 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)) + }, + 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() + } + + this.running = 0 + clearInterval(this.started) + this.stopped = setInterval(() => { + this.timeStopped() + }, 100) + }, + timeStopped () { + timeEnd.setMilliseconds(timeEnd.getMilliseconds() + 100) + }, + 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_consideracoes = this.formatTime(time) + } + } else { + audioAlertFinish.play() + this.alert = setTimeout(() => { + this.reset() + }, 5000) + } + }, + 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(crono) + }, 100) + }, + pollData () { + this.fetchData() + + this.polling = setInterval(() => { + console.info('Fetching data from backend') + this.fetchData() + }, 100) + } + }, + beforeDestroy () { + console.info('Destroying polling.') + clearInterval(this.polling) + }, + created () { + console.info('Start polling data...') + this.pollData() + } +}) 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) 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 = '
', '
', 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
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 %}
diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html
index 1cb2e2c83..35f8f3430 100644
--- a/sapl/templates/painel/index.html
+++ b/sapl/templates/painel/index.html
@@ -1,495 +1,231 @@
{% load i18n %}
{% load common_tags %}
+{% load staticfiles %}
{% load render_bundle from webpack_loader %}
{% load webpack_static from webpack_loader %}
-
-
-
-
+ [[p.nome]] + | ++ [[p.partido]] + | ++ [[p.voto]] + | +
+ [[ o.numero]]º  [[o.nome]] + | +