Browse Source

Merge branch 'vuejs_painel' of https://github.com/interlegis/sapl into websocket_sapl

Websocket_painel
AlGouvea 3 years ago
parent
commit
156381156c
  1. 266
      frontend/src/__apps/painel/main.js
  2. 286
      sapl/api/core/__init__.py
  3. 3
      sapl/api/urls.py
  4. 668
      sapl/api/views.py
  5. 406
      sapl/api/views_customize.py
  6. 20
      sapl/base/templatetags/common_tags.py
  7. 20
      sapl/painel/views.py
  8. 110
      sapl/relatorios/views.py
  9. 66
      sapl/sessao/views.py
  10. 3
      sapl/templates/materia/materialegislativa_filter.html
  11. 536
      sapl/templates/painel/index.html
  12. 3
      sapl/utils.py

266
frontend/src/__apps/painel/main.js

@ -1 +1,267 @@
import './scss/painel.scss' 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()
}
})

286
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

3
sapl/api/urls.py

@ -6,7 +6,8 @@ from rest_framework.routers import DefaultRouter
from sapl.api.deprecated import MateriaLegislativaViewSet, SessaoPlenariaViewSet,\ from sapl.api.deprecated import MateriaLegislativaViewSet, SessaoPlenariaViewSet,\
AutoresProvaveisListView, AutoresPossiveisListView, AutorListView,\ AutoresProvaveisListView, AutoresPossiveisListView, AutorListView,\
ModelChoiceView 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 from .apps import AppConfig

668
sapl/api/views.py

@ -1,39 +1,14 @@
import logging import logging
from django import apps
from django.conf import settings 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.db.models.signals import post_save
from django.dispatch import receiver 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.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.authtoken.models import Token
from rest_framework.decorators import action, api_view, permission_classes from rest_framework.decorators import api_view, permission_classes
from rest_framework.fields import SerializerMethodField
from rest_framework.permissions import IsAuthenticated, IsAdminUser from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView 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) @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}) 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): class AppVersionView(APIView):
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)

406
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)

20
sapl/base/templatetags/common_tags.py

@ -51,6 +51,7 @@ def model_verbose_name_plural(class_name):
model = get_class(class_name) model = get_class(class_name)
return model._meta.verbose_name_plural return model._meta.verbose_name_plural
@register.filter @register.filter
def meta_model_value(instance, attr): def meta_model_value(instance, attr):
try: try:
@ -103,6 +104,22 @@ def paginacao_limite_superior(pagina):
return int(pagina) * 10 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 @register.filter
def lookup(d, key): def lookup(d, key):
return d[key] if key in d else [] return d[key] if key in d else []
@ -245,6 +262,7 @@ def youtube_url(value):
r = re.findall(youtube_pattern, value) r = re.findall(youtube_pattern, value)
return True if r else False return True if r else False
@register.filter @register.filter
def facebook_url(value): def facebook_url(value):
value = value.lower() value = value.lower()
@ -252,6 +270,7 @@ def facebook_url(value):
r = re.findall(facebook_pattern, value) r = re.findall(facebook_pattern, value)
return True if r else False return True if r else False
@register.filter @register.filter
def youtube_id(value): def youtube_id(value):
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
@ -339,4 +358,3 @@ def dont_break_out(value):
_safe = '<div class="dont-break-out">{}</div>'.format(value) _safe = '<div class="dont-break-out">{}</div>'.format(value)
_safe = mark_safe(_safe) _safe = mark_safe(_safe)
return _safe return _safe

20
sapl/painel/views.py

@ -352,6 +352,18 @@ def get_cronometro_status(request, name):
cronometro = '' cronometro = ''
return 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): def get_materia_aberta(pk):
return OrdemDia.objects.filter( return OrdemDia.objects.filter(
@ -558,10 +570,10 @@ def get_dados_painel(request, pk):
'sessao_solene': sessao.tipo.nome == "Solene", 'sessao_solene': sessao.tipo.nome == "Solene",
'sessao_finalizada': sessao.finalizada, 'sessao_finalizada': sessao.finalizada,
'tema_solene': sessao.tema_solene, 'tema_solene': sessao.tema_solene,
'cronometro_aparte': get_cronometro_status(request, 'aparte'), 'cronometro_aparte': get_cronometro_value(request, 'aparte'),
'cronometro_discurso': get_cronometro_status(request, 'discurso'), 'cronometro_discurso': get_cronometro_value(request, 'discurso'),
'cronometro_ordem': get_cronometro_status(request, 'ordem'), 'cronometro_ordem': get_cronometro_value(request, 'ordem'),
'cronometro_consideracoes': get_cronometro_status(request, 'consideracoes'), 'cronometro_consideracoes': get_cronometro_value(request, 'consideracoes'),
'status_painel': sessao.painel_aberto, 'status_painel': sessao.painel_aberto,
'brasao': brasao 'brasao': brasao
} }

110
sapl/relatorios/views.py

@ -535,7 +535,8 @@ def get_sessao_plenaria(sessao, casa):
for composicao in IntegranteMesa.objects.select_related('parlamentar', 'cargo')\ for composicao in IntegranteMesa.objects.select_related('parlamentar', 'cargo')\
.filter(sessao_plenaria=sessao)\ .filter(sessao_plenaria=sessao)\
.order_by('cargo_id'): .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 sigla = '' if not partido_sigla else partido_sigla.partido.sigla
lst_mesa.append({ lst_mesa.append({
'nom_parlamentar': composicao.parlamentar.nome_parlamentar, 'nom_parlamentar': composicao.parlamentar.nome_parlamentar,
@ -545,7 +546,8 @@ def get_sessao_plenaria(sessao, casa):
# Lista de presença na sessão # Lista de presença na sessão
lst_presenca_sessao = [] 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]: for parlamentar in [p.parlamentar for p in presenca]:
lst_presenca_sessao.append({ lst_presenca_sessao.append({
"nom_parlamentar": parlamentar.nome_parlamentar, "nom_parlamentar": parlamentar.nome_parlamentar,
@ -554,7 +556,8 @@ def get_sessao_plenaria(sessao, casa):
# Lista de ausencias na sessão # Lista de ausencias na sessão
lst_ausencia_sessao = [] 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: for ausente in ausencia:
lst_ausencia_sessao.append({ lst_ausencia_sessao.append({
"parlamentar": ausente.parlamentar, "parlamentar": ausente.parlamentar,
@ -564,7 +567,8 @@ def get_sessao_plenaria(sessao, casa):
# Exibe os Expedientes # Exibe os Expedientes
lst_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: for e in expedientes:
conteudo = e.conteudo conteudo = e.conteudo
if not is_empty(conteudo): if not is_empty(conteudo):
@ -572,9 +576,11 @@ def get_sessao_plenaria(sessao, casa):
# https://github.com/interlegis/sapl/issues/1046 # https://github.com/interlegis/sapl/issues/1046
conteudo = re.sub('style=".*?"', '', conteudo) conteudo = re.sub('style=".*?"', '', conteudo)
conteudo = re.sub('class=".*?"', '', 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('<p\s+>', '<p>', conteudo) conteudo = re.sub('<p\s+>', '<p>', conteudo)
conteudo = re.sub('<br\s+/>', '<br/>', conteudo) # OSTicket Ticket #796450 # OSTicket Ticket #796450
conteudo = re.sub('<br\s+/>', '<br/>', conteudo)
conteudo = html.unescape(conteudo) conteudo = html.unescape(conteudo)
# escape special character '&' # escape special character '&'
@ -612,12 +618,15 @@ def get_sessao_plenaria(sessao, casa):
"votacao_observacao": ' ' "votacao_observacao": ' '
} }
numeracao = Numeracao.objects.filter(materia=expediente_materia.materia).first() numeracao = Numeracao.objects.filter(
materia=expediente_materia.materia).first()
if numeracao: 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() 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: if autoria:
for a in autoria: for a in autoria:
if a.autor.nome: if a.autor.nome:
@ -630,14 +639,19 @@ def get_sessao_plenaria(sessao, casa):
materia=expediente_materia.materia).first() materia=expediente_materia.materia).first()
rp = expediente_materia.retiradapauta_set.filter( rp = expediente_materia.retiradapauta_set.filter(
materia=expediente_materia.materia).first() materia=expediente_materia.materia).first()
rl = expediente_materia.registroleitura_set.filter(
materia=expediente_materia.materia).first()
if rv: if rv:
resultado = rv.tipo_resultado_votacao.nome resultado = rv.tipo_resultado_votacao.nome
resultado_observacao = rv.observacao resultado_observacao = rv.observacao
elif rp: elif rp:
resultado = rp.tipo_de_retirada.descricao resultado = rp.tipo_de_retirada.descricao
resultado_observacao = rp.observacao resultado_observacao = rp.observacao
elif rl:
resultado = _('Matéria lida')
resultado_observacao = rl.observacao
else: else:
resultado = _('Matéria lida') \ resultado = _('Matéria não lida') \
if expediente_materia.tipo_votacao == 4 \ if expediente_materia.tipo_votacao == 4 \
else _('Matéria não votada') else _('Matéria não votada')
resultado_observacao = _(' ') resultado_observacao = _(' ')
@ -672,8 +686,10 @@ def get_sessao_plenaria(sessao, casa):
# Lista dos oradores do Expediente # Lista dos oradores do Expediente
lst_oradores_expediente = [] lst_oradores_expediente = []
for orador_expediente in OradorExpediente.objects.filter(sessao_plenaria=sessao).order_by('numero_ordem'): for orador_expediente in OradorExpediente.objects.filter(sessao_plenaria=sessao).order_by('numero_ordem'):
parlamentar = Parlamentar.objects.get(id=orador_expediente.parlamentar.id) parlamentar = Parlamentar.objects.get(
partido_sigla = Filiacao.objects.filter(parlamentar=parlamentar).first() id=orador_expediente.parlamentar.id)
partido_sigla = Filiacao.objects.filter(
parlamentar=parlamentar).first()
lst_oradores_expediente.append({ lst_oradores_expediente.append({
"num_ordem": orador_expediente.numero_ordem, "num_ordem": orador_expediente.numero_ordem,
"nom_parlamentar": parlamentar.nome_parlamentar, "nom_parlamentar": parlamentar.nome_parlamentar,
@ -709,7 +725,8 @@ def get_sessao_plenaria(sessao, casa):
numeracao = materia.numeracao_set.first() numeracao = materia.numeracao_set.first()
if numeracao: 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() materia_em_tramitacao = materia.materiaemtramitacao_set.first()
dic_votacao.update({ dic_votacao.update({
@ -736,14 +753,19 @@ def get_sessao_plenaria(sessao, casa):
materia=votacao.materia).first() materia=votacao.materia).first()
rp = votacao.retiradapauta_set.filter( rp = votacao.retiradapauta_set.filter(
materia=votacao.materia).first() materia=votacao.materia).first()
rl = votacao.registroleitura_set.filter(
materia=votacao.materia).first()
if rv: if rv:
resultado = rv.tipo_resultado_votacao.nome resultado = rv.tipo_resultado_votacao.nome
resultado_observacao = rv.observacao resultado_observacao = rv.observacao
elif rp: elif rp:
resultado = rp.tipo_de_retirada.descricao resultado = rp.tipo_de_retirada.descricao
resultado_observacao = rp.observacao resultado_observacao = rp.observacao
elif rl:
resultado = _('Matéria lida')
resultado_observacao = rl.observacao
else: else:
resultado = _('Matéria lida') if \ resultado = _('Matéria não lida') if \
votacao.tipo_votacao == 4 else _('Matéria não votada') votacao.tipo_votacao == 4 else _('Matéria não votada')
resultado_observacao = _(' ') resultado_observacao = _(' ')
@ -777,11 +799,14 @@ def get_sessao_plenaria(sessao, casa):
# Lista dos oradores da Ordem do Dia # Lista dos oradores da Ordem do Dia
lst_oradores_ordemdia = [] 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: for orador_ordemdia in oradores_ordem_dia:
parlamentar_orador = Parlamentar.objects.get(id=orador_ordemdia.parlamentar.id) parlamentar_orador = Parlamentar.objects.get(
sigla_partido = Filiacao.objects.filter(parlamentar=parlamentar_orador).first() id=orador_ordemdia.parlamentar.id)
sigla_partido = Filiacao.objects.filter(
parlamentar=parlamentar_orador).first()
lst_oradores_ordemdia.append({ lst_oradores_ordemdia.append({
"num_ordem": orador_ordemdia.numero_ordem, "num_ordem": orador_ordemdia.numero_ordem,
@ -794,7 +819,8 @@ def get_sessao_plenaria(sessao, casa):
lst_oradores = [] lst_oradores = []
for orador in Orador.objects.select_related('parlamentar').filter(sessao_plenaria=sessao).order_by('numero_ordem'): for orador in Orador.objects.select_related('parlamentar').filter(sessao_plenaria=sessao).order_by('numero_ordem'):
parlamentar = orador.parlamentar 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({ lst_oradores.append({
"num_ordem": orador.numero_ordem, "num_ordem": orador.numero_ordem,
"nom_parlamentar": parlamentar.nome_parlamentar, "nom_parlamentar": parlamentar.nome_parlamentar,
@ -841,7 +867,8 @@ def get_sessao_plenaria(sessao, casa):
def get_turno(materia): def get_turno(materia):
descricao_turno = '' descricao_turno = ''
descricao_tramitacao = '' 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="") tramitacoes_turno = tramitacoes.exclude(turno="")
if tramitacoes: if tramitacoes:
@ -850,7 +877,8 @@ def get_turno(materia):
if t[0] == tramitacoes_turno.first().turno: if t[0] == tramitacoes_turno.first().turno:
descricao_turno = str(t[1]) descricao_turno = str(t[1])
break 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 return descricao_turno, descricao_tramitacao
@ -1231,9 +1259,11 @@ def get_pauta_sessao(sessao, casa):
# https://github.com/interlegis/sapl/issues/1046 # https://github.com/interlegis/sapl/issues/1046
conteudo = re.sub('style=".*?"', '', conteudo) conteudo = re.sub('style=".*?"', '', conteudo)
conteudo = re.sub('class=".*?"', '', 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('<p\s+>', '<p>', conteudo) conteudo = re.sub('<p\s+>', '<p>', conteudo)
conteudo = re.sub('<br\s+/>', '<br/>', conteudo) # OSTicket Ticket #796450 # OSTicket Ticket #796450
conteudo = re.sub('<br\s+/>', '<br/>', conteudo)
conteudo = html.unescape(conteudo) conteudo = html.unescape(conteudo)
# escape special character '&' # escape special character '&'
@ -1263,7 +1293,8 @@ def make_pdf(base_url, main_template, header_template, main_css='', header_css='
# Template of header # Template of header
html = HTML(base_url=base_url, string=header_template) 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_page = header.pages[0]
header_body = get_page_body(header_page._page_box.all_children()) 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({'object': sessao_plenaria})
context.update({'data': dt.today().strftime('%d/%m/%Y')}) context.update({'data': dt.today().strftime('%d/%m/%Y')})
context.update({'rodape': rodape}) 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_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 = HttpResponse(content_type='application/pdf;')
response['Content-Disposition'] = 'inline; filename=relatorio.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({'data': dt.today().strftime('%d/%m/%Y')})
context.update({'rodape': rodape}) 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_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 = HttpResponse(content_type='application/pdf;')
response['Content-Disposition'] = 'inline; filename=relatorio.pdf' response['Content-Disposition'] = 'inline; filename=relatorio.pdf'
@ -1520,7 +1557,8 @@ def relatorio_sessao_plenaria_pdf(request, pk):
'decimo_quarto_ordenacao': 'ocorrencias_sessao.html' '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 {} \ info = "Resumo da {}ª Reunião {} \
da {}ª Sessão Legislativa da {} \ da {}ª Sessão Legislativa da {} \
@ -1535,7 +1573,8 @@ def relatorio_sessao_plenaria_pdf(request, pk):
"logotipo": casa.logotipo, "logotipo": casa.logotipo,
"info": info}) "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 = HttpResponse(content_type='application/pdf;')
response['Content-Disposition'] = 'inline; filename=relatorio.pdf' response['Content-Disposition'] = 'inline; filename=relatorio.pdf'
@ -1556,7 +1595,8 @@ def gera_etiqueta_ml(materia_legislativa, base_url):
max_ementa_size = 240 max_ementa_size = 240
ementa = materia_legislativa.ementa 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 = { context = {
'numero': materia_legislativa.numero, 'numero': materia_legislativa.numero,
@ -1570,10 +1610,12 @@ def gera_etiqueta_ml(materia_legislativa, base_url):
'barcode': barcode '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) 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() pdf_file = main_doc.write_pdf()
return pdf_file return pdf_file

66
sapl/sessao/views.py

@ -86,7 +86,8 @@ def reordena_materias(request, pk, tipo, ordenacao):
"ordemdia": "sapl.sessao:ordemdia_list" "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 = [] update_list = []
for numero, materia in enumerate(materias, 1): for numero, materia in enumerate(materias, 1):
@ -106,7 +107,8 @@ def verifica_presenca(request, model, spk, is_leitura=False):
else: else:
text = 'Votação não pode ser aberta sem presenças' 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) msg = _(text)
messages.add_message(request, messages.ERROR, msg) messages.add_message(request, messages.ERROR, msg)
return False return False
@ -204,7 +206,8 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
for i, row in enumerate(context['rows']): for i, row in enumerate(context['rows']):
materia = context['object_list'][i].materia materia = context['object_list'][i].materia
obj = context['object_list'][i] 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 "-" numeracao = materia.numeracao_set.first() if materia.numeracao_set.first() else "-"
autoria = materia.autoria_set.filter(primeiro_autor=True) autoria = materia.autoria_set.filter(primeiro_autor=True)
autor = ', '.join([str(a.autor) for a in autoria]) if autoria else "-" 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 # url em toda a string de title_materia
context['rows'][i][1] = (title_materia, None) context['rows'][i][1] = (title_materia, None)
exist_resultado = obj.registrovotacao_set.filter(materia=obj.materia).exists() exist_resultado = obj.registrovotacao_set.filter(
exist_retirada = obj.retiradapauta_set.filter(materia=obj.materia).exists() materia=obj.materia).exists()
exist_leitura = obj.registroleitura_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\ if (obj.tipo_votacao != 4 and not exist_resultado and not exist_retirada) or\
(obj.tipo_votacao == 4 and not exist_leitura): (obj.tipo_votacao == 4 and not exist_leitura):
@ -571,7 +577,8 @@ class TransferenciaMateriasSessaoAbstract(PermissionRequiredMixin, ListView):
TransferenciaMateriasSessaoAbstract, self TransferenciaMateriasSessaoAbstract, self
).get_context_data(**kwargs) ).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['subnav_template_name'] = 'sessao/subnav.yaml'
context['root_pk'] = self.kwargs['pk'] context['root_pk'] = self.kwargs['pk']
@ -627,7 +634,7 @@ class TransferenciaMateriasSessaoAbstract(PermissionRequiredMixin, ListView):
messages.add_message(request, messages.ERROR, msg) messages.add_message(request, messages.ERROR, msg)
msg_c = _( 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.' 'Interlegis.'
) )
messages.add_message(request, messages.WARNING, msg_c) messages.add_message(request, messages.WARNING, msg_c)
@ -925,7 +932,6 @@ class OradorCrud(MasterDetailCrud):
form_class = OradorForm form_class = OradorForm
template_name = 'sessao/oradores_create.html' template_name = 'sessao/oradores_create.html'
def get_initial(self): def get_initial(self):
return {'id_sessao': self.kwargs['pk']} return {'id_sessao': self.kwargs['pk']}
@ -937,7 +943,8 @@ class OradorCrud(MasterDetailCrud):
if tipo_sessao.nome == "Solene": if tipo_sessao.nome == "Solene":
context.update( context.update(
{'subnav_template_name': 'sessao/subnav-solene.yaml'}) {'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 context["ultima_ordem"] = ultimo_orador.numero_ordem if ultimo_orador else 0
return context return context
@ -1009,7 +1016,8 @@ class OradorExpedienteCrud(OradorCrud):
if tipo_sessao.nome == "Solene": if tipo_sessao.nome == "Solene":
context.update( context.update(
{'subnav_template_name': 'sessao/subnav-solene.yaml'}) {'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 context["ultima_ordem"] = ultimo_orador.numero_ordem if ultimo_orador else 0
return context return context
@ -1074,6 +1082,7 @@ class OradorExpedienteCrud(OradorCrud):
class OradorOrdemDiaCrud(OradorCrud): class OradorOrdemDiaCrud(OradorCrud):
model = OradorOrdemDia model = OradorOrdemDia
class CreateView(MasterDetailCrud.CreateView): class CreateView(MasterDetailCrud.CreateView):
form_class = OradorOrdemDiaForm form_class = OradorOrdemDiaForm
template_name = 'sessao/oradores_create.html' template_name = 'sessao/oradores_create.html'
@ -1087,7 +1096,8 @@ class OradorOrdemDiaCrud(OradorCrud):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**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 context["ultima_ordem"] = ultimo_orador.numero_ordem if ultimo_orador else 0
return context return context
@ -1936,20 +1946,26 @@ def get_materias_expediente(sessao_plenaria):
rv = m.registrovotacao_set.filter(materia=m.materia).first() rv = m.registrovotacao_set.filter(materia=m.materia).first()
rp = m.retiradapauta_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: if rv:
resultado = rv.tipo_resultado_votacao.nome resultado = rv.tipo_resultado_votacao.nome
resultado_observacao = rv.observacao resultado_observacao = rv.observacao
elif rp: elif rp:
resultado = rp.tipo_de_retirada.descricao resultado = rp.tipo_de_retirada.descricao
resultado_observacao = rp.observacao resultado_observacao = rp.observacao
elif rl:
resultado = _('Matéria lida')
resultado_observacao = rl.observacao
else: 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 = '' resultado_observacao = ''
voto_nominal = [] voto_nominal = []
if m.tipo_votacao == 2: if m.tipo_votacao == 2:
for voto in VotoParlamentar.objects.filter(expediente=m.id): 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() voto = RegistroVotacao.objects.filter(expediente=m.id).last()
if voto: if voto:
@ -2061,20 +2077,26 @@ def get_materias_ordem_do_dia(sessao_plenaria):
# Verificar resultado # Verificar resultado
rv = o.registrovotacao_set.filter(materia=o.materia).first() rv = o.registrovotacao_set.filter(materia=o.materia).first()
rp = o.retiradapauta_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: if rv:
resultado = rv.tipo_resultado_votacao.nome resultado = rv.tipo_resultado_votacao.nome
resultado_observacao = rv.observacao resultado_observacao = rv.observacao
elif rp: elif rp:
resultado = rp.tipo_de_retirada.descricao resultado = rp.tipo_de_retirada.descricao
resultado_observacao = rp.observacao resultado_observacao = rp.observacao
elif rl:
resultado = _('Matéria lida')
resultado_observacao = rl.observacao
else: 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 = '' resultado_observacao = ''
voto_nominal = [] voto_nominal = []
if o.tipo_votacao == 2: if o.tipo_votacao == 2:
for voto in VotoParlamentar.objects.filter(ordem=o.id): 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() voto = RegistroVotacao.objects.filter(ordem=o.id).last()
if voto: if voto:
@ -3435,7 +3457,8 @@ class VotacaoExpedienteView(SessaoPermissionMixin):
self.logger.error("user=" + username + ". " + str(e)) self.logger.error("user=" + username + ". " + str(e))
return self.form_invalid(form) return self.form_invalid(form)
else: else:
expediente = ExpedienteMateria.objects.get(id=expediente_id) expediente = ExpedienteMateria.objects.get(
id=expediente_id)
resultado = TipoResultadoVotacao.objects.get( resultado = TipoResultadoVotacao.objects.get(
id=request.POST['resultado_votacao']) id=request.POST['resultado_votacao'])
expediente.resultado = resultado.nome expediente.resultado = resultado.nome
@ -3586,7 +3609,8 @@ class PautaSessaoDetailView(DetailView):
# ===================================================================== # =====================================================================
# Identificação Básica # Identificação Básica
abertura = self.object.data_inicio.strftime('%d/%m/%Y') 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_inicio = self.object.hora_inicio
hora_fim = self.object.hora_fim hora_fim = self.object.hora_fim
@ -3612,7 +3636,8 @@ class PautaSessaoDetailView(DetailView):
resultado = _('Matéria não votada') resultado = _('Matéria não votada')
resultado_observacao = _(' ') 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() numeracao = m.materia.numeracao_set.first()
materias_expediente.append({ materias_expediente.append({
@ -3662,7 +3687,8 @@ class PautaSessaoDetailView(DetailView):
resultado = _('Matéria não votada') resultado = _('Matéria não votada')
resultado_observacao = _(' ') 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() numeracao = o.materia.numeracao_set.first()
materias_ordem.append({ materias_ordem.append({

3
sapl/templates/materia/materialegislativa_filter.html

@ -89,6 +89,9 @@
<strong>Status:</strong> &nbsp;{{m.tramitacao_set.first.status}}</br> <strong>Status:</strong> &nbsp;{{m.tramitacao_set.first.status}}</br>
<strong>Data Fim Prazo (Tramitação):</strong>&nbsp;{{m.tramitacao_set.first.data_fim_prazo|default_if_none:""}}</br> <strong>Data Fim Prazo (Tramitação):</strong>&nbsp;{{m.tramitacao_set.first.data_fim_prazo|default_if_none:""}}</br>
{% endif %} {% endif %}
{% if m|resultado_votacao %}
<strong>Resultado:</strong> &nbsp;{{m|resultado_votacao}}</br>
{% endif %}
{% if m.registrovotacao_set.exists %} {% if m.registrovotacao_set.exists %}
<strong>Data Votação:</strong> <strong>Data Votação:</strong>
{% for rv in m.registrovotacao_set.all %} {% for rv in m.registrovotacao_set.all %}

536
sapl/templates/painel/index.html

@ -1,10 +1,9 @@
{% load i18n %} {% load i18n %}
{% load common_tags %} {% load common_tags %}
{% load staticfiles %}
{% load render_bundle from webpack_loader %} {% load render_bundle from webpack_loader %}
{% load webpack_static from webpack_loader %} {% load webpack_static from webpack_loader %}
<!DOCTYPE HTML>
<!--[if IE 8]> <html class="no-js lt-ie9" lang="pt-br"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9" lang="pt-br"> <![endif]-->
<!--[if gt IE 8]><!--> <!--[if gt IE 8]><!-->
<html lang="pt-br"> <html lang="pt-br">
@ -21,7 +20,6 @@
{% render_bundle 'painel' 'css' %} {% render_bundle 'painel' 'css' %}
{% endblock webpack_loader_css %} {% endblock webpack_loader_css %}
<style type="text/css"> <style type="text/css">
html, body { html, body {
max-width: 100%; max-width: 100%;
@ -35,83 +33,169 @@
</style> </style>
</head> </head>
<body class="painel-principal"> <body class="painel-principal">
<div id="app-painel"> <!-- app painel -->
<audio type="hidden" id="audio" src="{% webpack_static 'audio/ring.mp3' %}"></audio> <audio type="hidden" id="audio" src="{% webpack_static 'audio/ring.mp3' %}"></audio>
<div class="row justify-content-center">
<img src="{{ MEDIA_URL }}{{ logotipo }}"
alt="Logo" class="img-responsive" style="height: 15vh"/>
</div>
<div class="d-flex justify-content-center"> <div class="d-flex justify-content-center">
<h1 id="sessao_plenaria" class="title text-title"></h1> <h1 id="sessao_plenaria" class="title text-title">[[ sessao_plenaria ]]</h1>
</div> </div>
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col-md-5 text-right">
<span id="sessao_plenaria_data" class="text-value"></span> <span id="sessao_plenaria_data" class="text-value">[[ sessao_plenaria_data ]]</span>
</div>
<div class="col text-center">
<span id="sessao_plenaria_hora_inicio" class="text-value"></span>
</div>
</div> </div>
<div class="col-md-7 text-center">
<div class="row justify-content-center"> <span id="sessao_plenaria_hora_inicio" class="text-value">[[ sessao_plenaria_hora_inicio ]]</span>
<div class="col-1">
<img src="" id="logo-painel" class="logo-painel" alt=""/>
</div> </div>
</div> </div>
<div class="row justify-content-center"> <div class="row justify-content-center" style="margin-bottom: 50px; margin-top: 50px;">
<h2 class="text-danger"><span id="message"></span></h2> <h2 class="text-danger"><span v-if="!painel_aberto">PAINEL ENCONTRA-SE FECHADO</span></h2>
</div> </div>
<div class="row"> <div class="row">
<div class="col text-center"><span class="text-value data-hora" id="date"></span></div> <div class="col-md-5 text-right"><span class="text-value data-hora" id="date"></span></div>
<div class="col text-center"><span class="text-value data-hora" id="relogio"></span></div> <div class="col-md-7 text-center" style="margin-top:10px"><span class="text-value data-hora" id="relogio"></span></div>
</div> </div>
<div style="margin-bottom: 50px;"> <!-- outer div -->
<div class="">
<div class="d-flex justify-content-start"> <div class="d-flex justify-content-start">
<div class="col-md-4"> <div class="col-md-2"></div>
<div class="text-center painel"> <div class="col-md-3"> <!-- Lista parlamentares -->
<div class="text center painel">
<h2 class="text-subtitle">Parlamentares</h2> <h2 class="text-subtitle">Parlamentares</h2>
<span id="parlamentares" class="text-value text-center"></span> <div v-if="painel_aberto"> <!-- v-if -->
</div> <table>
</div> <tbody v-for="p in presentes">
<div class="d-flex col-md-8 painels"> <tr>
<div class="col-md-6 text-center painel" id="aparecer_oradores"> <td style="padding-right: 20px;" class="d-flex justify-content-start" v-bind:style="{ color: p.color }">
[[p.nome]]
</td>
<td style="padding-right: 20px;" v-bind:style="{ color: p.color }">
[[p.partido]]
</td>
<td style="padding-right: 20px;" v-if="p.voto !== ''" v-bind:style="{ color: p.color }">
[[p.voto]]
</td>
</tr>
</tbody>
</table>
</div> <!-- v-if -->
<div v-else> <!-- v-else -->
<span style="color:white">
<center>A listagem de parlamentares só aparecerá quando o painel estiver aberto.</center>
</span>
</div> <!-- v-if -->
</div>
</div> <!-- Lista parlamentares -->
<div class="d-flex col-md-7 painels justify-content-center">
<div v-if="oradores.length > 0" class="text-center painel" id="aparecer_oradores">
<h2 class="text-subtitle">Oradores</h2> <h2 class="text-subtitle">Oradores</h2>
<span id="orador"></span> <div v-if="painel_aberto"> <!-- v-if Lista de oradores-->
</div> <table>
<tbody v-for="o in oradores">
<div class="col-md-6 text-center painel"> <tr class="d-flex justify-content-center">
<td style="padding-right: 20px; color: white; font-size: 20px;">
[[ o.numero]]º &nbsp[[o.nome]]
</td>
</tr>
</tbody>
</table>
</div> <!--v-if Lista de oradores-->
<div v-else>
<span style="color:white">
<center>A listagem de oradores só aparecerá quando o painel estiver aberto.</center>
</span>
</div>
</div>
</div>
</div>
</div> <!-- outer div -->
<div class="row"> <!-- row -->
<div class="col-md-2"></div>
<div class="col-md-4 text-left painel">
<h2 class="text-subtitle">Cronômetros</h2> <h2 class="text-subtitle">Cronômetros</h2>
<div class="text-value"> <div class="text-value">
Discurso: <span id="cronometro_discurso"></span><br> Discurso:<span id='discurso' style="margin-right: 20px;">[[ cronometro_discurso ]]</span><span v-if="painel_aberto">
Aparte: <span id="cronometro_aparte"></span><br> <span v-if='running != 1'><button class="btn btn-success" v-on:click="start (1)">Iniciar</button><button style="margin-left: 10px;" class="btn btn-success" v-on:click="reset">Reiniciar</button></span>
Questão de Ordem: <span id="cronometro_ordem"></span><br> <span v-else><button class="btn btn-danger" v-on:click="stop(1)">Parar</button></span>
Considerações Finais: <span id="cronometro_consideracoes"></span> </span><br>
</div>
</div>
<div class="col-md-6 text-center painel" id="resultado_votacao_div"> Aparte:<span id='discurso' style="margin-right: 20px;">[[ cronometro_aparte ]]</span><span v-if="painel_aberto">
<h2 class="text-subtitle">Resultado</h2> <span v-if='running != 2'><button class="btn btn-success" v-on:click="start (2)">Iniciar</button><button style="margin-left: 10px;" class="btn btn-success" v-on:click="reset">Reiniciar</button></span>
<span id="votacao" class="text-value"></span> <span v-else><button class="btn btn-danger" v-on:click="stop(2)">Parar</button></span>
<h2><span id="resultado_votacao" lass="text-title"></span> </span><br>
</div>
<div class="col-md-6 text-center painel" id="obs_materia_div"> Questão de Ordem: <span id='discurso' style="margin-right: 20px;">[[ cronometro_ordem ]]</span><span v-if="painel_aberto">
<h2 class="text-subtitle" id="mat_em_votacao">Matéria em Votação</h2> <span v-if='running != 3'><button class="btn btn-success" v-on:click="start (3)">Iniciar</button><button style="margin-left: 10px;" class="btn btn-success" v-on:click="reset">Reiniciar</button></span>
<span id="materia_legislativa_texto" class="text-value"></span> <span v-else><button class="btn btn-danger" v-on:click="stop(3)">Parar</button></span>
</span><br>
Considerações Finais: <span id='discurso' style="margin-right: 20px;">[[ cronometro_consideracoes ]]</span><span v-if="painel_aberto">
<span v-if='running != 4'><button class="btn btn-success" v-on:click="start (4)">Iniciar</button><button style="margin-left: 10px;" class="btn btn-success" v-on:click="reset">Reiniciar</button></span>
<span v-else><button class="btn btn-danger" v-on:click="stop(4)">Parar</button></span>
</span>
</div>
</div>
<div v-if="sessao_solene">
<div class="col-md-4 text-center painel" id="tema_solene_div">
<h2 class="text-subtitle">Tema da Sessão Solene</h2>
<span id="sessao_solene_tema" class="text-value">[[ sessao_solene_tema ]]</span>
</div>
</div>
<div v-else class="col-md-5" style="margin-top: -45vh; margin-left: 51vw; position: absolute;">
<div v-if="painel_aberto">
<div v-if="!sessao_finalizada" class="text-center painel" id="resultado_votacao_div">
<h2 class="text-subtitle" style="margin-left: -50px; margin-top: 50px;">Resultado</h2>
<div v-if="materia_legislativa_texto">
<span id="votacao" class="text-value">
<li>Sim: [[ numero_votos_sim ]]</li>
<li>Não: [[ numero_votos_nao ]]</li>
<li>Abstenções: [[ numero_abstencoes ]]</li>
<li>Presentes: [[ num_presentes ]]</li>
<li>Total votos: [[ total_votos ]]</li>
</span>
<h2><span id="resultado_votacao" v-bind:style="resultado_votacao_css" lass="text-title">[[ tipo_resultado ]]</span></h2>
</div>
<div v-else>
<center>Não há votação, pois não há nenhuma matéria aberta ou já votada.</center>
</div>
</div>
<div v-if="painel_aberto">
<div v-if="!sessao_finalizada" class="text-center painel" id="obs_materia_div">
<h2 class="text-subtitle" id="mat_em_votacao">[[ mat_em_votacao ]]</h2>
<div v-if="materia_legislativa_texto !== ''">
<span id="materia_legislativa_texto" class="text-value">[[ materia_legislativa_texto ]] </span>
<br> <br>
<span id="materia_legislativa_ementa" class="text-value"></span> <span id="materia_legislativa_ementa" class="text-value">[[ materia_legislativa_ementa ]]</span>
<br> <br>
<span id="observacao_materia" class="text-value"></span> <span id="observacao_materia" class="text-value"> [[ observacao_materia ]]</span>
</div> </div>
<div v-else>
<div class="col-md-6 text-center painel" id="tema_solene_div" style="display: none"> <span class="text-value">Não há nenhuma matéria votada ou para votação.</span>
<h2 class="text-subtitle">Tema da Sessão Solene</h2>
<span id="sessao_solene_tema" class="text-value"></span>
</div> </div>
</div> </div>
</div> </div>
<div v-else>
<span class="text-value">A Matéria em votação só aparecerá quando o painel estiver aberto</span>
</div> </div>
</div> </div>
<div v-else>
<div class="text-center painel">
<h2 class="text-subtitle" style="margin-top: 50px;">Resultado</h2>
<span style="color:white">
<h1>A votação só aparecerá quando o painel estiver aberto</h1>
</span>
</div>
</div>
</div>
</div> <!-- row -->
</div> <!-- app painel -->
</body> </body>
{% block webpack_loader_js %} {% block webpack_loader_js %}
@ -123,8 +207,7 @@
{% block webpack_loader_chunks_js %} {% block webpack_loader_chunks_js %}
{% endblock webpack_loader_chunks_js %} {% endblock webpack_loader_chunks_js %}
<script type="text/javascript"> <script type='text/javascript'>
$(document).ready(function() { $(document).ready(function() {
// As constantes decisões sobre a existência ou não do horário de verão, // As constantes decisões sobre a existência ou não do horário de verão,
@ -143,353 +226,6 @@
$("#date").append(moment().format("DD/MM/YY")); $("#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) {
$("#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");
var oradores = $("#orador")
$("#votacao").text('');
presentes.children().remove();
votacao.children().remove();
oradores.children().remove();
var oradores_list = data["oradores"];
var presentes_list = data["presentes"];
if (data["status_painel"] == true) {
presentes.append('<table id="parlamentares_list">');
$.each(presentes_list, function (index, parlamentar) {
if (parlamentar.voto == 'Voto Informado'){
$('#parlamentares_list').append('<tr><td style="padding-right:20px; color:yellow" >' +
parlamentar.nome +
'</td> <td style="padding-right:20px; color:yellow">' +
parlamentar.partido + '</td> <td style="padding-right:20px; color:yellow">'
+ '</td></tr>')
}
else{
$('#parlamentares_list').append(show_voto(parlamentar))
}
});
presentes.append('</table>')
if (data["oradores"].length > 0){
$('#aparecer_oradores').show();
oradores.append('<table id="oradores_list">');
$.each(oradores_list, function (index, orador) {
$('#oradores_list').append('<tr><td style="padding-right:20px; color:white" >' +
orador.numero + 'º &nbsp' +
orador.nome +'</td></tr>')
});
oradores.append('</table>');
}
else {
$('#aparecer_oradores').hide();
}
}
else{
presentes.append('<span style="color:white" id="parlamentares_list">');
$('#parlamentares_list').append(
'<center>A listagem de parlamentares só aparecerá quando o painel estiver aberto.</center>')
presentes.append('</span>');
oradores.append('<span style="color:white" id="oradores_list">');
$('#oradores_list').append(
'<center>A listagem de oradores só aparecerá quando o painel estiver aberto.</center>')
oradores.append('</span>');
votacao.append('<span id="votacao">');
$("#votacao").append('<center>A votação só aparecerá quando o painel estiver aberto</center>');
votacao.append('</span>');
}
if(data["status_painel"]){
if (data['materia_legislativa_texto']){
var votacao = $("#votacao");
votacao.append("<li>Sim: " + data["numero_votos_sim"] + "</li>");
votacao.append("<li>Não: " + data["numero_votos_nao"] + "</li>");
votacao.append("<li>Abstenções: " + data["numero_abstencoes"] + "</li>");
votacao.append("<li>Presentes: " + data["num_presentes"] + "</li>");
votacao.append("<li>Total votos: " + data["total_votos"] + "</li>");
}
else{
$("#votacao").append('<center>Não há votação, pois não há nenhuma matéria aberta ou já votada.</center>');
}
}
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();
}
if(data['sessao_finalizada']){
$("#obs_materia_div").hide();
$("#resultado_votacao_div").hide();
}
else if (data['materia_legislativa_texto']){
if (data["status_painel"] == true){
$("#materia_legislativa_texto").text(data["materia_legislativa_texto"]);
$("#materia_legislativa_ementa").text(data["materia_legislativa_ementa"]);
}
else{
$("#materia_legislativa_texto").text('A Matéria em votação só aparecerá quando o painel estiver aberto');
}
}
else{
$("#materia_legislativa_texto").text('Não há nenhuma matéria votada ou para votação.');
}
if (data['observacao_materia'] && data["status_painel"] == true){
var texto = data['observacao_materia'];
if(texto.length > 151) {
$("#observacao_materia").text(texto.substr(0, 145).concat('(...)'));
}
else{
$("#observacao_materia").text(texto);
}
}
else{
$("#observacao_materia").text('');
}
if (data['tipo_resultado'] && data['status_painel'] == true){
if(data['tipo_votacao'] != 'Leitura' && !data['sessao_finalizada'] && !data["sessao_solene"]){
$("#resultado_votacao").css("color", "#45919D");
$("#mat_em_votacao").text("Matéria em Votação");
$("#resultado_votacao_div").show();
}
else{
$("#resultado_votacao_div").hide();
$("#mat_em_votacao").text("Matéria em Leitura");
}
console.log(data["tipo_resultado"], data['tipo_votacao']);
$("#resultado_votacao").text(data["tipo_resultado"]);
var resultado_votacao_upper = $("#resultado_votacao").text().toUpperCase();
console.log(resultado_votacao_upper, data['tipo_resultado']);
if (resultado_votacao_upper.search("APROV") != -1){
$("#resultado_votacao").css("color", "#7CFC00");
$("#mat_em_votacao").text("Matéria Votada");
}
else if (resultado_votacao_upper.search("REJEIT") != -1){
$("#resultado_votacao").css("color", "red");
$("#mat_em_votacao").text("Matéria Votada");
}
else if (resultado_votacao_upper.search("LIDA") != -1){
$("#mat_em_votacao").text("Matéria Lida");
}
}
else{
$("#resultado_votacao").text('');
if(data['tipo_votacao'] != 'Leitura')
$("#mat_em_votacao").text("Matéria em Votação");
else{
$("#mat_em_votacao").text("Matéria em Leitura");
}
}
},
error: function(err) {
console.error(err);
},
dataType: "json",
complete: setTimeout(function() {poll()}, 500),
timeout: 20000 // TODO: decrease
}) })
})();
});
function show_voto(parlamentar) {
color = 'white'
if (parlamentar.voto == "Sim"){
color = 'green'
}
else if (parlamentar.voto == "Não"){
color = 'red'
}
return ('<tr> <td style="padding-right:20px">' +
`<font color="`+color+`">${parlamentar.nome}</font> </td> <td style="padding-right:20px">` +
`<font color="`+color+`">${parlamentar.partido}</font> </td> <td style="padding-right:20px">` +
`<font color="`+color+`">${parlamentar.voto}</font> </td></tr>`)
}
</script> </script>
</html> </html>

3
sapl/utils.py

@ -550,7 +550,8 @@ FILTER_OVERRIDES_DATEFIELD = {
class FilterOverridesMetaMixin: class FilterOverridesMetaMixin:
filter_overrides = { filter_overrides = {
models.DateField: FILTER_OVERRIDES_DATEFIELD models.DateField: FILTER_OVERRIDES_DATEFIELD,
models.DateTimeField: FILTER_OVERRIDES_DATEFIELD
} }

Loading…
Cancel
Save