mirror of https://github.com/interlegis/sapl.git
AlGouvea
3 years ago
12 changed files with 1377 additions and 1246 deletions
@ -1 +1,267 @@ |
|||
import './scss/painel.scss' |
|||
import Vue from 'vue' |
|||
import axios from 'axios' |
|||
|
|||
axios.defaults.xsrfCookieName = 'csrftoken' |
|||
axios.defaults.xsrfHeaderName = 'X-CSRFToken' |
|||
|
|||
// Variaveis dos cronometros
|
|||
var crono = 0 |
|||
var time = null |
|||
var timeEnd = null |
|||
var audioAlertFinish = document.getElementById('audio') |
|||
var cronometroStart = [] |
|||
|
|||
const v = new Vue({ // eslint-disable-line
|
|||
delimiters: ['[[', ']]'], |
|||
el: '#app-painel', |
|||
data () { |
|||
return { |
|||
message: 'Hello VueJUS', // TODO: remove when porting to VueJS is done
|
|||
polling: null, |
|||
painel_aberto: true, |
|||
sessao_plenaria: '', |
|||
sessao_plenaria_data: '', |
|||
sessao_plenaria_hora_inicio: '', |
|||
brasao: '', |
|||
cronometro_discurso: '', |
|||
cronometro_aparte: '', |
|||
cronometro_ordem: '', |
|||
cronometro_consideracoes: '', |
|||
sessao_solene: false, |
|||
sessao_solene_tema: '', |
|||
presentes: [], |
|||
oradores: [], |
|||
numero_votos_sim: 0, |
|||
numero_votos_nao: 0, |
|||
numero_abstencoes: 0, |
|||
num_presentes: 0, |
|||
total_votos: 0, |
|||
sessao_finalizada: true, |
|||
materia_legislativa_texto: '', |
|||
materia_legislativa_ementa: '', |
|||
observacao_materia: '', |
|||
mat_em_votacao: '', |
|||
resultado_votacao_css: '', |
|||
tipo_resultado: '', |
|||
tipo_votacao: '', |
|||
running: 0 |
|||
} |
|||
}, |
|||
methods: { |
|||
msgMateria () { |
|||
if (this.tipo_resultado && this.painel_aberto) { |
|||
if (this.tipo_votacao !== 'Leitura' && !this.sessao_finalizada && !this.sessao_solene) { |
|||
this.resultado_votacao_css = 'color: #45919D' |
|||
this.mat_em_votacao = 'Matéria em Votação' |
|||
} else { |
|||
this.resultado_votacao_css = 'color: #45919D' |
|||
this.mat_em_votacao = 'Matéria em Leitura' |
|||
} |
|||
|
|||
this.resultado_votacao = this.tipo_resultado |
|||
|
|||
var resultado_votacao_upper = this.resultado_votacao.toUpperCase() |
|||
|
|||
if (resultado_votacao_upper.search('APROV') !== -1) { |
|||
this.resultado_votacao_css = 'color: #7CFC00' |
|||
this.mat_em_votacao = 'Matéria Votada' |
|||
} else if (resultado_votacao_upper.search('REJEIT') !== -1) { |
|||
this.resultado_votacao_css = 'color: red' |
|||
this.mat_em_votacao = 'Matéria Votada' |
|||
} else if (resultado_votacao_upper.search('LIDA') !== -1) { |
|||
this.mat_em_votacao = 'Matéria Lida' |
|||
} |
|||
} else { |
|||
this.resultado_votacao = '' |
|||
if (this.tipo_votacao !== 'Leitura') { |
|||
this.mat_em_votacao = 'Matéria em Votação' |
|||
} else { |
|||
this.mat_em_votacao = 'Matéria em Leitura' |
|||
} |
|||
} |
|||
}, |
|||
atribuiColor (parlamentar) { |
|||
var color = 'white' |
|||
if (parlamentar.voto === 'Voto Informado' || parlamentar.voto === '') { |
|||
color = 'yellow' |
|||
} else { |
|||
if (parlamentar.voto === 'Sim') { |
|||
color = 'green' |
|||
} else if (parlamentar.voto === 'Não') { |
|||
color = 'red' |
|||
} |
|||
} |
|||
parlamentar.color = color |
|||
}, |
|||
capObservacao (texto) { |
|||
if (texto && texto.length > 151) { |
|||
return texto.substr(0, 145).concat('(...)') |
|||
} |
|||
return texto |
|||
}, |
|||
converterUrl (url) { |
|||
url = url.slice(-(url.length - url.lastIndexOf('/'))) |
|||
url = '/painel' + url + '/dados' |
|||
return url |
|||
}, |
|||
fetchData () { |
|||
// TODO: how to get no hardcoded URL?
|
|||
$.get(this.converterUrl(window.location.pathname), function (response) { |
|||
this.brasao = response.brasao |
|||
this.painel_aberto = response.status_painel |
|||
this.sessao_finalizada = response.sessao_finalizada |
|||
this.sessao_plenaria = response.sessao_plenaria |
|||
this.sessao_plenaria_data = 'Data Início: ' + response.sessao_plenaria_data |
|||
this.sessao_plenaria_hora_inicio = 'Hora Início: ' + response.sessao_plenaria_hora_inicio |
|||
this.sessao_solene = response.sessao_solene |
|||
this.sessao_solene_tema = response.sessao_solene_tema |
|||
|
|||
this.presentes = response.presentes |
|||
this.presentes.forEach(parlamentar => { |
|||
this.atribuiColor(parlamentar) |
|||
}) |
|||
|
|||
this.oradores = response.oradores |
|||
|
|||
this.materia_legislativa_texto = response.materia_legislativa_texto |
|||
this.numero_votos_sim = response.numero_votos_sim |
|||
this.numero_votos_nao = response.numero_votos_nao |
|||
this.numero_abstencoes = response.numero_abstencoes |
|||
this.num_presentes = response.num_presentes |
|||
this.total_votos = response.total_votos |
|||
|
|||
this.materia_legislativa_texto = response.materia_legislativa_texto |
|||
this.materia_legislativa_ementa = response.materia_legislativa_ementa |
|||
this.observacao_materia = this.capObservacao(response.observacao_materia) |
|||
|
|||
this.tipo_resultado = response.tipo_resultado |
|||
this.tipo_votacao = response.tipo_votacao |
|||
this.mat_em_votacao = this.msgMateria() |
|||
|
|||
// Cronometros
|
|||
cronometroStart[0] = response.cronometro_discurso |
|||
cronometroStart[1] = response.cronometro_aparte |
|||
cronometroStart[2] = response.cronometro_ordem |
|||
cronometroStart[3] = response.cronometro_consideracoes |
|||
|
|||
if (time === null) { |
|||
// Pegar data atual
|
|||
this.cronometro_discurso = new Date() |
|||
this.cronometro_aparte = this.cronometro_discurso |
|||
this.cronometro_ordem = this.cronometro_discurso |
|||
this.cronometro_consideracoes = this.cronometro_discurso |
|||
|
|||
// Setar cada Cronometro
|
|||
var temp = new Date() |
|||
temp.setSeconds(this.cronometro_discurso.getSeconds() + cronometroStart[0]) |
|||
var res = new Date(temp - this.cronometro_discurso) |
|||
this.cronometro_discurso = this.formatTime(res) |
|||
|
|||
temp = new Date() |
|||
temp.setSeconds(this.cronometro_aparte.getSeconds() + cronometroStart[1]) |
|||
res = new Date(temp - this.cronometro_aparte) |
|||
this.cronometro_aparte = this.formatTime(res) |
|||
|
|||
temp = new Date() |
|||
temp.setSeconds(this.cronometro_ordem.getSeconds() + cronometroStart[2]) |
|||
res = new Date(temp - this.cronometro_ordem) |
|||
this.cronometro_ordem = this.formatTime(res) |
|||
|
|||
temp = new Date() |
|||
temp.setSeconds(this.cronometro_consideracoes.getSeconds() + cronometroStart[3]) |
|||
res = new Date(temp - this.cronometro_consideracoes) |
|||
this.cronometro_consideracoes = this.formatTime(res) |
|||
} |
|||
}.bind(this)) |
|||
}, |
|||
formatTime (time) { |
|||
var tempo = '00:' + time.getMinutes().toLocaleString('en-US', { |
|||
minimumIntegerDigits: 2, |
|||
useGrouping: false |
|||
}) + ':' + time.getSeconds().toLocaleString('en-US', { |
|||
minimumIntegerDigits: 2, |
|||
useGrouping: false |
|||
}) |
|||
return tempo |
|||
}, |
|||
stop: function stop (crono) { |
|||
if (crono === 5) { |
|||
audioAlertFinish.play() |
|||
} |
|||
|
|||
this.running = 0 |
|||
clearInterval(this.started) |
|||
this.stopped = setInterval(() => { |
|||
this.timeStopped() |
|||
}, 100) |
|||
}, |
|||
timeStopped () { |
|||
timeEnd.setMilliseconds(timeEnd.getMilliseconds() + 100) |
|||
}, |
|||
reset: function reset () { |
|||
this.running = 0 |
|||
time = null |
|||
clearInterval(this.started) |
|||
clearInterval(this.stopped) |
|||
}, |
|||
clockRunning (crono) { |
|||
var now = new Date() |
|||
time = new Date(timeEnd - now) |
|||
|
|||
// Definir propriamento o tempo
|
|||
time.setHours(timeEnd.getHours() - now.getHours()) |
|||
|
|||
if (timeEnd > now) { |
|||
if (crono === 1) { |
|||
this.cronometro_discurso = this.formatTime(time) |
|||
} else if (crono === 2) { |
|||
this.cronometro_aparte = this.formatTime(time) |
|||
} else if (crono === 3) { |
|||
this.cronometro_ordem = this.formatTime(time) |
|||
} else { |
|||
this.cronometro_consideracoes = this.formatTime(time) |
|||
} |
|||
} else { |
|||
audioAlertFinish.play() |
|||
this.alert = setTimeout(() => { |
|||
this.reset() |
|||
}, 5000) |
|||
} |
|||
}, |
|||
start: function startStopWatch (temp_crono) { |
|||
if (this.running !== 0) return |
|||
|
|||
crono = temp_crono |
|||
if (time === null) { |
|||
time = cronometroStart[crono - 1] |
|||
console.log(time) |
|||
timeEnd = new Date() |
|||
timeEnd.setSeconds(timeEnd.getSeconds() + time) |
|||
} else { |
|||
clearInterval(this.stopped) |
|||
} |
|||
this.running = crono |
|||
|
|||
this.started = setInterval(() => { |
|||
this.clockRunning(crono) |
|||
}, 100) |
|||
}, |
|||
pollData () { |
|||
this.fetchData() |
|||
|
|||
this.polling = setInterval(() => { |
|||
console.info('Fetching data from backend') |
|||
this.fetchData() |
|||
}, 100) |
|||
} |
|||
}, |
|||
beforeDestroy () { |
|||
console.info('Destroying polling.') |
|||
clearInterval(this.polling) |
|||
}, |
|||
created () { |
|||
console.info('Start polling data...') |
|||
this.pollData() |
|||
} |
|||
}) |
|||
|
@ -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 |
@ -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) |
@ -1,495 +1,231 @@ |
|||
{% load i18n %} |
|||
{% load common_tags %} |
|||
{% load staticfiles %} |
|||
|
|||
{% load render_bundle 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 gt IE 8]><!--> |
|||
<html lang="pt-br"> |
|||
<!--<![endif]--> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<!-- TODO: does it need this head_title here? --> |
|||
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title> |
|||
|
|||
{% block webpack_loader_css %} |
|||
{% render_chunk_vendors 'css' %} |
|||
{% render_bundle 'global' 'css' %} |
|||
{% render_bundle 'painel' 'css' %} |
|||
{% endblock webpack_loader_css %} |
|||
|
|||
|
|||
<style type="text/css"> |
|||
html, body { |
|||
max-width: 100%; |
|||
overflow-x: hidden; |
|||
} |
|||
@media screen { |
|||
ul, li { |
|||
list-style-type: none; |
|||
} |
|||
} |
|||
</style> |
|||
</head> |
|||
<body class="painel-principal"> |
|||
<audio type="hidden" id="audio" src="{% webpack_static 'audio/ring.mp3' %}"></audio> |
|||
|
|||
<div class="d-flex justify-content-center"> |
|||
<h1 id="sessao_plenaria" class="title text-title"></h1> |
|||
</div> |
|||
<div class="row "> |
|||
<div class="col text-center"> |
|||
<span id="sessao_plenaria_data" class="text-value"></span> |
|||
</div> |
|||
<div class="col text-center"> |
|||
<span id="sessao_plenaria_hora_inicio" class="text-value"></span> |
|||
<!--<![endif]--> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<!-- TODO: does it need this head_title here? --> |
|||
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title> |
|||
|
|||
{% block webpack_loader_css %} |
|||
{% render_chunk_vendors 'css' %} |
|||
{% render_bundle 'global' 'css' %} |
|||
{% render_bundle 'painel' 'css' %} |
|||
{% endblock webpack_loader_css %} |
|||
|
|||
<style type="text/css"> |
|||
html, body { |
|||
max-width: 100%; |
|||
overflow-x: hidden; |
|||
} |
|||
@media screen { |
|||
ul, li { |
|||
list-style-type: none; |
|||
} |
|||
} |
|||
</style> |
|||
</head> |
|||
<body class="painel-principal"> |
|||
<div id="app-painel"> <!-- app painel --> |
|||
<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> |
|||
|
|||
<div class="row justify-content-center"> |
|||
<div class="col-1"> |
|||
<img src="" id="logo-painel" class="logo-painel" alt=""/> |
|||
|
|||
<div class="d-flex justify-content-center"> |
|||
<h1 id="sessao_plenaria" class="title text-title">[[ sessao_plenaria ]]</h1> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="row justify-content-center"> |
|||
<h2 class="text-danger"><span id="message"></span></h2> |
|||
</div> |
|||
|
|||
<div class="row"> |
|||
<div class="col text-center"><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> |
|||
|
|||
|
|||
<div class=""> |
|||
<div class="d-flex justify-content-start"> |
|||
<div class="col-md-4"> |
|||
<div class="text-center painel"> |
|||
<h2 class="text-subtitle">Parlamentares</h2> |
|||
<span id="parlamentares" class="text-value text-center"></span> |
|||
</div> |
|||
|
|||
<div class="row"> |
|||
<div class="col-md-5 text-right"> |
|||
<span id="sessao_plenaria_data" class="text-value">[[ sessao_plenaria_data ]]</span> |
|||
</div> |
|||
<div class="col-md-7 text-center"> |
|||
<span id="sessao_plenaria_hora_inicio" class="text-value">[[ sessao_plenaria_hora_inicio ]]</span> |
|||
</div> |
|||
<div class="d-flex col-md-8 painels"> |
|||
<div class="col-md-6 text-center painel" id="aparecer_oradores"> |
|||
<h2 class="text-subtitle">Oradores</h2> |
|||
<span id="orador"></span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="row justify-content-center" style="margin-bottom: 50px; margin-top: 50px;"> |
|||
<h2 class="text-danger"><span v-if="!painel_aberto">PAINEL ENCONTRA-SE FECHADO</span></h2> |
|||
</div> |
|||
|
|||
<div class="row"> |
|||
<div class="col-md-5 text-right"><span class="text-value data-hora" id="date"></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 class="col-md-6 text-center painel"> |
|||
<h2 class="text-subtitle">Cronômetros</h2> |
|||
<div class="text-value"> |
|||
Discurso: <span id="cronometro_discurso"></span><br> |
|||
Aparte: <span id="cronometro_aparte"></span><br> |
|||
Questão de Ordem: <span id="cronometro_ordem"></span><br> |
|||
Considerações Finais: <span id="cronometro_consideracoes"></span> |
|||
<div style="margin-bottom: 50px;"> <!-- outer div --> |
|||
<div class="d-flex justify-content-start"> |
|||
<div class="col-md-2"></div> |
|||
<div class="col-md-3"> <!-- Lista parlamentares --> |
|||
<div class="text center painel"> |
|||
<h2 class="text-subtitle">Parlamentares</h2> |
|||
<div v-if="painel_aberto"> <!-- v-if --> |
|||
<table> |
|||
<tbody v-for="p in presentes"> |
|||
<tr> |
|||
<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> |
|||
<div v-if="painel_aberto"> <!-- v-if Lista de oradores--> |
|||
<table> |
|||
<tbody v-for="o in oradores"> |
|||
<tr class="d-flex justify-content-center"> |
|||
<td style="padding-right: 20px; color: white; font-size: 20px;"> |
|||
[[ o.numero]]º  [[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 class="col-md-6 text-center painel" id="resultado_votacao_div"> |
|||
<h2 class="text-subtitle">Resultado</h2> |
|||
<span id="votacao" class="text-value"></span> |
|||
<h2><span id="resultado_votacao" lass="text-title"></span> |
|||
</div> |
|||
|
|||
<div class="col-md-6 text-center painel" id="obs_materia_div"> |
|||
<h2 class="text-subtitle" id="mat_em_votacao">Matéria em Votação</h2> |
|||
<span id="materia_legislativa_texto" class="text-value"></span> |
|||
<br> |
|||
<span id="materia_legislativa_ementa" class="text-value"></span> |
|||
<br> |
|||
<span id="observacao_materia" class="text-value"></span> |
|||
</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> |
|||
<div class="text-value"> |
|||
Discurso:<span id='discurso' style="margin-right: 20px;">[[ cronometro_discurso ]]</span><span v-if="painel_aberto"> |
|||
<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> |
|||
<span v-else><button class="btn btn-danger" v-on:click="stop(1)">Parar</button></span> |
|||
</span><br> |
|||
|
|||
Aparte:<span id='discurso' style="margin-right: 20px;">[[ cronometro_aparte ]]</span><span v-if="painel_aberto"> |
|||
<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 v-else><button class="btn btn-danger" v-on:click="stop(2)">Parar</button></span> |
|||
</span><br> |
|||
|
|||
Questão de Ordem: <span id='discurso' style="margin-right: 20px;">[[ cronometro_ordem ]]</span><span v-if="painel_aberto"> |
|||
<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 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 class="col-md-6 text-center painel" id="tema_solene_div" style="display: none"> |
|||
<h2 class="text-subtitle">Tema da Sessão Solene</h2> |
|||
<span id="sessao_solene_tema" class="text-value"></span> |
|||
</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> |
|||
<span id="materia_legislativa_ementa" class="text-value">[[ materia_legislativa_ementa ]]</span> |
|||
<br> |
|||
<span id="observacao_materia" class="text-value"> [[ observacao_materia ]]</span> |
|||
</div> |
|||
<div v-else> |
|||
<span class="text-value">Não há nenhuma matéria votada ou para votação.</span> |
|||
</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 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> |
|||
</div> |
|||
</body> |
|||
|
|||
{% block webpack_loader_js %} |
|||
{% render_chunk_vendors 'js' %} |
|||
{% render_bundle 'global' 'js' %} |
|||
{% render_bundle 'painel' 'js' %} |
|||
{% endblock webpack_loader_js %} |
|||
</div> <!-- row --> |
|||
</div> <!-- app painel --> |
|||
</body> |
|||
|
|||
{% block webpack_loader_chunks_js %} |
|||
{% endblock webpack_loader_chunks_js %} |
|||
{% block webpack_loader_js %} |
|||
{% render_chunk_vendors 'js' %} |
|||
{% render_bundle 'global' 'js' %} |
|||
{% render_bundle 'painel' 'js' %} |
|||
{% endblock webpack_loader_js %} |
|||
|
|||
<script type="text/javascript"> |
|||
{% block webpack_loader_chunks_js %} |
|||
{% endblock webpack_loader_chunks_js %} |
|||
|
|||
<script type='text/javascript'> |
|||
$(document).ready(function() { |
|||
|
|||
// As constantes decisões sobre a existência ou não do horário de verão, |
|||
// assim como que data de início e termino do mesmo, fizeram com que fosse necessário |
|||
// substituir a chamada a Date() por um esquema mais elaborado, onde se |
|||
// recupera o offset do UTC (-3 GMT, no caso de Brasília) e seta-se |
|||
// manualmente. Esta informação vem do servidor, desta forma não ficamos |
|||
// na dependência da atualização de browser, pois tanto o Date() em JS |
|||
// quanto as libs python (django.utils.timezone, datetime, pytz, etc) |
|||
// lêem do tzdata, que precisa ser atualizado toda vez que o governo |
|||
// brasileiro modifica alguma coisa relacionada ao horário de verão. |
|||
// Recuperando essa informação do servidor só teremos que atualizar as |
|||
// libs tzdata (Linux) e pytz (Python) uma vez. Além disso, o uso da |
|||
// biblioteca moment.js é recomendada, pois ela trata data e hora |
|||
// melhor que o Date() do JS. |
|||
|
|||
$("#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 + 'º  ' + |
|||
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> |
|||
</html> |
|||
|
|||
// As constantes decisões sobre a existência ou não do horário de verão, |
|||
// assim como que data de início e termino do mesmo, fizeram com que fosse necessário |
|||
// substituir a chamada a Date() por um esquema mais elaborado, onde se |
|||
// recupera o offset do UTC (-3 GMT, no caso de Brasília) e seta-se |
|||
// manualmente. Esta informação vem do servidor, desta forma não ficamos |
|||
// na dependência da atualização de browser, pois tanto o Date() em JS |
|||
// quanto as libs python (django.utils.timezone, datetime, pytz, etc) |
|||
// lêem do tzdata, que precisa ser atualizado toda vez que o governo |
|||
// brasileiro modifica alguma coisa relacionada ao horário de verão. |
|||
// Recuperando essa informação do servidor só teremos que atualizar as |
|||
// libs tzdata (Linux) e pytz (Python) uma vez. Além disso, o uso da |
|||
// biblioteca moment.js é recomendada, pois ela trata data e hora |
|||
// melhor que o Date() do JS. |
|||
|
|||
$("#date").append(moment().format("DD/MM/YY")); |
|||
|
|||
}) |
|||
</script> |
|||
</html> |
Loading…
Reference in new issue