Browse Source

Fix #2186 - Lista de inscrição para discurso (#2996)

pull/3061/head
Cesar Augusto de Carvalho 5 years ago
committed by Edward
parent
commit
3c65098285
  1. 34
      sapl/api/views.py
  2. 8
      sapl/base/tests/test_login.py
  3. 9
      sapl/painel/urls.py
  4. 72
      sapl/painel/views.py
  5. 4
      sapl/rules/map_rules.py
  6. 62
      sapl/sessao/forms.py
  7. 91
      sapl/sessao/migrations/0052_auto_20191209_0828.py
  8. 67
      sapl/sessao/models.py
  9. 28
      sapl/sessao/urls.py
  10. 109
      sapl/sessao/views.py
  11. 3
      sapl/templates/menu_tabelas_auxiliares.yaml
  12. 12
      sapl/templates/painel/index.html
  13. 289
      sapl/templates/painel/painel_discurso.html
  14. 2
      sapl/templates/sessao/cronometrolista_form.html
  15. 9
      sapl/templates/sessao/layouts.yaml
  16. 153
      sapl/templates/sessao/lista_discurso.html
  17. 5
      sapl/templates/sessao/subnav.yaml
  18. 57
      sapl/templates/sessao/tipolistadiscurso_detail.html

34
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

8
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 '<a class="nav-link" href="/login/"><img src="/static/sapl/frontend/img/user.png"></a>' in str(
response.content)
content = str(response.content)
query = '<a class="nav-link" href="/login/"><img src=".*/user.png"></a>'
result = re.findall(query, content)
assert len(result) > 0
def test_username_do_usuario_logado_aparece_na_barra(client, user):

9
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<pk>\d+)$', painel_view,
name="painel_principal"),
url(r'^painel/(?P<pk>\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<pk>\d+)/(?P<opcoes>\d+)/$', painel_parcial_view,
name="painel_parcial"),
url(r'^painel-discurso/(?P<sessao_pk>\d+)/(?P<lista_pk>\d+)/$', painel_discurso_view,
name="painel_discurso"),
url(r'^painel/(?P<pk>\d+)/(?P<lista_pk>\d+)/dados-discurso$', get_dados_painel_discurso,
name='dados_painel_discurso'),
]

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

4
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__),
]
}

62
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('<a href="{{ view.cancel_url }}"'
' class="btn btn-dark">Cancelar</a>')]
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Vincular Cronômetro à Lista de Discussão'),
row1, row2,
HTML("&nbsp;"),
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

91
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')]),
),
]

67
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,
@ -874,3 +875,69 @@ class RegistroLeitura(models.Model):
'RegistroLeitura deve ter exatamente um dos campos '
'ordem ou expediente preenchido. Ambos estão preenchidos: '
'{}, {}'. 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)

28
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<pk>\d+)/(?P<iso>\d+)/(?P<oid>\d+)/retirar-leitura$',
retirar_leitura, name='retirar_leitura'),
# Lista de Discurso
url(r'^sessao/(?P<pk>\d+)/lista-discurso/',
ListaDiscursoView.as_view(),
name='lista_discurso'),
url(r'^sistema/sessao-plenaria/lista-discurso/(?P<pk>\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<spk>\d+)/(?P<tpk>\d+)/$',
get_orador_listadiscurso, name='get_orador_listadiscurso'),
]

109
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)})

3
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:

12
sapl/templates/painel/index.html

@ -21,17 +21,6 @@
{% 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>
@ -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 %}",

289
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 %}
<!DOCTYPE HTML>
<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!--[if gt IE 8]><!-->
<html lang="en">
<!--<![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 %}
</head>
<body class="painel-principal">
<audio type="hidden" id="audio" src="{% webpack_static 'audio/ring.mp3' %}"></audio>
{% if painel_config.exibir_nome_casa %}
<div class="d-flex justify-content-center">
<h1 id="casa_legislativa" class="title text-title" style="color:red">{{casa.nome}}</h1>
</div>
{% endif %}
<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>
</div>
</div>
<div class="row justify-content-center">
<div class="col-1">
<img src="" id="logo-painel" class="logo-painel" alt=""/>
</div>
</div>
<div class="row justify-content-center">
<h2 class="text-danger"><span id="message"></span></h2>
<h2 class="text-danger">{{lista.tipo}}</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="d-flex justify-content-start">
<div class="col-md-4">
<div class="text-center painel">
<h2 class="text-subtitle">Parlamentares</h2>
<span id="oradores" class="text-value text-center"></span>
</div>
</div>
<div class="col-md-4">
<div id="orador-div" class="text-center painel">
<h2 class="text-subtitle">Orador(a)</h2>
<span id="orador" class="text-value text-center"></span>
<br><br>
<div id="foto-div"></div>
</div>
</div>
<div class="col-md-4 text-center painel">
<h2 class="text-subtitle">{% if cronometros|length == 1 %}Cronômetro{% elif cronometros|length > 1%}Cronômetros{% endif %}</h2>
<div class="text-value">
{% for cronometro in cronometros %}
{{cronometro}}: <span id="cronometro_{{cronometro.id}}"></span><br>
{% endfor %}
</div>
</div>
</div>
</body>
{% 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 %}
<script type="text/javascript">
var d = new Date();
var n = d.toLocaleDateString();
document.getElementById("date").innerHTML = n;
function checkTime(i) {
if (i<10) {i = "0" + i}; // add zero in front of numbers < 10
return i;
}
function startTime() {
var today=new Date();
var h=today.getHours();
var m=today.getMinutes();
var s=today.getSeconds();
m = checkTime(m);
s = checkTime(s);
$("#relogio").text(h+":"+m+":"+s)
var t = setTimeout(function(){
startTime()
}, 500);
}
function playAudioNumVezes(audio, times, ended) {
if (times <= 0) {
return;
}
let played = 0;
audio.addEventListener("ended", function() {
played++;
if (played < times) {
audio.play();
} else if (ended) {
ended();
}
});
audio.play();
}
function convertValueToDuration(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();
}
$(document).ready(function() {
//TODO: replace by a fancy jQuery clock
startTime();
var audioAlertFinish = document.getElementById("audio");
// Obtém duração do disparo ao término do tempo e converte para segundos
var duracao_disparo = "{{ painel_config.tempo_disparo_termino }}";
let tmp = duracao_disparo.split(":");
duracao_disparo = parseInt(tmp[0])*3600 + parseInt(tmp[1])*60 + parseInt(tmp[2]);
var num_vezes_toca_audio = Math.round(duracao_disparo/audioAlertFinish.duration);
var cronometros_previous = [];
{% for cron in cronometros %}
cronometros_previous.push(0);
$('#cronometro_' + "{{cron.id}}").runner({
autostart: {% if cron.status == "I"%} true {% else %} false {% endif %},
countdown: true,
startAt:
{% if cron.status == "R" %}
parseInt("{{cron.duracao_cronometro|duration_to_seconds}}") * 1000
{% elif cron.status == "S" %}
{% if cron.last_stop_duration %}
parseInt("{{cron.last_stop_duration|duration_to_seconds}}") * 1000
{% else %}
parseInt("{{cron.duracao_cronometro|duration_to_seconds}}") * 1000
{% endif %}
{% elif cron.status == "I" %}
parseInt("{{cron.duracao_cronometro|duration_difference:cron.ultima_alteracao_status}}") * 1000
{% endif %},
stopAt: 0,
milliseconds: false,
format: convertValueToDuration
}).on('runnerFinish', function(eventObject, info){
playAudioNumVezes(audioAlertFinish, num_vezes_toca_audio);
});
{% endfor %}
var tempo_disparo_antecedencia = "{{ painel_config.tempo_disparo_antecedencia }}"
tmp = tempo_disparo_antecedencia.split(":");
tempo_disparo_antecedencia = checkTime(parseInt(tmp[0])) + ":" + checkTime(parseInt(tmp[1])) + ":" + checkTime(parseInt(tmp[2]));
(function poll() {
$.ajax({
url: "{% url 'sapl.painel:dados_painel_discurso' sessao_id lista.tipo.pk %}",
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"])
$("#orador").text(data['orador']);
if (data["brasao"] != null)
$("#logo-painel").attr("src", data["brasao"]);
$("#foto-div").children().remove();
if (data["orador_img"] != null){
$("#foto-div").children().remove();
$("#foto-div").append('<img style="width:300px; height:300px" src=' +
data["orador_img"] + ' id="foto-orador" alt=""/>')
}
var oradores = $("#oradores");
oradores.children().remove();
var oradores_list = data["oradores"];
oradores.append('<table style="margin-left:30%" id="oradores_list">');
$.each(oradores_list, function (index, parlamentar) {
if(parlamentar != data['orador']){
$('#oradores_list').append('<tr><td class="text-value" style="color:white" >' +
' ' + (index+1) + '. &nbsp' + parlamentar + '</td></tr>')
}
else{
$('#oradores_list').append('<tr><td class="text-value" style="color:yellow" >' +
' ' + (index+1) + '. &nbsp' + parlamentar + '</td></tr>')
}
});
// obtém todos os ids em uma lista e
// converte os dados do status dos cronômetros de dicionário para uma lista
var ids = [];
var status_cronometros = [];
for (var key in data['cronometros']) {
if (data['cronometros'].hasOwnProperty(key)) {
ids.push(key);
status_cronometros.push(data['cronometros'][key]);
}
}
//console.log(data['cronometros'])
// converte os dados de dicionário para uma lista
var duracao_cronometros = [];
for (let id of ids) {
duracao_cronometros.push(data['duracao_cronometros'][id]);
}
for(let i=0; i<status_cronometros.length; i++){
if (!cronometros_previous[i])
cronometros_previous[i] = ''
// se houve alteração de status
if (status_cronometros[i] != cronometros_previous[i]) {
if(status_cronometros[i] == 'reset'){
// é necessário recriar o cronômetro com o valor da duração original devido a limitações da API
$('#cronometro_' + ids[i]).runner({
autostart: false,
countdown: true,
startAt: parseInt(duracao_cronometros[i]) * 1000,
stopAt: 0,
milliseconds: false,
format: convertValueToDuration
}).on('runnerFinish', function(eventObject, info){
playAudioNumVezes(audioAlertFinish, num_vezes_toca_audio);
});
}
// ações de start e stop
else{
$('#cronometro_' + ids[i]).runner(status_cronometros[i]);
}
cronometros_previous[i] = status_cronometros[i];
}
// Dispara aviso prévio se estiver configurado
if($('#cronometro_' + ids[i]).runner('info').formattedTime == tempo_disparo_antecedencia
&& "{{ painel_config.disparo_cronometro }}" == "True" ){
audioAlertFinish.play();
}
}
},
error: function(err) {
console.error(err);
},
dataType: "json",
complete: setTimeout(function() {poll()}, 500),
timeout: 20000 // TODO: decrease
})
})();
});
</script>
</html>

2
sapl/templates/sessao/cronometrolista_form.html

@ -0,0 +1,2 @@
{% extends "crud/form.html" %}
{% load i18n %}

9
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

153
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 %}
<h1 class="page-header">
Lista de Discurso <small>({{sessaoplenaria}})</small>
</h1>
{% endblock %}
{% block base_content %}
{{block.super}}
<div id="lista-discurso">
<br>
<div v-if="tipo_listas.length > 0">
<h3>Selecione um Tipo de Lista de Discurso</h3>
<select class="form-control" v-model="lista_selecionada">
<option v-for="l in tipo_listas" v-bind:value="{id:l.id, nome:l.nome}">
[[ l.nome ]]
</option>
</select>
</div>
<h3 v-else>
Não há <a href="{% url 'sapl.sessao:tipolistadiscurso_create' %}">
<b>Tipos de Lista de Discurso</b></a> criadas. Para criar, vá em
<b>Sistema -> Tabelas Auxiliares -> Tipo de Lista de Discurso.</b>
</h3>
<br>
<div v-if="lista_selecionada" class="form-group row">
<div class="col-md-6">
<button @click="selecionado_add = !selecionado_add; selecionado_painel = false;" :class="{btSelecionado:selecionado_add,}" class="btn btn-secondary btn-lg form-control" type="button">
Adicionar Parlamentar à Lista de Discurso
</button>
</div>
<div class="col-md-6">
<button @click="selecionado_add = false; selecionado_painel = !selecionado_painel;" :class="{btSelecionado:selecionado_painel,}" class="btn btn-secondary btn-lg form-control" type="button">
Painel da Lista de Discurso
</button>
</div>
</div>
<br>
<br>
<div v-if="selecionado_add" id="collapseAddParlamentar">
<div v-if="lista_selecionada" class="row">
<div class="col-md-6">
<h3>Parlamentares</h3>
<h6>Aperte CTRL para selecionar mais de um</h6>
<br>
<select v-if="parlamentares" v-model="parlamentares_selecionados" multiple size="15" class="form-control">
<option v-for="p in parlamentares" v-bind:value="{nome:p.nome, id:p.id}">
[[ p.nome ]]
</option>
</select>
</div>
<div v-if="parlamentares_selecionados.length > 0" class="col-md-6">
<h3>Lista de Discurso<h3>
<h6>Arraste para ordenar</h6>
<br>
{% comment %} <h3 v-for="p in parlamentares_selecionados">[[p.nome]] </h3> {% endcomment %}
<draggable v-model="parlamentares_selecionados" class="list-group" ghost-class="ghost">
<div class="list-group-item" v-for="(element,index) in parlamentares_selecionados" :key="element.id">
[[index+1]]. [[element.nome]]
</div>
</draggable>
</div>
</div>
<br><br>
<div v-if="lista_selecionada" class="row">
<div style="align:center" class="col-md-12 text-center">
<button v-on:click="saveParlamentarLista" type="button" class="btn btn-primary">Salvar Lista</button>
</div>
</div>
</div>
<div v-if="selecionado_painel" id="collapsePainelLista">
<div v-if="lista_selecionada" class="row">
<div v-if="parlamentares_selecionados.length > 0" class="col-md-6">
<h3>Lista de Discurso<h3>
<h6>Clique uma vez para escolher o orador e uma segunda vez para desmarcar o orador.</h6>
<br>
<b-list-group v-for="(element,index) in parlamentares_selecionados" :key="element.id">
<b-list-group-item button @click='setOrador(index)' :class="{current:parlamentares_selecionados[index].id == orador}">
[[index+1]]. [[element.nome]]
</b-list-group-item>
</b-list-group>
</div>
<div class="col-md-6">
<h3>Cronômetros<h3>
<br><br>
<div v-for="(cron,index) in cronometros_lista">
<div class="row">
<div class="col-md-12 mb-2"><h3><a :href="'/sistema/cronometro/'+ cron.id">[[cron.tipo]]</a></h3></div>
</div>
<countdown ref="countdown" :time="cron.duracao_cronometro._atual" :auto-start="counting[index]" :transform="transform" @end="handleCountdownEnd(index)">
<template slot-scope="props">
<input size="6" :id='"cronometro_"+cron.id' :value="props.hours+':'+props.minutes+':'+props.seconds" readyonly="true" class="form-control">
</template>
</countdown>
<br>
<div class="row">
<div v-if="!counting[index]" class="col-md-6">
<button type="button" :id='"cronometro_" + cron.id + "_Start"' class="btn btn-success" @click="startCountdown(index)">Iniciar</button>
</div>
<div v-else class="col-md-6">
<button type="button" :id='"cronometro_" + cron.id + "_Stop"' class="btn btn-danger" @click="stopCountdown(index)">Parar</button>
</div>
<div v-if="!counting[index]" class="col-md-6">
<button type="button" :id='"cronometro_" + cron.id + "_Reset"' class="btn btn-success" @click="resetCountdown(index)">Reiniciar</button>
</div>
</div>
<br><br>
</div>
</div>
</div>
<br><br>
<div v-if="lista_selecionada" class="row" >
<div style="text-align: center" class="col-md-12">
<div class="col-md-12">
<a @click="abrir_painel"
class="btn btn-primary active">
Abrir Painel
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block webpack_loader_js %}
{% render_chunk_vendors 'js' %}
{% render_bundle 'global' 'js' %}
{% render_bundle 'sessao' 'js' %}
{% endblock webpack_loader_js %}

5
sapl/templates/sessao/subnav.yaml

@ -41,10 +41,15 @@
url: votacao_bloco_ordemdia
check_permission: sessao.add_sessaoplenaria
- 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:

57
sapl/templates/sessao/tipolistadiscurso_detail.html

@ -0,0 +1,57 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% block sub_actions %}
{{block.super}}
<div class="actions btn-group btn-group-sm" role="group">
<a href="{% url 'sapl.sessao:cronometrolista_form' object.pk %}" class="btn btn-outline-primary">Vincular Cronômetro</a>
</div>
{% endblock sub_actions %}
{% block detail_content %}
{{block.super}}
{% if cronometros_lista %}
<h2 class="legend">Cronômetros vinculados</h2>
<table class="table table-striped table-hover table-link-ordering">
<thead>
<tr>
<th>Cronômetros</th>
</tr>
</thead>
<tbody>
{% for cl in cronometros_lista %}
<tr>
<td><a href="{% url 'sapl.painel:cronometro_detail' cl.cronometro.pk %}">{{cl.cronometro}}</a></td>
<td>
<button type="button" class="btn btn-danger float-right" onclick='desvincular("{{cl.pk}}")'>
Desvincular
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endblock %}
{% block extra_js %}
<script type="text/javascript">
function desvincular(pk){
var csrftoken = '{{ csrf_token }}';
$.ajax({
url: "/api/sessao/cronometrolista/"+pk,
headers: {
'X-CSRFToken':csrftoken,
},
type: 'delete',
contentType: 'application/json',
success: function(result){
console.log("Cronômetro desvinculado.");
}
});
location.reload();
}
</script>
{% endblock %}
Loading…
Cancel
Save