From 3c6509828505b7d950580abff6f457f417ab2711 Mon Sep 17 00:00:00 2001 From: Cesar Augusto de Carvalho Date: Tue, 10 Dec 2019 13:14:44 -0300 Subject: [PATCH] =?UTF-8?q?Fix=20#2186=20-=20Lista=20de=20inscri=C3=A7?= =?UTF-8?q?=C3=A3o=20para=20discurso=20(#2996)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/api/views.py | 34 ++- sapl/base/tests/test_login.py | 8 +- sapl/painel/urls.py | 9 +- sapl/painel/views.py | 72 ++++- sapl/rules/map_rules.py | 4 + sapl/sessao/forms.py | 62 +++- .../migrations/0052_auto_20191209_0828.py | 91 ++++++ sapl/sessao/models.py | 69 ++++- sapl/sessao/urls.py | 28 +- sapl/sessao/views.py | 109 ++++++- sapl/templates/menu_tabelas_auxiliares.yaml | 3 + sapl/templates/painel/index.html | 34 +-- sapl/templates/painel/painel_discurso.html | 289 ++++++++++++++++++ .../sessao/cronometrolista_form.html | 2 + sapl/templates/sessao/layouts.yaml | 9 + sapl/templates/sessao/lista_discurso.html | 153 ++++++++++ sapl/templates/sessao/subnav.yaml | 13 +- .../sessao/tipolistadiscurso_detail.html | 57 ++++ sapl/webpack-stats.json | 2 +- 19 files changed, 1005 insertions(+), 43 deletions(-) create mode 100644 sapl/sessao/migrations/0052_auto_20191209_0828.py create mode 100644 sapl/templates/painel/painel_discurso.html create mode 100644 sapl/templates/sessao/cronometrolista_form.html create mode 100644 sapl/templates/sessao/lista_discurso.html create mode 100644 sapl/templates/sessao/tipolistadiscurso_detail.html diff --git a/sapl/api/views.py b/sapl/api/views.py index b9f0a2403..7a7579a01 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -3,6 +3,7 @@ import logging from django import apps from django.conf import settings from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import FieldError from django.db.models import Q from django.db.models.fields.files import FileField from django.utils.decorators import classonlymethod @@ -26,10 +27,11 @@ 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.painel.models import Cronometro from sapl.parlamentares.models import Parlamentar from sapl.protocoloadm.models import DocumentoAdministrativo,\ DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado -from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao +from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao, SessaoPlenariaPresenca from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria @@ -514,7 +516,6 @@ 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) @@ -533,6 +534,21 @@ class _SessaoPlenariaViewSet: return Response(serializer.data) + @action(detail=True, methods=['GET']) + def parlamentares_presentes(self, request, *args, **kwargs): + sessao = self.get_object() + parlamentares = Parlamentar.objects.filter(sessaoplenariapresenca__sessao_plenaria=sessao) + + page = self.paginate_queryset(parlamentares) + if page is not None: + serializer = SaplApiViewSetConstrutor.get_class_for_model( + Parlamentar).serializer_class(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(page, many=True) + return Response(serializer.data) + + @customize(NormaJuridica) class _NormaJuridicaViewset: @@ -540,3 +556,17 @@ class _NormaJuridicaViewset: def destaques(self, request, *args, **kwargs): self.queryset = self.get_queryset().filter(norma_de_destaque=True) return self.list(request, *args, **kwargs) + + +@customize(Cronometro) +class _CronometroViewSet: + + def get_queryset(self, *args, **kwargs): + qs = super().get_queryset() + + try: + filter_condition = {k:v[0] for (k,v) in self.request.GET.items()} + qs = qs.filter(**filter_condition) + except FieldError as e: + pass + return qs \ No newline at end of file diff --git a/sapl/base/tests/test_login.py b/sapl/base/tests/test_login.py index 6c6a75cb8..bbe9f0695 100755 --- a/sapl/base/tests/test_login.py +++ b/sapl/base/tests/test_login.py @@ -12,9 +12,13 @@ def user(): def test_login_aparece_na_barra_para_usuario_nao_logado(client): + import re response = client.get('/') - assert '' in str( - response.content) + content = str(response.content) + query = '' + + result = re.findall(query, content) + assert len(result) > 0 def test_username_do_usuario_logado_aparece_na_barra(client, user): diff --git a/sapl/painel/urls.py b/sapl/painel/urls.py index 3186e16e6..dfb0e30f3 100644 --- a/sapl/painel/urls.py +++ b/sapl/painel/urls.py @@ -4,7 +4,8 @@ from .apps import AppConfig from .views import (cronometro_painel, get_dados_painel, painel_mensagem_view, painel_parlamentar_view, painel_view, painel_votacao_view, switch_painel, verifica_painel, votante_view, CronometroPainelCrud, - PainelConfigCrud,ordena_cronometro, painel_parcial_view) + PainelConfigCrud, ordena_cronometro, painel_parcial_view, painel_discurso_view, + get_dados_painel_discurso) app_name = AppConfig.name @@ -12,6 +13,7 @@ urlpatterns = [ url(r'^painel-principal/(?P\d+)$', painel_view, name="painel_principal"), url(r'^painel/(?P\d+)/dados$', get_dados_painel, name='dados_painel'), + url(r'^painel/mensagem$', painel_mensagem_view, name="painel_mensagem"), url(r'^painel/parlamentar$', painel_parlamentar_view, name='painel_parlamentar'), @@ -31,4 +33,9 @@ urlpatterns = [ url(r'^painel-parcial/(?P\d+)/(?P\d+)/$', painel_parcial_view, name="painel_parcial"), + + url(r'^painel-discurso/(?P\d+)/(?P\d+)/$', painel_discurso_view, + name="painel_discurso"), + url(r'^painel/(?P\d+)/(?P\d+)/dados-discurso$', get_dados_painel_discurso, + name='dados_painel_discurso'), ] diff --git a/sapl/painel/views.py b/sapl/painel/views.py index 207bbe0c3..7424ff0f5 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -1,6 +1,7 @@ import html import json import logging +import os from django.contrib import messages from django.contrib.auth.decorators import user_passes_test @@ -21,7 +22,8 @@ from sapl.parlamentares.models import Legislatura, Parlamentar, Votante from sapl.sessao.models import (ExpedienteMateria, OradorExpediente, OrdemDia, PresencaOrdemDia, RegistroVotacao, SessaoPlenaria, SessaoPlenariaPresenca, - VotoParlamentar, RegistroLeitura) + VotoParlamentar, CronometroLista, ListaDiscurso, + ParlamentarLista, RegistroLeitura) from sapl.utils import filiacao_data, get_client_ip, sort_lista_chave from .forms import CronometroForm, ConfiguracoesPainelForm @@ -759,3 +761,71 @@ def get_dados_painel(request, pk): # Retorna que não há nenhuma matéria já votada ou aberta return response_nenhuma_materia(get_presentes(pk, response, None)) + +@user_passes_test(check_permission) +def painel_discurso_view(request, sessao_pk, lista_pk): + cronometros_ids = CronometroLista.objects.filter(tipo_lista_id=lista_pk).values_list('cronometro', flat=True) + cronometros = Cronometro.objects.filter(id__in=cronometros_ids) + lista = ListaDiscurso.objects.get(tipo_id=lista_pk, sessao_plenaria_id=sessao_pk) + + context = { + 'head_title': str(_('Painel de Discurso')), + 'sessao_id': sessao_pk, + 'lista': lista, + 'cronometros': cronometros, + 'casa': CasaLegislativa.objects.last(), + 'painel_config': PainelConfig.objects.first(), + } + return render(request, 'painel/painel_discurso.html', context) + + +@user_passes_test(check_permission) +def get_dados_painel_discurso(request, pk, lista_pk): + sessao = SessaoPlenaria.objects.get(id=pk) + + casa = CasaLegislativa.objects.first() + + app_config = ConfiguracoesAplicacao.objects.first() + + brasao = None + if casa and app_config and (bool(casa.logotipo)): + brasao = casa.logotipo.url \ + if app_config.mostrar_brasao_painel else None + + CRONOMETRO_STATUS = { + 'I': 'start', + 'R': 'reset', + 'S': 'stop', + 'C': 'increment' + } + + cronometros = Cronometro.objects.filter(cronometrolista__tipo_lista_id=lista_pk) + + dict_status_cronometros = dict(cronometros.order_by('ordenacao').values_list('id', 'status')) + + for key, value in dict_status_cronometros.items(): + dict_status_cronometros[key] = CRONOMETRO_STATUS[dict_status_cronometros[key]] + + dict_duracao_cronometros = dict(cronometros.values_list('id', 'duracao_cronometro')) + + for key, value in dict_duracao_cronometros.items(): + dict_duracao_cronometros[key] = value.seconds + + lista = ListaDiscurso.objects.get(tipo_id=lista_pk, sessao_plenaria_id=pk) + orador = lista.orador_atual + oradores = ParlamentarLista.objects.filter(lista=lista).order_by('ordenacao').values_list('parlamentar__nome_parlamentar', flat=True) + + response = { + 'sessao_plenaria': str(sessao), + 'sessao_plenaria_data': sessao.data_inicio.strftime('%d/%m/%Y'), + 'sessao_plenaria_hora_inicio': sessao.hora_inicio, + 'cronometros': dict_status_cronometros, + 'duracao_cronometros': dict_duracao_cronometros, + 'sessao_finalizada': sessao.finalizada, + 'brasao': brasao, + 'orador': orador.nome_parlamentar if orador else '', + 'orador_img': orador.fotografia.url if orador and os.path.isfile(orador.fotografia.path) else None, + 'oradores': list(oradores) + } + + return JsonResponse(response) diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index 5d5028b33..f9a592594 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -188,6 +188,10 @@ rules_group_sessao = { (sessao.JustificativaAusencia, __base__, __perms_publicas__), (sessao.RetiradaPauta, __base__, __perms_publicas__), (sessao.RegistroLeitura, __base__, __perms_publicas__), + (sessao.ListaDiscurso, __base__, __perms_publicas__), + (sessao.TipoListaDiscurso, __base__, __perms_publicas__), + (sessao.CronometroLista, __base__, __perms_publicas__), + (sessao.ParlamentarLista, __base__, __perms_publicas__), ] } diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index 8c8909071..967cf8729 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -8,7 +8,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import transaction from django.db.models import Q -from django.forms import ModelForm +from django.forms import ModelForm, widgets from django.forms.widgets import CheckboxSelectMultiple from django.utils.translation import ugettext_lazy as _ @@ -18,7 +18,12 @@ from sapl.crispy_layout_mixin import (form_actions, to_row, from sapl.materia.forms import MateriaLegislativaFilterSet from sapl.materia.models import (MateriaLegislativa, StatusTramitacao, TipoMateriaLegislativa) - +from sapl.painel.models import Cronometro +from sapl.parlamentares.models import Parlamentar, Mandato +from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES, + MateriaPesquisaOrderingFilter, autor_label, + autor_modal, timezone, choice_anos_com_sessaoplenaria, + FileFieldCheckMixin, verifica_afastamento_parlamentar) from sapl.parlamentares.models import Mandato, Parlamentar from sapl.utils import (autor_label, autor_modal, choice_anos_com_sessaoplenaria, @@ -26,14 +31,14 @@ from sapl.utils import (autor_label, autor_modal, MateriaPesquisaOrderingFilter, RANGE_DIAS_MES, RANGE_MESES, timezone, validar_arquivo) - from .models import (ExpedienteMateria, JustificativaAusencia, OcorrenciaSessao, Orador, OradorExpediente, OradorOrdemDia, OrdemDia, ORDENACAO_RESUMO, PresencaOrdemDia, RegistroLeitura, ResumoOrdenacao, RetiradaPauta, SessaoPlenaria, SessaoPlenariaPresenca, - TipoResultadoVotacao, TipoRetiradaPauta) + TipoResultadoVotacao, TipoRetiradaPauta, + CronometroLista) MES_CHOICES = RANGE_MESES DIA_CHOICES = RANGE_DIAS_MES @@ -890,3 +895,52 @@ class OrdemExpedienteLeituraForm(forms.ModelForm): form_actions(more=actions), ) ) + + +class CronometroListaForm(ModelForm): + + cronometro = forms.ModelChoiceField( + queryset=Cronometro.objects.all(), + label="Cronômetro" + ) + + nome_lista = forms.CharField( + label='Lista de Discussão', + widget=widgets.TextInput(attrs={'readonly': 'readonly'}) + ) + + class Meta: + model = CronometroLista + exclude = [] + widgets = { + 'tipo_lista': forms.HiddenInput(), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + row1 = to_row( + [('nome_lista', 6),('cronometro', 6),]) + row2 = to_row( + [('tipo_lista', 6)] + ) + + actions = [HTML('Cancelar')] + + self.helper = SaplFormHelper() + self.helper.layout = Layout( + Fieldset(_('Vincular Cronômetro à Lista de Discussão'), + row1, row2, + HTML(" "), + form_actions(more=actions) + ) + ) + + def save(self): + cd = self.cleaned_data + cronometro = cd['cronometro'] + tipo_lista = cd['tipo_lista'] + cronometro_lista = CronometroLista.objects.create(cronometro=cronometro, tipo_lista=tipo_lista) + + return cronometro_lista diff --git a/sapl/sessao/migrations/0052_auto_20191209_0828.py b/sapl/sessao/migrations/0052_auto_20191209_0828.py new file mode 100644 index 000000000..90cd6e933 --- /dev/null +++ b/sapl/sessao/migrations/0052_auto_20191209_0828.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-12-09 11:28 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0035_merge_20190802_0954'), + ('painel', '0011_cronometro_last_stop_duration'), + ('sessao', '0051_merge_20191209_0910'), + ] + + operations = [ + migrations.CreateModel( + name='CronometroLista', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cronometro', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='painel.Cronometro')), + ], + options={ + 'verbose_name': 'Tipo de Lista de Discurso - Cronômetro', + 'verbose_name_plural': 'Tipo de Listas de Discurso - Cronômetros', + 'ordering': ['tipo_lista', 'cronometro'], + }, + ), + migrations.CreateModel( + name='ListaDiscurso', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('orador_atual', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Parlamentar')), + ('sessao_plenaria', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sessao.SessaoPlenaria', verbose_name='Sessão Plenária')), + ], + options={ + 'verbose_name': 'Lista de Discurso', + 'verbose_name_plural': 'Listas de Discurso', + 'ordering': ['tipo', 'sessao_plenaria'], + }, + ), + migrations.CreateModel( + name='ParlamentarLista', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ordenacao', models.PositiveIntegerField(blank=True, null=True, verbose_name='Ordenação')), + ('lista', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sessao.ListaDiscurso')), + ('parlamentar', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Parlamentar')), + ], + options={ + 'verbose_name': 'Lista de Discurso - Parlamentar', + 'verbose_name_plural': 'Listas de Discurso - Parlamentares', + 'ordering': ['lista', 'parlamentar', 'ordenacao'], + }, + ), + migrations.CreateModel( + name='TipoListaDiscurso', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nome', models.CharField(max_length=100, verbose_name='Tipo')), + ], + options={ + 'verbose_name': 'Tipo de Lista de Discurso', + 'verbose_name_plural': 'Tipos de Lista de Discurso', + 'ordering': ['nome'], + }, + ), + migrations.AddField( + model_name='listadiscurso', + name='tipo', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sessao.TipoListaDiscurso', verbose_name='Tipo de Lista de Discurso'), + ), + migrations.AddField( + model_name='cronometrolista', + name='tipo_lista', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sessao.TipoListaDiscurso'), + ), + migrations.AlterUniqueTogether( + name='parlamentarlista', + unique_together=set([('lista', 'parlamentar')]), + ), + migrations.AlterUniqueTogether( + name='listadiscurso', + unique_together=set([('tipo', 'sessao_plenaria')]), + ), + migrations.AlterUniqueTogether( + name='cronometrolista', + unique_together=set([('tipo_lista', 'cronometro')]), + ), + ] diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index 5c9c47bd3..b4c88c9aa 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -10,6 +10,7 @@ import reversion from sapl.base.models import Autor from sapl.materia.models import MateriaLegislativa +from sapl.painel.models import Cronometro from sapl.parlamentares.models import (CargoMesa, Legislatura, Parlamentar, Partido, SessaoLegislativa) from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation, @@ -873,4 +874,70 @@ class RegistroLeitura(models.Model): raise ValidationError( 'RegistroLeitura deve ter exatamente um dos campos ' 'ordem ou expediente preenchido. Ambos estão preenchidos: ' - '{}, {}'. format(self.ordem, self.expediente)) \ No newline at end of file + '{}, {}'. format(self.ordem, self.expediente)) +class TipoListaDiscurso(models.Model): + nome = models.CharField(max_length=100, verbose_name=_('Tipo')) + + class Meta: + verbose_name = _('Tipo de Lista de Discurso') + verbose_name_plural = _('Tipos de Lista de Discurso') + ordering = ['nome'] + + def __str__(self): + return self.nome + + +@reversion.register() +class ListaDiscurso(models.Model): + tipo = models.ForeignKey(TipoListaDiscurso, + on_delete=models.PROTECT, + verbose_name=_('Tipo de Lista de Discurso')) + sessao_plenaria = models.ForeignKey(SessaoPlenaria, + on_delete=models.PROTECT, + verbose_name=_('Sessão Plenária')) + orador_atual = models.ForeignKey(Parlamentar, + on_delete=models.PROTECT, + blank=True, + null=True) + + class Meta: + verbose_name = _('Lista de Discurso') + verbose_name_plural = _('Listas de Discurso') + ordering = ['tipo', 'sessao_plenaria'] + unique_together = (('tipo', 'sessao_plenaria'),) + + def __str__(self): + return str(self.tipo) + ' - ' + str(self.sessao_plenaria) + +@reversion.register() +class CronometroLista(models.Model): + cronometro = models.ForeignKey(Cronometro, on_delete=models.PROTECT) + tipo_lista = models.ForeignKey(TipoListaDiscurso, on_delete=models.PROTECT) + + class Meta: + verbose_name = _('Tipo de Lista de Discurso - Cronômetro') + verbose_name_plural = _('Tipo de Listas de Discurso - Cronômetros') + ordering = ['tipo_lista', 'cronometro'] + unique_together = (('tipo_lista', 'cronometro'),) + + def __str__(self): + return str(self.tipo_lista) + ' - ' + str(self.cronometro) + +@reversion.register() +class ParlamentarLista(models.Model): + parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT) + lista = models.ForeignKey(ListaDiscurso, on_delete=models.PROTECT) + ordenacao = models.PositiveIntegerField( + blank=True, + null=True, + verbose_name=_("Ordenação") + ) + + class Meta: + verbose_name = _('Lista de Discurso - Parlamentar') + verbose_name_plural = _('Listas de Discurso - Parlamentares') + ordering = ['lista', 'parlamentar', 'ordenacao'] + unique_together = (('lista', 'parlamentar'),) + + def __str__(self): + return str(self.lista) + ' - ' + str(self.parlamentar) diff --git a/sapl/sessao/urls.py b/sapl/sessao/urls.py index 0480c4416..d2415d405 100644 --- a/sapl/sessao/urls.py +++ b/sapl/sessao/urls.py @@ -38,7 +38,13 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente, voto_nominal_parlamentar, ExpedienteLeituraView, OrdemDiaLeituraView, - retirar_leitura) + retirar_leitura, + ListaDiscursoView, + TipoListaDiscursoCrud, + CronometroListaFormView, + salva_listadiscurso_parlamentares, + salva_orador_listadiscurso, + get_orador_listadiscurso) from .apps import AppConfig @@ -102,6 +108,10 @@ urlpatterns = [ include(TipoJustificativaCrud.get_urls())), url(r'^sistema/sessao-plenaria/tipo-retirada-pauta/', include(TipoRetiradaPautaCrud.get_urls())), + url(r'^sistema/sessao-plenaria/tipo-lista-discurso/', + include(TipoListaDiscursoCrud.get_urls())), + + url(r'^sistema/resumo-ordenacao/', resumo_ordenacao, name='resumo_ordenacao'), @@ -202,4 +212,20 @@ urlpatterns = [ url(r'^sessao/(?P\d+)/(?P\d+)/(?P\d+)/retirar-leitura$', retirar_leitura, name='retirar_leitura'), + + # Lista de Discurso + url(r'^sessao/(?P\d+)/lista-discurso/', + ListaDiscursoView.as_view(), + name='lista_discurso'), + + url(r'^sistema/sessao-plenaria/lista-discurso/(?P\d+)/cronometro/create', + CronometroListaFormView.as_view(), name='cronometrolista_form'), + + url(r'^sistema/salva-listadiscurso-parlamentares/$', + salva_listadiscurso_parlamentares, name='salva_listadiscurso_parlamentares'), + url(r'^sistema/salva-orador-listadiscurso/$', + salva_orador_listadiscurso, name='salva_orador_listadiscurso'), + + url(r'^sistema/get-orador-lista/(?P\d+)/(?P\d+)/$', + get_orador_listadiscurso, name='get_orador_listadiscurso'), ] diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 5339eac10..0d1dced60 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -48,14 +48,15 @@ from .forms import (AdicionarVariasMateriasFilterSet, ExpedienteForm, PresencaForm, SessaoPlenariaFilterSet, SessaoPlenariaForm, VotacaoEditForm, VotacaoForm, VotacaoNominalForm, RetiradaPautaForm, OradorOrdemDiaForm, - OrdemExpedienteLeituraForm) + OrdemExpedienteLeituraForm, + CronometroListaForm) from .models import (CargoMesa, ExpedienteMateria, ExpedienteSessao, OcorrenciaSessao, IntegranteMesa, MateriaLegislativa, Orador, OradorExpediente, OrdemDia, PresencaOrdemDia, RegistroVotacao, ResumoOrdenacao, SessaoPlenaria, SessaoPlenariaPresenca, TipoExpediente, TipoResultadoVotacao, TipoSessaoPlenaria, VotoParlamentar, TipoRetiradaPauta, - RetiradaPauta, TipoJustificativa, JustificativaAusencia, OradorOrdemDia, - ORDENACAO_RESUMO, RegistroLeitura) + RetiradaPauta, TipoJustificativa, JustificativaAusencia, OradorOrdemDia, ORDENACAO_RESUMO, + RegistroLeitura, TipoListaDiscurso, ListaDiscurso, CronometroLista, ParlamentarLista) TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria') @@ -65,6 +66,19 @@ TipoResultadoVotacaoCrud = CrudAux.build( TipoRetiradaPautaCrud = CrudAux.build(TipoRetiradaPauta, 'tipo_retirada_pauta') +class TipoListaDiscursoCrud(CrudAux): + model = TipoListaDiscurso + + class DetailView(CrudAux.DetailView): + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + tipo_lista = context['object'] + cronometros_lista = CronometroLista.objects.filter(tipo_lista=tipo_lista) + context['cronometros_lista'] = cronometros_lista + return context + + def reordernar_materias_expediente(request, pk): expedientes = ExpedienteMateria.objects.filter( sessao_plenaria_id=pk @@ -4512,3 +4526,92 @@ def retirar_leitura(request, pk, iso, oid): ordem_expediente.save() return HttpResponseRedirect(succ_url) +class ListaDiscursoView(TemplateView): + template_name = 'sessao/lista_discurso.html' + + def get(self, request, *args, **kwargs): + return TemplateView.get(self, request, *args, **kwargs) + + + def get_context_data(self, **kwargs): + context = TemplateView.get_context_data(self, **kwargs) + sessao_pk = kwargs['pk'] + sessao = SessaoPlenaria.objects.get(id=sessao_pk) + + context.update({ + 'root_pk': sessao_pk, + 'subnav_template_name': 'sessao/subnav.yaml', + 'sessaoplenaria': sessao, + }) + return context + + +class CronometroListaFormView(FormView): + form_class = CronometroListaForm + template_name = 'sessao/cronometrolista_form.html' + + def get_initial(self): + initial = super().get_initial() + tipo_lista_pk = self.kwargs['pk'] + tipo_lista = TipoListaDiscurso.objects.get(id=tipo_lista_pk) + initial['tipo_lista'] = tipo_lista + initial['nome_lista'] = tipo_lista.nome + return initial + + def form_valid(self, form): + form.save() + return super().form_valid(form) + + @property + def cancel_url(self): + return reverse('sapl.sessao:tipolistadiscurso_detail', + kwargs={'pk': self.kwargs['pk']}) + + def get_success_url(self): + return reverse('sapl.sessao:tipolistadiscurso_detail', + kwargs={'pk': self.kwargs['pk']}) + + +def salva_listadiscurso_parlamentares(request): + parlamentares_selecionados_ids = request.GET.getlist('parlamentares_selecionados[]') + tipo_lista_id = request.GET.get('lista_selecionada') + sessao_id = request.GET.get('sessao_pk') + + if parlamentares_selecionados_ids: + lista = ListaDiscurso.objects.get_or_create(tipo_id=tipo_lista_id, sessao_plenaria_id=sessao_id)[0] + ParlamentarLista.objects.filter(lista=lista).delete() + parlamentares_lista = [] + for i,parlamentar_id in enumerate(parlamentares_selecionados_ids): + parlamentares_lista.append(ParlamentarLista(lista=lista, parlamentar_id=parlamentar_id, ordenacao=i+1)) + ParlamentarLista.objects.bulk_create(parlamentares_lista) + else: + ParlamentarLista.objects.filter(lista__tipo_id=tipo_lista_id, lista__sessao_plenaria_id=sessao_id).delete() + ListaDiscurso.objects.filter(tipo_id=tipo_lista_id, sessao_plenaria_id=sessao_id).delete() + return JsonResponse({}) + + +def salva_orador_listadiscurso(request): + tipo_id=request.GET.get('tipo_lista_pk') + sessao_id=request.GET.get('sessao_pk') + lista = ListaDiscurso.objects.get(tipo_id=tipo_id, sessao_plenaria_id=sessao_id) + orador_pk = request.GET.get('orador_pk') + if orador_pk != '-1': + lista.orador_atual = Parlamentar.objects.get(id=orador_pk) + else: + lista.orador_atual = None + lista.save() + return JsonResponse({}) + +def get_orador_listadiscurso(request, spk, tpk): + # Não foi utilizado .get para não quebrar caso não exista registro + lista = ListaDiscurso.objects.filter(tipo_id=tpk, sessao_plenaria_id=spk).first() + parlamentares = [] + orador_pk = -1 + orador_nome = '' + if lista: + if lista.orador_atual: + orador_pk = lista.orador_atual.pk + orador_nome = lista.orador_atual.nome_parlamentar + parlamentares = ParlamentarLista.objects.filter(lista=lista).order_by('ordenacao').values_list('parlamentar__id', 'parlamentar__nome_parlamentar') + return JsonResponse({'orador': (orador_pk, orador_nome), + 'parlamentares': list(parlamentares)}) diff --git a/sapl/templates/menu_tabelas_auxiliares.yaml b/sapl/templates/menu_tabelas_auxiliares.yaml index 8453dcb43..5bfc3dd42 100644 --- a/sapl/templates/menu_tabelas_auxiliares.yaml +++ b/sapl/templates/menu_tabelas_auxiliares.yaml @@ -173,6 +173,9 @@ - title: {% trans 'Tipo de Justificativa' %} url: sapl.sessao:tipojustificativa_list css_class: btn btn-link + - title: {% trans 'Tipo de Lista de Discurso' %} + url: sapl.sessao:tipolistadiscurso_list + css_class: btn btn-link - title: {% trans 'Módulo Painel' %} css_class: head_title children: diff --git a/sapl/templates/painel/index.html b/sapl/templates/painel/index.html index 46bebd614..e9f8d53f7 100644 --- a/sapl/templates/painel/index.html +++ b/sapl/templates/painel/index.html @@ -15,23 +15,12 @@ {% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %} - {% block webpack_loader_css %} - {% render_chunk_vendors 'css' %} - {% render_bundle 'global' 'css' %} - {% render_bundle 'painel' 'css' %} - {% endblock webpack_loader_css %} - - + {% block webpack_loader_css %} + {% render_chunk_vendors 'css' %} + {% render_bundle 'global' 'css' %} + {% render_bundle 'painel' 'css' %} + {% endblock webpack_loader_css %} + @@ -72,12 +61,12 @@
{% if exibicao.parlamentares %} -
-
-

Parlamentares

- +
+
+

Parlamentares

+ +
-
{% endif %}
@@ -288,7 +277,6 @@ tmp = tempo_disparo_antecedencia.split(":"); tempo_disparo_antecedencia = checkTime(parseInt(tmp[0])) + ":" + checkTime(parseInt(tmp[1])) + ":" + checkTime(parseInt(tmp[2])); - var counter = 1; (function poll() { $.ajax({ url: "{% url 'sapl.painel:dados_painel' sessao_id %}", diff --git a/sapl/templates/painel/painel_discurso.html b/sapl/templates/painel/painel_discurso.html new file mode 100644 index 000000000..e7197c5ae --- /dev/null +++ b/sapl/templates/painel/painel_discurso.html @@ -0,0 +1,289 @@ +{% load i18n %} +{% load common_tags %} + +{% load render_bundle from webpack_loader %} +{% load webpack_static from webpack_loader %} + + + + + + + + + + + {% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %} + + {% block webpack_loader_css %} + {% render_chunk_vendors 'css' %} + {% render_bundle 'global' 'css' %} + {% render_bundle 'painel' 'css' %} + {% endblock webpack_loader_css %} + + + + + + {% if painel_config.exibir_nome_casa %} +
+

{{casa.nome}}

+
+ {% endif %} +
+

+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+

+

{{lista.tipo}}

+
+ +
+
+
+
+ +
+ +
+
+

Parlamentares

+ +
+
+ +
+
+

Orador(a)

+ +

+
+
+
+ +
+

{% if cronometros|length == 1 %}Cronômetro{% elif cronometros|length > 1%}Cronômetros{% endif %}

+
+ {% for cronometro in cronometros %} + {{cronometro}}:
+ {% endfor %} +
+
+
+ + + {% block webpack_loader_js %} + {% render_chunk_vendors 'js' %} + {% render_bundle 'global' 'js' %} + {% render_bundle 'painel' 'js' %} + {% endblock webpack_loader_js %} + + {% block webpack_loader_chunks_js %} + {% endblock webpack_loader_chunks_js %} + + + + diff --git a/sapl/templates/sessao/cronometrolista_form.html b/sapl/templates/sessao/cronometrolista_form.html new file mode 100644 index 000000000..a7c7409c8 --- /dev/null +++ b/sapl/templates/sessao/cronometrolista_form.html @@ -0,0 +1,2 @@ +{% extends "crud/form.html" %} +{% load i18n %} \ No newline at end of file diff --git a/sapl/templates/sessao/layouts.yaml b/sapl/templates/sessao/layouts.yaml index 9cde05569..94e1b2f7e 100644 --- a/sapl/templates/sessao/layouts.yaml +++ b/sapl/templates/sessao/layouts.yaml @@ -103,6 +103,15 @@ TipoRetiradaPauta: {% trans 'Tipo Retirada Pauta'%}: - descricao +TipoListaDiscurso: + {% trans 'Tipo de Lista de Discurso' %}: + - nome + +ListaDiscurso: + {% trans 'Lista de Discurso' %}: + - tipo + - sessao_plenaria + RetiradaPauta: {% trans 'Retirada de Pauta' %}: - tipo_de_retirada materia diff --git a/sapl/templates/sessao/lista_discurso.html b/sapl/templates/sessao/lista_discurso.html new file mode 100644 index 000000000..4691b1d5f --- /dev/null +++ b/sapl/templates/sessao/lista_discurso.html @@ -0,0 +1,153 @@ +{% extends "base.html" %} +{% load i18n staticfiles menus %} + +{% load common_tags %} +{% load render_bundle from webpack_loader %} +{% load webpack_static from webpack_loader %} + + +{% block title %} +

+ Lista de Discurso ({{sessaoplenaria}}) +

+{% endblock %} + +{% block base_content %} + {{block.super}} +
+
+ +
+

Selecione um Tipo de Lista de Discurso

+ +
+ +

+ Não há + Tipos de Lista de Discurso criadas. Para criar, vá em + Sistema -> Tabelas Auxiliares -> Tipo de Lista de Discurso. +

+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+

Parlamentares

+
Aperte CTRL para selecionar mais de um
+
+ + +
+
+

Lista de Discurso

+

Arraste para ordenar
+
+ + {% comment %}

[[p.nome]]

{% endcomment %} + + +
+ [[index+1]]. [[element.nome]] +
+
+
+ +
+

+ +
+
+ +
+
+
+ +
+
+
+

Lista de Discurso

+

Clique uma vez para escolher o orador e uma segunda vez para desmarcar o orador.
+
+ + + [[index+1]]. [[element.nome]] + + +
+
+

Cronômetros

+

+
+ + + + + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +

+
+

+
+

+
+ +
+
+ +
+{% endblock %} + +{% block webpack_loader_js %} + {% render_chunk_vendors 'js' %} + {% render_bundle 'global' 'js' %} + {% render_bundle 'sessao' 'js' %} +{% endblock webpack_loader_js %} diff --git a/sapl/templates/sessao/subnav.yaml b/sapl/templates/sessao/subnav.yaml index 59a127ad3..6c2f056a6 100644 --- a/sapl/templates/sessao/subnav.yaml +++ b/sapl/templates/sessao/subnav.yaml @@ -41,10 +41,15 @@ url: votacao_bloco_ordemdia check_permission: sessao.add_sessaoplenaria -- title: {% trans 'Painel Eletrônico' %} - url: painel - {% if not 'painel_aberto'|get_config_attr %}check_permission: painel.list_painel{%endif%} - check_permission: painel.list_painel +- title: {% trans 'Operador' %} + children: + - title: {% trans 'Painel Eletrônico' %} + url: painel + {% if not 'painel_aberto'|get_config_attr %}check_permission: painel.list_painel{%endif%} + check_permission: painel.list_painel + - title: {% trans 'Lista de Discurso' %} + url: lista_discurso + check_permission: painel.list_painel - title: {% trans 'Resumo' %} children: diff --git a/sapl/templates/sessao/tipolistadiscurso_detail.html b/sapl/templates/sessao/tipolistadiscurso_detail.html new file mode 100644 index 000000000..5d096a8dd --- /dev/null +++ b/sapl/templates/sessao/tipolistadiscurso_detail.html @@ -0,0 +1,57 @@ +{% extends "crud/detail.html" %} +{% load i18n %} + +{% block sub_actions %} + {{block.super}} + +{% endblock sub_actions %} + +{% block detail_content %} + {{block.super}} + {% if cronometros_lista %} +

Cronômetros vinculados

+ + + + + + + + {% for cl in cronometros_lista %} + + + + + {% endfor %} + + + {% endif %} +{% endblock %} + +{% block extra_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/sapl/webpack-stats.json b/sapl/webpack-stats.json index d6d0af383..6bcaa322e 100644 --- a/sapl/webpack-stats.json +++ b/sapl/webpack-stats.json @@ -1 +1 @@ -{"status":"done","publicPath":"/static/sapl/frontend/","chunks":{"null":[{"name":"css/chunk-681dd124.3d0928b2.css","publicPath":"/static/sapl/frontend/css/chunk-681dd124.3d0928b2.css","path":"../sapl/sapl/static/sapl/frontend/css/chunk-681dd124.3d0928b2.css"},{"name":"js/chunk-681dd124.64f6fdc0.js","publicPath":"/static/sapl/frontend/js/chunk-681dd124.64f6fdc0.js","path":"../sapl/sapl/static/sapl/frontend/js/chunk-681dd124.64f6fdc0.js"},{"name":"css/chunk-681dd124.3d0928b2.css.map","publicPath":"/static/sapl/frontend/css/chunk-681dd124.3d0928b2.css.map","path":"../sapl/sapl/static/sapl/frontend/css/chunk-681dd124.3d0928b2.css.map"}],"chunk-vendors":[{"name":"css/chunk-vendors.7da9088b.css","publicPath":"/static/sapl/frontend/css/chunk-vendors.7da9088b.css","path":"../sapl/sapl/static/sapl/frontend/css/chunk-vendors.7da9088b.css"},{"name":"js/chunk-vendors.7a4ea0f9.js","publicPath":"/static/sapl/frontend/js/chunk-vendors.7a4ea0f9.js","path":"../sapl/sapl/static/sapl/frontend/js/chunk-vendors.7a4ea0f9.js"},{"name":"css/chunk-vendors.7da9088b.css.map","publicPath":"/static/sapl/frontend/css/chunk-vendors.7da9088b.css.map","path":"../sapl/sapl/static/sapl/frontend/css/chunk-vendors.7da9088b.css.map"}],"compilacao":[{"name":"css/compilacao.eff62463.css","publicPath":"/static/sapl/frontend/css/compilacao.eff62463.css","path":"../sapl/sapl/static/sapl/frontend/css/compilacao.eff62463.css"},{"name":"js/compilacao.77fbad73.js","publicPath":"/static/sapl/frontend/js/compilacao.77fbad73.js","path":"../sapl/sapl/static/sapl/frontend/js/compilacao.77fbad73.js"},{"name":"css/compilacao.eff62463.css.map","publicPath":"/static/sapl/frontend/css/compilacao.eff62463.css.map","path":"../sapl/sapl/static/sapl/frontend/css/compilacao.eff62463.css.map"}],"global":[{"name":"css/global.a77827ad.css","publicPath":"/static/sapl/frontend/css/global.a77827ad.css","path":"../sapl/sapl/static/sapl/frontend/css/global.a77827ad.css"},{"name":"js/global.e051cc2f.js","publicPath":"/static/sapl/frontend/js/global.e051cc2f.js","path":"../sapl/sapl/static/sapl/frontend/js/global.e051cc2f.js"},{"name":"css/global.a77827ad.css.map","publicPath":"/static/sapl/frontend/css/global.a77827ad.css.map","path":"../sapl/sapl/static/sapl/frontend/css/global.a77827ad.css.map"}],"online":[{"name":"css/online.b7332556.css","publicPath":"/static/sapl/frontend/css/online.b7332556.css","path":"../sapl/sapl/static/sapl/frontend/css/online.b7332556.css"},{"name":"js/online.9ef5423a.js","publicPath":"/static/sapl/frontend/js/online.9ef5423a.js","path":"../sapl/sapl/static/sapl/frontend/js/online.9ef5423a.js"},{"name":"css/online.b7332556.css.map","publicPath":"/static/sapl/frontend/css/online.b7332556.css.map","path":"../sapl/sapl/static/sapl/frontend/css/online.b7332556.css.map"}],"painel":[{"name":"css/painel.5d957a9b.css","publicPath":"/static/sapl/frontend/css/painel.5d957a9b.css","path":"../sapl/sapl/static/sapl/frontend/css/painel.5d957a9b.css"},{"name":"js/painel.35e9809a.js","publicPath":"/static/sapl/frontend/js/painel.35e9809a.js","path":"../sapl/sapl/static/sapl/frontend/js/painel.35e9809a.js"},{"name":"css/painel.5d957a9b.css.map","publicPath":"/static/sapl/frontend/css/painel.5d957a9b.css.map","path":"../sapl/sapl/static/sapl/frontend/css/painel.5d957a9b.css.map"}]}} \ No newline at end of file +{"status":"done","publicPath":"/static/sapl/frontend/","chunks":{"null":[{"name":"css/chunk-681dd124.3d0928b2.css","publicPath":"/static/sapl/frontend/css/chunk-681dd124.3d0928b2.css","path":"../sapl/sapl/static/sapl/frontend/css/chunk-681dd124.3d0928b2.css"},{"name":"js/chunk-681dd124.64f6fdc0.js","publicPath":"/static/sapl/frontend/js/chunk-681dd124.64f6fdc0.js","path":"../sapl/sapl/static/sapl/frontend/js/chunk-681dd124.64f6fdc0.js"},{"name":"css/chunk-681dd124.3d0928b2.css.map","publicPath":"/static/sapl/frontend/css/chunk-681dd124.3d0928b2.css.map","path":"../sapl/sapl/static/sapl/frontend/css/chunk-681dd124.3d0928b2.css.map"}],"chunk-vendors":[{"name":"css/chunk-vendors.7da9088b.css","publicPath":"/static/sapl/frontend/css/chunk-vendors.7da9088b.css","path":"../sapl/sapl/static/sapl/frontend/css/chunk-vendors.7da9088b.css"},{"name":"js/chunk-vendors.7a4ea0f9.js","publicPath":"/static/sapl/frontend/js/chunk-vendors.7a4ea0f9.js","path":"../sapl/sapl/static/sapl/frontend/js/chunk-vendors.7a4ea0f9.js"},{"name":"css/chunk-vendors.7da9088b.css.map","publicPath":"/static/sapl/frontend/css/chunk-vendors.7da9088b.css.map","path":"../sapl/sapl/static/sapl/frontend/css/chunk-vendors.7da9088b.css.map"}],"compilacao":[{"name":"css/compilacao.eff62463.css","publicPath":"/static/sapl/frontend/css/compilacao.eff62463.css","path":"../sapl/sapl/static/sapl/frontend/css/compilacao.eff62463.css"},{"name":"js/compilacao.77fbad73.js","publicPath":"/static/sapl/frontend/js/compilacao.77fbad73.js","path":"../sapl/sapl/static/sapl/frontend/js/compilacao.77fbad73.js"},{"name":"css/compilacao.eff62463.css.map","publicPath":"/static/sapl/frontend/css/compilacao.eff62463.css.map","path":"../sapl/sapl/static/sapl/frontend/css/compilacao.eff62463.css.map"}],"global":[{"name":"css/global.a77827ad.css","publicPath":"/static/sapl/frontend/css/global.a77827ad.css","path":"../sapl/sapl/static/sapl/frontend/css/global.a77827ad.css"},{"name":"js/global.e051cc2f.js","publicPath":"/static/sapl/frontend/js/global.e051cc2f.js","path":"../sapl/sapl/static/sapl/frontend/js/global.e051cc2f.js"},{"name":"css/global.a77827ad.css.map","publicPath":"/static/sapl/frontend/css/global.a77827ad.css.map","path":"../sapl/sapl/static/sapl/frontend/css/global.a77827ad.css.map"}],"online":[{"name":"css/online.b7332556.css","publicPath":"/static/sapl/frontend/css/online.b7332556.css","path":"../sapl/sapl/static/sapl/frontend/css/online.b7332556.css"},{"name":"js/online.9ef5423a.js","publicPath":"/static/sapl/frontend/js/online.9ef5423a.js","path":"../sapl/sapl/static/sapl/frontend/js/online.9ef5423a.js"},{"name":"css/online.b7332556.css.map","publicPath":"/static/sapl/frontend/css/online.b7332556.css.map","path":"../sapl/sapl/static/sapl/frontend/css/online.b7332556.css.map"}],"painel":[{"name":"css/painel.5d957a9b.css","publicPath":"/static/sapl/frontend/css/painel.5d957a9b.css","path":"../sapl/sapl/static/sapl/frontend/css/painel.5d957a9b.css"},{"name":"js/painel.35e9809a.js","publicPath":"/static/sapl/frontend/js/painel.35e9809a.js","path":"../sapl/sapl/static/sapl/frontend/js/painel.35e9809a.js"},{"name":"css/painel.5d957a9b.css.map","publicPath":"/static/sapl/frontend/css/painel.5d957a9b.css.map","path":"../sapl/sapl/static/sapl/frontend/css/painel.5d957a9b.css.map"}]}}