Browse Source

Fixing conflicts with Merge branch '3.1.x' of https://github.com/interlegis/sapl into 1801-justificativa

pull/2260/head
Mariana Mendes 7 years ago
parent
commit
9cddae0e93
  1. 1
      .gitignore
  2. 4
      docker-compose.yml
  3. 2
      release.sh
  4. 18
      sapl/audiencia/views.py
  5. 134
      sapl/base/email_utils.py
  6. 26
      sapl/base/forms.py
  7. 42
      sapl/base/receivers.py
  8. 0
      sapl/base/signals.py
  9. 13
      sapl/base/templatetags/common_tags.py
  10. 16
      sapl/compilacao/views.py
  11. 6
      sapl/legacy/management/commands/ressuscitar_deps.py
  12. 9
      sapl/legacy/migracao.py
  13. 32
      sapl/legacy/migracao_dados.py
  14. 7
      sapl/legacy/migracao_documentos.py
  15. 22
      sapl/legacy/scripts/ressuscita_dependencias.py
  16. 2
      sapl/materia/apps.py
  17. 3
      sapl/materia/forms.py
  18. 21
      sapl/materia/migrations/0032_auto_20181022_1743.py
  19. 2
      sapl/materia/models.py
  20. 29
      sapl/materia/receivers.py
  21. 2
      sapl/materia/tests/test_email_templates.py
  22. 34
      sapl/materia/views.py
  23. 50
      sapl/norma/forms.py
  24. 40
      sapl/norma/migrations/0014_auto_20181008_1655.py
  25. 29
      sapl/norma/models.py
  26. 5
      sapl/norma/urls.py
  27. 37
      sapl/norma/views.py
  28. 3
      sapl/protocoloadm/apps.py
  29. 27
      sapl/protocoloadm/forms.py
  30. 32
      sapl/protocoloadm/migrations/0008_acompanhamentodocumento.py
  31. 20
      sapl/protocoloadm/migrations/0008_auto_20181009_1741.py
  32. 16
      sapl/protocoloadm/migrations/0009_merge.py
  33. 29
      sapl/protocoloadm/models.py
  34. 65
      sapl/protocoloadm/tests/test_docadm_email_templates.py
  35. 14
      sapl/protocoloadm/urls.py
  36. 184
      sapl/protocoloadm/views.py
  37. 14
      sapl/relatorios/templates/pdf_pauta_sessao_gerar.py
  38. 28
      sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py
  39. 39
      sapl/relatorios/views.py
  40. 44
      sapl/rules/map_rules.py
  41. 4
      sapl/rules/tests/test_rules.py
  42. 17
      sapl/sessao/forms.py
  43. 28
      sapl/sessao/migrations/0024_ocorrenciasessao.py
  44. 21
      sapl/sessao/migrations/0025_auto_20180919_1116.py
  45. 66
      sapl/sessao/migrations/0026_auto_20181016_1944.py
  46. 39
      sapl/sessao/models.py
  47. 132
      sapl/sessao/tests/test_sessao.py
  48. 10
      sapl/sessao/urls.py
  49. 76
      sapl/sessao/views.py
  50. 171
      sapl/templates/404.html
  51. 135
      sapl/templates/500.html
  52. 10
      sapl/templates/base.html
  53. 9
      sapl/templates/compilacao/dispositivo_form_search_fragment.html
  54. 10
      sapl/templates/compilacao/messages.html
  55. 16
      sapl/templates/crud/detail.html
  56. 25
      sapl/templates/email/acompanhar_documento.html
  57. 16
      sapl/templates/email/acompanhar_documento.txt
  58. 2
      sapl/templates/email/tramitacao.html
  59. 4
      sapl/templates/email/tramitacao.txt
  60. 15
      sapl/templates/materia/materialegislativa_detail.html
  61. 47
      sapl/templates/norma/autorianorma_form.html
  62. 4
      sapl/templates/norma/layouts.yaml
  63. 3
      sapl/templates/norma/subnav.yaml
  64. 21
      sapl/templates/protocoloadm/acompanhamento_documento.html
  65. 3
      sapl/templates/protocoloadm/documentoadministrativo_filter.html
  66. 6
      sapl/templates/sessao/blocos_ata/ocorrencias_da_sessao.html
  67. 6
      sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html
  68. 32
      sapl/templates/sessao/ocorrencia_sessao.html
  69. 2
      sapl/templates/sessao/resumo.html
  70. 1
      sapl/templates/sessao/resumo_ata.html
  71. 2
      sapl/templates/sessao/subnav.yaml
  72. 2
      setup.py

1
.gitignore

@ -87,6 +87,7 @@ target/
*.sublime-workspace *.sublime-workspace
.ipynb_checkpoints/ .ipynb_checkpoints/
*.ipynb *.ipynb
.vscode/
# specific to this project # specific to this project

4
docker-compose.yml

@ -1,5 +1,5 @@
sapldb: sapldb:
image: postgres:9.6.8-alpine image: postgres:10.5-alpine
restart: always restart: always
environment: environment:
POSTGRES_PASSWORD: sapl POSTGRES_PASSWORD: sapl
@ -11,7 +11,7 @@ sapldb:
ports: ports:
- "5432:5432" - "5432:5432"
sapl: sapl:
image: interlegis/sapl:3.1.122 image: interlegis/sapl:3.1.128
restart: always restart: always
environment: environment:
ADMIN_PASSWORD: interlegis ADMIN_PASSWORD: interlegis

2
release.sh

@ -21,7 +21,7 @@ function bump_version {
function commit_and_push { function commit_and_push {
echo "committing..." echo "committing..."
git add docker-compose.yml setup.py git add docker-compose.yml setup.py sapl/templates/base.html
git commit -m "Release: $NEXT_VERSION" git commit -m "Release: $NEXT_VERSION"
git tag $NEXT_VERSION git tag $NEXT_VERSION

18
sapl/audiencia/views.py

@ -1,4 +1,5 @@
from django.http import HttpResponse from django.http import HttpResponse
from django.core.urlresolvers import reverse
from django.views.decorators.clickjacking import xframe_options_exempt from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import UpdateView from django.views.generic import UpdateView
from sapl.crud.base import RP_DETAIL, RP_LIST, Crud from sapl.crud.base import RP_DETAIL, RP_LIST, Crud
@ -23,6 +24,23 @@ class AudienciaCrud(Crud):
class ListView(Crud.ListView): class ListView(Crud.ListView):
paginate_by = 10 paginate_by = 10
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
audiencia_materia = {}
for o in context['object_list']:
# indexado pelo numero da audiencia
audiencia_materia[str(o.numero)] = o.materia
for row in context['rows']:
coluna_materia = row[3] # se mudar a ordem de listagem mudar aqui
if coluna_materia[0]:
materia = audiencia_materia[row[0][0]]
url_materia = reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': materia.id})
row[3] = (coluna_materia[0], url_materia)
return context
class CreateView(Crud.CreateView): class CreateView(Crud.CreateView):
form_class = AudienciaForm form_class = AudienciaForm

134
sapl/materia/email_utils.py → sapl/base/email_utils.py

@ -8,7 +8,8 @@ from django.utils import timezone
from sapl.base.models import CasaLegislativa from sapl.base.models import CasaLegislativa
from sapl.settings import EMAIL_SEND_USER from sapl.settings import EMAIL_SEND_USER
from .models import AcompanhamentoMateria from sapl.materia.models import AcompanhamentoMateria
from sapl.protocoloadm.models import AcompanhamentoDocumento
def load_email_templates(templates, context={}): def load_email_templates(templates, context={}):
@ -61,56 +62,73 @@ def enviar_emails(sender, recipients, messages):
fail_silently=False) fail_silently=False)
def criar_email_confirmacao(base_url, casa_legislativa, materia, hash_txt=''): def criar_email_confirmacao(base_url, casa_legislativa, doc_mat, tipo, hash_txt=''):
if not casa_legislativa: if not casa_legislativa:
raise ValueError("Casa Legislativa é obrigatória") raise ValueError("Casa Legislativa é obrigatória")
if not materia: if not doc_mat:
raise ValueError("Matéria é obrigatória") if tipo == "materia":
msg = "Matéria é obrigatória"
else:
msg = "Documento é obrigatório"
raise ValueError(msg)
# FIXME i18n # FIXME i18n
casa_nome = (casa_legislativa.nome + ' de ' + casa_nome = ("{} de {} - {}".format(casa_legislativa.nome,
casa_legislativa.municipio + '-' + casa_legislativa.municipio,
casa_legislativa.uf) casa_legislativa.uf))
if tipo == "materia":
doc_mat_url = reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': doc_mat.id})
confirmacao_url = reverse('sapl.materia:acompanhar_confirmar',
kwargs={'pk': doc_mat.id})
ementa = doc_mat.ementa
autores = [autoria.autor.nome for autoria in doc_mat.autoria_set.all()]
else:
doc_mat_url = reverse('sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': doc_mat.id})
confirmacao_url = reverse('sapl.protocoloadm:acompanhar_confirmar',
kwargs={'pk': doc_mat.id})
ementa = doc_mat.assunto
autores = ""
materia_url = reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': materia.id})
confirmacao_url = reverse('sapl.materia:acompanhar_confirmar',
kwargs={'pk': materia.id})
autores = []
for autoria in materia.autoria_set.all():
autores.append(autoria.autor.nome)
templates = load_email_templates(['email/acompanhar.txt', templates = load_email_templates(['email/acompanhar.txt',
'email/acompanhar.html'], 'email/acompanhar.html'],
{"casa_legislativa": casa_nome, {"casa_legislativa": casa_nome,
"logotipo": casa_legislativa.logotipo, "logotipo": casa_legislativa.logotipo,
"descricao_materia": materia.ementa, "descricao_materia": ementa,
"autoria": autores, "autoria": autores,
"hash_txt": hash_txt, "hash_txt": hash_txt,
"base_url": base_url, "base_url": base_url,
"materia": str(materia), "materia": str(doc_mat),
"materia_url": materia_url, "materia_url": doc_mat_url,
"confirmacao_url": confirmacao_url, }) "confirmacao_url": confirmacao_url, })
return templates return templates
def do_envia_email_confirmacao(base_url, casa, materia, destinatario): def do_envia_email_confirmacao(base_url, casa, tipo, doc_mat, destinatario):
# #
# Envia email de confirmacao para atualizações de tramitação # Envia email de confirmacao para atualizações de tramitação
# #
sender = EMAIL_SEND_USER sender = EMAIL_SEND_USER
# FIXME i18n # FIXME i18n
subject = "[SAPL] " + str(materia) + " - Ative o Acompanhamento da Materia" if tipo == "materia":
msg = " - Ative o Acompanhamento da Matéria"
else:
msg = " - Ative o Acompanhamento de Documento"
subject = "[SAPL] {} {}".format(str(doc_mat), msg)
messages = [] messages = []
recipients = [] recipients = []
email_texts = criar_email_confirmacao(base_url, email_texts = criar_email_confirmacao(base_url,
casa, casa,
materia, doc_mat,
tipo,
destinatario.hash,) destinatario.hash,)
recipients.append(destinatario.email) recipients.append(destinatario.email)
messages.append({ messages.append({
@ -123,30 +141,41 @@ def do_envia_email_confirmacao(base_url, casa, materia, destinatario):
enviar_emails(sender, recipients, messages) enviar_emails(sender, recipients, messages)
def criar_email_tramitacao(base_url, casa_legislativa, materia, status, def criar_email_tramitacao(base_url, casa_legislativa, tipo, doc_mat, status,
unidade_destino, hash_txt=''): unidade_destino, hash_txt=''):
if not casa_legislativa: if not casa_legislativa:
raise ValueError("Casa Legislativa é obrigatória") raise ValueError("Casa Legislativa é obrigatória")
if not materia: if not doc_mat:
raise ValueError("Matéria é obrigatória") if tipo == "materia":
msg = "Matéria é obrigatória"
else:
msg = "Documento é obrigatório"
raise ValueError(msg)
# FIXME i18n # FIXME i18n
casa_nome = (casa_legislativa.nome + ' de ' + casa_nome = ("{} de {} - {}".format(casa_legislativa.nome,
casa_legislativa.municipio + '-' + casa_legislativa.municipio,
casa_legislativa.uf) casa_legislativa.uf))
if tipo == "materia":
url_materia = reverse('sapl.materia:tramitacao_list', doc_mat_url = reverse('sapl.materia:tramitacao_list',
kwargs={'pk': materia.id}) kwargs={'pk': doc_mat.id})
url_excluir = reverse('sapl.materia:acompanhar_excluir', url_excluir = reverse('sapl.materia:acompanhar_excluir',
kwargs={'pk': materia.id}) kwargs={'pk': doc_mat.id})
ementa = doc_mat.ementa
autores = [autoria.autor.nome for autoria in doc_mat.autoria_set.all()]
tramitacao = doc_mat.tramitacao_set.last()
autores = [] else:
for autoria in materia.autoria_set.all(): doc_mat_url = reverse('sapl.protocoloadm:tramitacaoadministrativo_list',
autores.append(autoria.autor.nome) kwargs={'pk': doc_mat.id})
url_excluir = reverse('sapl.protocoloadm:acompanhar_excluir',
tramitacao = materia.tramitacao_set.last() kwargs={'pk': doc_mat.id})
autores = ""
ementa = doc_mat.assunto
tramitacao = doc_mat.tramitacaoadministrativo_set.last()
templates = load_email_templates(['email/tramitacao.txt', templates = load_email_templates(['email/tramitacao.txt',
'email/tramitacao.html'], 'email/tramitacao.html'],
@ -154,34 +183,42 @@ def criar_email_tramitacao(base_url, casa_legislativa, materia, status,
"data_registro": dt.strftime( "data_registro": dt.strftime(
timezone.now(), timezone.now(),
"%d/%m/%Y"), "%d/%m/%Y"),
"cod_materia": materia.id, "cod_materia": doc_mat.id,
"logotipo": casa_legislativa.logotipo, "logotipo": casa_legislativa.logotipo,
"descricao_materia": materia.ementa, "descricao_materia": ementa,
"autoria": autores, "autoria": autores,
"data": tramitacao.data_tramitacao, "data": tramitacao.data_tramitacao,
"status": status, "status": status,
"localizacao": unidade_destino, "localizacao": unidade_destino,
"texto_acao": tramitacao.texto, "texto_acao": tramitacao.texto,
"hash_txt": hash_txt, "hash_txt": hash_txt,
"materia": str(materia), "materia": str(doc_mat),
"base_url": base_url, "base_url": base_url,
"materia_url": url_materia, "materia_url": doc_mat_url,
"excluir_url": url_excluir}) "excluir_url": url_excluir})
return templates return templates
def do_envia_email_tramitacao(base_url, materia, status, unidade_destino): def do_envia_email_tramitacao(base_url, tipo, doc_mat, status, unidade_destino):
# #
# Envia email de tramitacao para usuarios cadastrados # Envia email de tramitacao para usuarios cadastrados
# #
destinatarios = AcompanhamentoMateria.objects.filter(materia=materia, if tipo == "materia":
confirmado=True) destinatarios = AcompanhamentoMateria.objects.filter(materia=doc_mat,
confirmado=True)
else:
destinatarios = AcompanhamentoDocumento.objects.filter(documento=doc_mat,
confirmado=True)
casa = CasaLegislativa.objects.first() casa = CasaLegislativa.objects.first()
sender = EMAIL_SEND_USER sender = EMAIL_SEND_USER
# FIXME i18n # FIXME i18nn
subject = "[SAPL] " + str(materia) + \ if tipo == "materia":
" - Acompanhamento de Materia Legislativa" msg = " - Acompanhamento de Matéria Legislativa"
else:
msg = " - Acompanhamento de Documento"
subject = "[SAPL] {} {}".format(str(doc_mat), msg)
connection = get_connection() connection = get_connection()
connection.open() connection.open()
@ -190,10 +227,11 @@ def do_envia_email_tramitacao(base_url, materia, status, unidade_destino):
try: try:
email_texts = criar_email_tramitacao(base_url, email_texts = criar_email_tramitacao(base_url,
casa, casa,
materia, tipo,
doc_mat,
status, status,
unidade_destino, unidade_destino,
destinatario.hash,) destinatario.hash)
email = EmailMultiAlternatives( email = EmailMultiAlternatives(
subject, subject,

26
sapl/base/forms.py

@ -262,6 +262,32 @@ class TipoAutorForm(ModelForm):
super(TipoAutorForm, self).__init__(*args, **kwargs) super(TipoAutorForm, self).__init__(*args, **kwargs)
def clean(self):
super(TipoAutorForm, self).clean()
if not self.is_valid():
return self.cleaned_data
cd = self.cleaned_data
lista = ['comissão',
'comis',
'parlamentar',
'bancada',
'bloco',
'comissao',
'vereador',
'órgão',
'orgao',
'deputado',
'senador',
'vereadora',
'frente']
for l in lista:
if l in cd['descricao'].lower():
raise ValidationError(_('A descrição colocada não pode ser usada '
'por ser equivalente a um tipo já existente'))
class AutorForm(ModelForm): class AutorForm(ModelForm):
senha = forms.CharField( senha = forms.CharField(

42
sapl/base/receivers.py

@ -0,0 +1,42 @@
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from sapl.materia.models import Tramitacao
from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.base.signals import tramitacao_signal
from sapl.utils import get_base_url
from sapl.base.email_utils import do_envia_email_tramitacao
@receiver(tramitacao_signal)
def handle_tramitacao_signal(sender, **kwargs):
tramitacao = kwargs.get("post")
request = kwargs.get("request")
if 'protocoloadm' in str(sender):
doc_mat = tramitacao.documento
tipo = "documento"
elif 'materia' in str(sender):
tipo = "materia"
doc_mat = tramitacao.materia
do_envia_email_tramitacao(
get_base_url(request),
tipo,
doc_mat,
tramitacao.status,
tramitacao.unidade_tramitacao_destino)
@receiver(post_delete)
def status_tramitacao_materia(sender, instance, **kwargs):
if isinstance(sender, TramitacaoAdministrativo):
if instance.status.indicador == 'F':
materia = instance.materia
materia.em_tramitacao = True
materia.save()
elif isinstance(sender, TramitacaoAdministrativo):
if instance.status.indicador == 'F':
documento = instance.documento
documento.tramitacao = True
documento.save()

0
sapl/materia/signals.py → sapl/base/signals.py

13
sapl/base/templatetags/common_tags.py

@ -198,6 +198,19 @@ def url(value):
return True return True
return False return False
@register.filter
def audio_url(value):
return True if url(value) and value.endswith("mp3") else False
@register.filter
def video_url(value):
return True if url(value) and value.endswith("mp4") else False
@register.filter
def file_extension(value):
import pathlib
return pathlib.Path(value).suffix.replace('.', '')
@register.filter @register.filter
def cronometro_to_seconds(value): def cronometro_to_seconds(value):

16
sapl/compilacao/views.py

@ -2892,7 +2892,21 @@ class DispositivoSearchFragmentFormView(ListView):
itens.append(item) itens.append(item)
return JsonResponse(itens, safe=False) return JsonResponse(itens, safe=False)
return ListView.get(self, request, *args, **kwargs) response = ListView.get(self, request, *args, **kwargs)
if not self.object_list.exists():
messages.info(
request, _('Não foram encontrados resultados '
'com seus critérios de busca!'))
try:
r = response.render()
return response
except Exception as e:
messages.error(request, "Erro - %s" % e)
context = {}
self.template_name = 'compilacao/messages.html'
return self.render_to_response(context)
def get_queryset(self): def get_queryset(self):
try: try:

6
sapl/legacy/management/commands/ressucitar_deps.py → sapl/legacy/management/commands/ressuscitar_deps.py

@ -1,12 +1,12 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from sapl.legacy.scripts.ressucita_dependencias import adiciona_ressucitar from sapl.legacy.scripts.ressuscita_dependencias import adiciona_ressuscitar
class Command(BaseCommand): class Command(BaseCommand):
help = 'Ressucita dependências apagadas ' \ help = 'Ressuscita dependências apagadas ' \
'que são necessárias para migrar outros registros' 'que são necessárias para migrar outros registros'
def handle(self, *args, **options): def handle(self, *args, **options):
adiciona_ressucitar() adiciona_ressuscitar()

9
sapl/legacy/migracao.py

@ -70,12 +70,13 @@ def scrap_sde(url, usuario, senha=None):
{'__ac_name': usuario, '__ac_password': senha}) {'__ac_name': usuario, '__ac_password': senha})
assert res.status_code == 200 assert res.status_code == 200
url_proposicao = '{}/sapl_documentos/proposicao/{}/renderXML?xsl=__default__' # noqa url_proposicao_tmpl = '{}/sapl_documentos/proposicao/{}/renderXML?xsl=__default__' # noqa
total = Proposicao.objects.count() total = Proposicao.objects.count()
for num, proposicao in enumerate(Proposicao.objects.all()): for num, proposicao in enumerate(Proposicao.objects.all()):
pk = proposicao.pk pk = proposicao.pk
res = session.get(url_proposicao.format(url, pk)) url_proposicao = url_proposicao_tmpl.format(url, pk)
print("pk: {} status: {} (progresso: {:.2%})".format( res = session.get(url_proposicao)
pk, res.status_code, num / total)) print("pk: {} status: {} {} (progresso: {:.2%})".format(
pk, res.status_code, url_proposicao, num / total))
if res.status_code == 200: if res.status_code == 200:
salva_conteudo_do_sde(proposicao, res.content) salva_conteudo_do_sde(proposicao, res.content)

32
sapl/legacy/migracao_dados.py

@ -623,6 +623,36 @@ def corrige_unidades_tramitacao_destino_vazia_como_anterior():
'''.format(tabela_tramitacao)) '''.format(tabela_tramitacao))
def apaga_ref_a_mats_e_docs_inexistentes_em_proposicoes():
# as referencias a matérias e documentos apagados não aparecem no 3.1
# além do que, se ressuscitássemos essas matérias e docs,
# não seria possível apagá-los,
# pois é impossível para um usuário não autor acessar as proposicões
# para apagar a referências antes
exec_legado('''
update proposicao set cod_materia = NULL where cod_materia not in (
select cod_materia from materia_legislativa
where ind_excluido <> 1);
''')
props_sem_mats = list(primeira_coluna(exec_legado('''
select cod_proposicao from proposicao p inner join tipo_proposicao t
on p.tip_proposicao = t.tip_proposicao
where t.ind_mat_ou_doc = 'M' and cod_mat_ou_doc not in (
select cod_materia from materia_legislativa
where ind_excluido <> 1)
''')))
props_sem_docs = list(primeira_coluna(exec_legado('''
select cod_proposicao from proposicao p inner join tipo_proposicao t
on p.tip_proposicao = t.tip_proposicao
where t.ind_mat_ou_doc = 'D' and cod_mat_ou_doc not in (
select cod_documento from documento_acessorio
where ind_excluido <> 1);
''')))
exec_legado_em_subconjunto('''
update proposicao set cod_mat_ou_doc = NULL
where cod_proposicao in {}''', props_sem_mats + props_sem_docs)
def uniformiza_banco(): def uniformiza_banco():
propaga_exclusoes(PROPAGACOES_DE_EXCLUSAO) propaga_exclusoes(PROPAGACOES_DE_EXCLUSAO)
checa_registros_votacao_ambiguos_e_remove_nao_usados() checa_registros_votacao_ambiguos_e_remove_nao_usados()
@ -722,6 +752,8 @@ sessao_plenaria_presenca | dat_sessao = NULL | dat_sessao = 0
select cod_materia from materia_legislativa select cod_materia from materia_legislativa
where ind_excluido <> 1);''') where ind_excluido <> 1);''')
apaga_ref_a_mats_e_docs_inexistentes_em_proposicoes()
class Record: class Record:
pass pass

7
sapl/legacy/migracao_documentos.py

@ -1,4 +1,5 @@
import os import os
import shutil
import re import re
from glob import glob from glob import glob
from os.path import join from os.path import join
@ -52,6 +53,12 @@ def mover_documento(repo, origem, destino, ignora_origem_ausente=False):
if ignora_origem_ausente and not os.path.exists(origem): if ignora_origem_ausente and not os.path.exists(origem):
print('Origem ignorada ao mover documento: {}'.format(origem)) print('Origem ignorada ao mover documento: {}'.format(origem))
return return
# apaga destino, se houver, e renomeia origem para destino
if os.path.exists(destino):
if os.path.isdir(destino):
shutil.rmtree(destino)
else:
os.remove(destino)
os.makedirs(os.path.dirname(destino), exist_ok=True) os.makedirs(os.path.dirname(destino), exist_ok=True)
os.rename(origem, destino) os.rename(origem, destino)

22
sapl/legacy/scripts/ressucita_dependencias.py → sapl/legacy/scripts/ressuscita_dependencias.py

@ -70,6 +70,7 @@ tipo_dependente /sistema/parlamentar/tipo-dependente
origem /sistema/materia/origem origem /sistema/materia/origem
documento_acessorio /materia/documentoacessorio documento_acessorio /materia/documentoacessorio
tipo_fim_relatoria /sistema/materia/tipo-fim-relatoria tipo_fim_relatoria /sistema/materia/tipo-fim-relatoria
tipo_situacao_militar /sistema/parlamentar/tipo-militar
''' '''
urls = dict(stripsplit(urls)) urls = dict(stripsplit(urls))
@ -194,7 +195,7 @@ Para facilitar sua conferência, seguem os links para as proposições envolvida
'''.format(table.draw(), links, sqls) '''.format(table.draw(), links, sqls)
def get_dependencias_a_ressucitar(slug): def get_dependencias_a_ressuscitar(slug):
ocorrencias = yaml.load( ocorrencias = yaml.load(
Path(DIR_REPO.child('ocorrencias.yaml').read_file())) Path(DIR_REPO.child('ocorrencias.yaml').read_file()))
fks_faltando = ocorrencias.get('fk') fks_faltando = ocorrencias.get('fk')
@ -265,7 +266,7 @@ SQLS_CRIACAO = [
('unidade_tramitacao', ''' ('unidade_tramitacao', '''
insert into unidade_tramitacao ( insert into unidade_tramitacao (
cod_unid_tramitacao, cod_comissao, cod_orgao, cod_parlamentar, ind_excluido) cod_unid_tramitacao, cod_comissao, cod_orgao, cod_parlamentar, ind_excluido)
values ({}, NULL, NULL, NULL, 0); values ({}, NULL, NULL, 0, 0);
'''), '''),
('autor', SQL_INSERT_TIPO_AUTOR.format(0) + ''' ('autor', SQL_INSERT_TIPO_AUTOR.format(0) + '''
insert into autor ( insert into autor (
@ -307,6 +308,9 @@ SQLS_CRIACAO = [
insert into parlamentar (cod_parlamentar, nom_completo, nom_parlamentar, sex_parlamentar, cod_casa, ind_ativo, ind_unid_deliberativa, ind_excluido) insert into parlamentar (cod_parlamentar, nom_completo, nom_parlamentar, sex_parlamentar, cod_casa, ind_ativo, ind_unid_deliberativa, ind_excluido)
values ({}, "DESCONHECIDO", "DESCONHECIDO", "M", 0, 0, 0, 0); values ({}, "DESCONHECIDO", "DESCONHECIDO", "M", 0, 0, 0, 0);
'''), '''),
('tipo_sessao_plenaria', '''
insert into tipo_sessao_plenaria (tip_sessao, nom_sessao, ind_excluido, num_minimo) values ({}, "DESCONHECIDO", 0, 0);
'''),
] ]
SQLS_CRIACAO = {k: (dedent(sql.strip()), extras) SQLS_CRIACAO = {k: (dedent(sql.strip()), extras)
for k, sql, *extras in SQLS_CRIACAO} for k, sql, *extras in SQLS_CRIACAO}
@ -349,8 +353,8 @@ def get_sql_criar(tabela_alvo, campo, valor, slug):
return sql, links return sql, links
TEMPLATE_RESSUCITADOS = '''{} TEMPLATE_RESSUSCITADOS = '''{}
/* RESSUCITADOS /* RESSUSCITADOS
SOBRE REGISTROS QUE ESTAVAM APAGADOS E FORAM RESTAURADOS SOBRE REGISTROS QUE ESTAVAM APAGADOS E FORAM RESTAURADOS
@ -397,11 +401,11 @@ def get_sqls_desexcluir_criar(preambulo, desexcluir, criar, slug):
links = sem_repeticoes_mantendo_ordem(links) links = sem_repeticoes_mantendo_ordem(links)
sqls, links = ['\n'.join(sorted(s)) for s in [sqls, links]] sqls, links = ['\n'.join(sorted(s)) for s in [sqls, links]]
return TEMPLATE_RESSUCITADOS.format(preambulo, links, sqls) return TEMPLATE_RESSUSCITADOS.format(preambulo, links, sqls)
def get_ressucitar(slug): def get_ressuscitar(slug):
preambulo, desexcluir, criar = get_dependencias_a_ressucitar(slug) preambulo, desexcluir, criar = get_dependencias_a_ressuscitar(slug)
return get_sqls_desexcluir_criar(preambulo, desexcluir, criar, slug) return get_sqls_desexcluir_criar(preambulo, desexcluir, criar, slug)
@ -413,8 +417,8 @@ def get_slug():
return siglas_para_slugs[sigla] return siglas_para_slugs[sigla]
def adiciona_ressucitar(): def adiciona_ressuscitar():
sqls = get_ressucitar(get_slug()) sqls = get_ressuscitar(get_slug())
if sqls.strip(): if sqls.strip():
arq_ajustes_pre_migracao = get_arquivo_ajustes_pre_migracao() arq_ajustes_pre_migracao = get_arquivo_ajustes_pre_migracao()
conteudo = arq_ajustes_pre_migracao.read_file() conteudo = arq_ajustes_pre_migracao.read_file()

2
sapl/materia/apps.py

@ -8,4 +8,4 @@ class AppConfig(apps.AppConfig):
verbose_name = _('Matéria') verbose_name = _('Matéria')
def ready(self): def ready(self):
from . import receivers from sapl.base import receivers

3
sapl/materia/forms.py

@ -1855,14 +1855,13 @@ class ConfirmarProposicaoForm(ProposicaoForm):
else: else:
# numeracao == 'U' ou não informada # numeracao == 'U' ou não informada
nm = Protocolo.objects.all().aggregate(Max('numero')) nm = Protocolo.objects.all().aggregate(Max('numero'))
protocolo = Protocolo() protocolo = Protocolo()
protocolo.numero = (nm['numero__max'] + 1) if nm['numero__max'] else 1 protocolo.numero = (nm['numero__max'] + 1) if nm['numero__max'] else 1
protocolo.ano = timezone.now().year protocolo.ano = timezone.now().year
protocolo.tipo_protocolo = '1' protocolo.tipo_protocolo = '1'
protocolo.interessado = str(proposicao.autor) protocolo.interessado = str(proposicao.autor)[:200] # tamanho máximo 200
protocolo.autor = proposicao.autor protocolo.autor = proposicao.autor
protocolo.assunto_ementa = proposicao.descricao protocolo.assunto_ementa = proposicao.descricao
protocolo.numero_paginas = cd['numero_de_paginas'] protocolo.numero_paginas = cd['numero_de_paginas']

21
sapl/materia/migrations/0032_auto_20181022_1743.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-10-22 20:43
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('materia', '0031_auto_20180924_1724'),
]
operations = [
migrations.AlterField(
model_name='autoria',
name='autor',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='base.Autor', verbose_name='Autor'),
),
]

2
sapl/materia/models.py

@ -307,7 +307,7 @@ class MateriaLegislativa(models.Model):
class Autoria(models.Model): class Autoria(models.Model):
autor = models.ForeignKey(Autor, autor = models.ForeignKey(Autor,
verbose_name=_('Autor'), verbose_name=_('Autor'),
on_delete=models.CASCADE) on_delete=models.PROTECT)
materia = models.ForeignKey( materia = models.ForeignKey(
MateriaLegislativa, on_delete=models.CASCADE, MateriaLegislativa, on_delete=models.CASCADE,
verbose_name=_('Matéria Legislativa')) verbose_name=_('Matéria Legislativa'))

29
sapl/materia/receivers.py

@ -1,29 +0,0 @@
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from sapl.materia.models import Tramitacao
from sapl.materia.signals import tramitacao_signal
from sapl.utils import get_base_url
from .email_utils import do_envia_email_tramitacao
@receiver(tramitacao_signal)
def handle_tramitacao_signal(sender, **kwargs):
tramitacao = kwargs.get("post")
request = kwargs.get("request")
materia = tramitacao.materia
do_envia_email_tramitacao(
get_base_url(request),
materia,
tramitacao.status,
tramitacao.unidade_tramitacao_destino)
@receiver(post_delete, sender=Tramitacao)
def status_tramitacao_materia(sender, instance, **kwargs):
if instance.status.indicador == 'F':
materia = instance.materia
materia.em_tramitacao = True
materia.save()

2
sapl/materia/tests/test_email_templates.py

@ -1,6 +1,6 @@
from django.core import mail from django.core import mail
from sapl.materia.email_utils import enviar_emails, load_email_templates from sapl.base.email_utils import enviar_emails, load_email_templates
def test_email_template_loading(): def test_email_template_loading():

34
sapl/materia/views.py

@ -46,7 +46,7 @@ from sapl.utils import (YES_NO_CHOICES, autor_label, autor_modal,
get_mime_type_from_file_extension, montar_row_autor, get_mime_type_from_file_extension, montar_row_autor,
show_results_filter_set) show_results_filter_set)
from .email_utils import do_envia_email_confirmacao from sapl.base.email_utils import do_envia_email_confirmacao
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
AdicionarVariasAutoriasFilterSet, DespachoInicialForm, AdicionarVariasAutoriasFilterSet, DespachoInicialForm,
DocumentoAcessorioForm, EtiquetaPesquisaForm, DocumentoAcessorioForm, EtiquetaPesquisaForm,
@ -65,7 +65,7 @@ from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria,
RegimeTramitacao, Relatoria, StatusTramitacao, RegimeTramitacao, Relatoria, StatusTramitacao,
TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa, TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa,
TipoProposicao, Tramitacao, UnidadeTramitacao) TipoProposicao, Tramitacao, UnidadeTramitacao)
from .signals import tramitacao_signal from sapl.base.signals import tramitacao_signal
AssuntoMateriaCrud = CrudAux.build(AssuntoMateria, 'assunto_materia') AssuntoMateriaCrud = CrudAux.build(AssuntoMateria, 'assunto_materia')
@ -481,10 +481,13 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView):
form = ReceberProposicaoForm(request.POST) form = ReceberProposicaoForm(request.POST)
if form.is_valid(): if form.is_valid():
proposicoes = Proposicao.objects.filter( try:
data_envio__isnull=False, data_recebimento__isnull=True) # A ultima parte do código deve ser a pk da Proposicao
id = form.cleaned_data["cod_hash"].split("/")[1]
proposicao = Proposicao.objects.get(id=id,
data_envio__isnull=False,
data_recebimento__isnull=True)
for proposicao in proposicoes:
if proposicao.texto_articulado.exists(): if proposicao.texto_articulado.exists():
ta = proposicao.texto_articulado.first() ta = proposicao.texto_articulado.first()
# FIXME hash para textos articulados # FIXME hash para textos articulados
@ -492,7 +495,7 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView):
else: else:
hasher = gerar_hash_arquivo( hasher = gerar_hash_arquivo(
proposicao.texto_original.path, proposicao.texto_original.path,
str(proposicao.pk)) \ str(proposicao.id)) \
if proposicao.texto_original else None if proposicao.texto_original else None
if hasher == form.cleaned_data['cod_hash']: if hasher == form.cleaned_data['cod_hash']:
return HttpResponseRedirect( return HttpResponseRedirect(
@ -500,8 +503,12 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView):
kwargs={ kwargs={
'hash': hasher.split('/')[0][1:], 'hash': hasher.split('/')[0][1:],
'pk': proposicao.pk})) 'pk': proposicao.pk}))
except ObjectDoesNotExist:
messages.error(request, _('Proposição não encontrada!')) messages.error(request, _('Proposição não encontrada!'))
except IndexError:
messages.error(request, _('Código de recibo mal formado!'))
except IOError:
messages.error(request, _('Erro abrindo texto original de proposição'))
return self.form_invalid(form) return self.form_invalid(form)
def get_success_url(self): def get_success_url(self):
@ -1114,7 +1121,7 @@ class TramitacaoCrud(MasterDetailCrud):
msg = _('Tramitação criada, mas e-mail de acompanhamento ' msg = _('Tramitação criada, mas e-mail de acompanhamento '
'de matéria não enviado. Há problemas na configuração ' 'de matéria não enviado. Há problemas na configuração '
'do e-mail.') 'do e-mail.')
messages.add_message(self.request, messages.ERROR, msg) messages.add_message(self.request, messages.WARNING, msg)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
return super().form_valid(form) return super().form_valid(form)
@ -1141,7 +1148,7 @@ class TramitacaoCrud(MasterDetailCrud):
msg = _('Tramitação atualizada, mas e-mail de acompanhamento ' msg = _('Tramitação atualizada, mas e-mail de acompanhamento '
'de matéria não enviado. Há problemas na configuração ' 'de matéria não enviado. Há problemas na configuração '
'do e-mail.') 'do e-mail.')
messages.add_message(self.request, messages.ERROR, msg) messages.add_message(self.request, messages.WARNING, msg)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
return super().form_valid(form) return super().form_valid(form)
@ -1683,6 +1690,7 @@ class AcompanhamentoMateriaView(CreateView):
do_envia_email_confirmacao(base_url, do_envia_email_confirmacao(base_url,
casa, casa,
"materia",
materia, materia,
destinatario) destinatario)
@ -1876,9 +1884,9 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
flag_error = True flag_error = True
if flag_error: if flag_error:
msg = _('Tramitação criada, mas e-mail de acompanhamento ' msg = _('Tramitação criada, mas e-mail de acompanhamento '
'de matéria não enviado. Há problemas na configuração ' 'de matéria não enviado. A não configuração do servidor de e-mail '
'do e-mail.') 'impede o envio de aviso de tramitação')
messages.add_message(self.request, messages.ERROR, msg) messages.add_message(self.request, messages.WARNING, msg)
status = StatusTramitacao.objects.get(id=request.POST['status']) status = StatusTramitacao.objects.get(id=request.POST['status'])

50
sapl/norma/forms.py

@ -5,17 +5,18 @@ from crispy_forms.layout import Fieldset, Layout
from django import forms from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import models from django.db import models
from django.forms import ModelForm, widgets from django.forms import ModelForm, widgets, ModelChoiceField
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import form_actions, to_row from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.settings import MAX_DOC_UPLOAD_SIZE from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import NormaPesquisaOrderingFilter, RANGE_ANOS, RangeWidgetOverride from sapl.utils import NormaPesquisaOrderingFilter, RANGE_ANOS, RangeWidgetOverride
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada, from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica) TipoNormaJuridica, AutoriaNorma)
def ANO_CHOICES(): def ANO_CHOICES():
@ -191,6 +192,51 @@ class NormaJuridicaForm(ModelForm):
return norma return norma
class AutoriaNormaForm(ModelForm):
tipo_autor = ModelChoiceField(label=_('Tipo Autor'),
required=False,
queryset=TipoAutor.objects.all(),
empty_label=_('Selecione'),)
data_relativa = forms.DateField(
widget=forms.HiddenInput(), required=False)
def __init__(self, *args, **kwargs):
super(AutoriaNormaForm, self).__init__(*args, **kwargs)
row1 = to_row([('tipo_autor', 4),
('autor', 4),
('primeiro_autor', 4)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Autoria'),
row1, 'data_relativa', form_actions(label='Salvar')))
if not kwargs['instance']:
self.fields['autor'].choices = []
class Meta:
model = AutoriaNorma
fields = ['tipo_autor', 'autor', 'primeiro_autor', 'data_relativa']
def clean(self):
cd = super(AutoriaNormaForm, self).clean()
if not self.is_valid():
return self.cleaned_data
autorias = AutoriaNorma.objects.filter(
norma=self.instance.norma, autor=cd['autor'])
pk = self.instance.pk
if ((not pk and autorias.exists()) or
(pk and autorias.exclude(pk=pk).exists())):
raise ValidationError(_('Esse Autor já foi cadastrado.'))
return cd
class AnexoNormaJuridicaForm(ModelForm): class AnexoNormaJuridicaForm(ModelForm):
class Meta: class Meta:
model = AnexoNormaJuridica model = AnexoNormaJuridica

40
sapl/norma/migrations/0014_auto_20181008_1655.py

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-10-08 19:55
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('base', '0021_appconfig_esfera_federacao'),
('norma', '0013_anexonormajuridica_assunto_anexo'),
]
operations = [
migrations.CreateModel(
name='AutoriaNorma',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('primeiro_autor', models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Primeiro Autor')),
('autor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.Autor', verbose_name='Autor')),
('norma', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='norma.NormaJuridica', verbose_name='Matéria Legislativa')),
],
options={
'ordering': ('-primeiro_autor', 'autor__nome'),
'verbose_name': 'Autoria',
'verbose_name_plural': 'Autorias',
},
),
migrations.AddField(
model_name='normajuridica',
name='autores',
field=models.ManyToManyField(through='norma.AutoriaNorma', to='base.Autor'),
),
migrations.AlterUniqueTogether(
name='autorianorma',
unique_together=set([('autor', 'norma')]),
),
]

29
sapl/norma/models.py

@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from model_utils import Choices from model_utils import Choices
import reversion import reversion
from sapl.base.models import Autor
from sapl.compilacao.models import TextoArticulado from sapl.compilacao.models import TextoArticulado
from sapl.materia.models import MateriaLegislativa from sapl.materia.models import MateriaLegislativa
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
@ -129,6 +130,12 @@ class NormaJuridica(models.Model):
auto_now=True, auto_now=True,
verbose_name=_('Data')) verbose_name=_('Data'))
autores = models.ManyToManyField(
Autor,
through='AutoriaNorma',
through_fields=('norma', 'autor'),
symmetrical=False)
class Meta: class Meta:
verbose_name = _('Norma Jurídica') verbose_name = _('Norma Jurídica')
verbose_name_plural = _('Normas Jurídicas') verbose_name_plural = _('Normas Jurídicas')
@ -184,6 +191,28 @@ class NormaJuridica(models.Model):
update_fields=update_fields) update_fields=update_fields)
@reversion.register()
class AutoriaNorma(models.Model):
autor = models.ForeignKey(Autor,
verbose_name=_('Autor'),
on_delete=models.CASCADE)
norma = models.ForeignKey(
NormaJuridica, on_delete=models.CASCADE,
verbose_name=_('Matéria Legislativa'))
primeiro_autor = models.BooleanField(verbose_name=_('Primeiro Autor'),
choices=YES_NO_CHOICES,
default=False)
class Meta:
verbose_name = _('Autoria')
verbose_name_plural = _('Autorias')
unique_together = (('autor', 'norma'), )
ordering = ('-primeiro_autor', 'autor__nome')
def __str__(self):
return _('Autoria: %(autor)s - %(norma)s') % {
'autor': self.autor, 'norma': self.norma}
@reversion.register() @reversion.register()
class LegislacaoCitada(models.Model): class LegislacaoCitada(models.Model):
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE) materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE)

5
sapl/norma/urls.py

@ -3,7 +3,7 @@ from django.conf.urls import include, url
from sapl.norma.views import (AnexoNormaJuridicaCrud,AssuntoNormaCrud, NormaCrud, NormaPesquisaView, from sapl.norma.views import (AnexoNormaJuridicaCrud,AssuntoNormaCrud, NormaCrud, NormaPesquisaView,
NormaRelacionadaCrud, NormaTaView, TipoNormaCrud, NormaRelacionadaCrud, NormaTaView, TipoNormaCrud,
TipoVinculoNormaJuridicaCrud, recuperar_norma, TipoVinculoNormaJuridicaCrud, recuperar_norma,
recuperar_numero_norma) recuperar_numero_norma, AutoriaNormaCrud)
from .apps import AppConfig from .apps import AppConfig
@ -13,7 +13,8 @@ app_name = AppConfig.name
urlpatterns = [ urlpatterns = [
url(r'^norma/', include(NormaCrud.get_urls() + url(r'^norma/', include(NormaCrud.get_urls() +
NormaRelacionadaCrud.get_urls() + NormaRelacionadaCrud.get_urls() +
AnexoNormaJuridicaCrud.get_urls())), AnexoNormaJuridicaCrud.get_urls() +
AutoriaNormaCrud.get_urls())),
# Integração com Compilação # Integração com Compilação
url(r'^norma/(?P<pk>[0-9]+)/ta$', NormaTaView.as_view(), name='norma_ta'), url(r'^norma/(?P<pk>[0-9]+)/ta$', NormaTaView.as_view(), name='norma_ta'),

37
sapl/norma/views.py

@ -21,9 +21,9 @@ from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
from sapl.utils import show_results_filter_set from sapl.utils import show_results_filter_set
from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm, from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm,
NormaPesquisaSimplesForm, NormaRelacionadaForm) NormaPesquisaSimplesForm, NormaRelacionadaForm, AutoriaNormaForm)
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada, from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, TipoVinculoNormaJuridica) TipoNormaJuridica, TipoVinculoNormaJuridica, AutoriaNorma)
# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') # LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '')
@ -274,6 +274,39 @@ def recuperar_numero_norma(request):
return response return response
class AutoriaNormaCrud(MasterDetailCrud):
model = AutoriaNorma
parent_field = 'norma'
help_topic = 'despacho_autoria'
public = [RP_LIST, RP_DETAIL]
list_field_names = ['autor', 'autor__tipo__descricao', 'primeiro_autor']
class LocalBaseMixin():
form_class = AutoriaNormaForm
@property
def layout_key(self):
return None
class CreateView(LocalBaseMixin, MasterDetailCrud.CreateView):
def get_initial(self):
initial = super().get_initial()
norma = NormaJuridica.objects.get(id=self.kwargs['pk'])
initial['data_relativa'] = norma.data
initial['autor'] = []
return initial
class UpdateView(LocalBaseMixin, MasterDetailCrud.UpdateView):
def get_initial(self):
initial = super().get_initial()
initial.update({
'data_relativa': self.object.norma.data_apresentacao,
'tipo_autor': self.object.autor.tipo.id,
})
return initial
class ImpressosView(PermissionRequiredMixin, TemplateView): class ImpressosView(PermissionRequiredMixin, TemplateView):
template_name = 'materia/impressos/impressos.html' template_name = 'materia/impressos/impressos.html'
permission_required = ('materia.can_access_impressos', ) permission_required = ('materia.can_access_impressos', )

3
sapl/protocoloadm/apps.py

@ -6,3 +6,6 @@ class AppConfig(apps.AppConfig):
name = 'sapl.protocoloadm' name = 'sapl.protocoloadm'
label = 'protocoloadm' label = 'protocoloadm'
verbose_name = _('Protocolo Administrativo') verbose_name = _('Protocolo Administrativo')
def ready(self):
from sapl.base import receivers

27
sapl/protocoloadm/forms.py

@ -2,7 +2,7 @@
import django_filters import django_filters
from crispy_forms.bootstrap import InlineRadios from crispy_forms.bootstrap import InlineRadios
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Fieldset, Layout from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout
from django import forms from django import forms
from django.core.exceptions import (MultipleObjectsReturned, from django.core.exceptions import (MultipleObjectsReturned,
ObjectDoesNotExist, ValidationError) ObjectDoesNotExist, ValidationError)
@ -19,7 +19,8 @@ from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa,
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter, from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter,
RangeWidgetOverride, autor_label, autor_modal) RangeWidgetOverride, autor_label, autor_modal)
from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo, Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo) TramitacaoAdministrativo)
@ -39,6 +40,28 @@ EM_TRAMITACAO = [('', '---------'),
(0, 'Sim'), (0, 'Sim'),
(1, 'Não')] (1, 'Não')]
class AcompanhamentoDocumentoForm(ModelForm):
class Meta:
model = AcompanhamentoDocumento
fields = ['email']
def __init__(self, *args, **kwargs):
row1 = to_row([('email', 10)])
row1.append(
Column(form_actions(label='Cadastrar'), css_class='col-md-2')
)
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
_('Acompanhamento de Documento por e-mail'), row1
)
)
super(AcompanhamentoDocumentoForm, self).__init__(*args, **kwargs)
class ProtocoloFilterSet(django_filters.FilterSet): class ProtocoloFilterSet(django_filters.FilterSet):

32
sapl/protocoloadm/migrations/0008_acompanhamentodocumento.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-09-27 15:24
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0007_auto_20180924_1724'),
]
operations = [
migrations.CreateModel(
name='AcompanhamentoDocumento',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('usuario', models.CharField(max_length=50)),
('email', models.EmailField(max_length=100, verbose_name='E-mail')),
('data_cadastro', models.DateField(auto_now_add=True)),
('hash', models.CharField(max_length=8)),
('confirmado', models.BooleanField(default=False)),
('documento', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='protocoloadm.DocumentoAdministrativo')),
],
options={
'verbose_name_plural': 'Acompanhamentos de Documento',
'verbose_name': 'Acompanhamento de Documento',
},
),
]

20
sapl/protocoloadm/migrations/0008_auto_20181009_1741.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-10-09 20:41
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0007_auto_20180924_1724'),
]
operations = [
migrations.AlterField(
model_name='protocolo',
name='interessado',
field=models.CharField(blank=True, max_length=200, verbose_name='Interessado'),
),
]

16
sapl/protocoloadm/migrations/0009_merge.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-10-10 11:10
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0008_acompanhamentodocumento'),
('protocoloadm', '0008_auto_20181009_1741'),
]
operations = [
]

29
sapl/protocoloadm/models.py

@ -67,7 +67,7 @@ class Protocolo(models.Model):
blank=True, null=True, verbose_name=_('Tipo de Protocolo')) blank=True, null=True, verbose_name=_('Tipo de Protocolo'))
tipo_processo = models.PositiveIntegerField() tipo_processo = models.PositiveIntegerField()
interessado = models.CharField( interessado = models.CharField(
max_length=60, blank=True, verbose_name=_('Interessado')) max_length=200, blank=True, verbose_name=_('Interessado'))
autor = models.ForeignKey(Autor, autor = models.ForeignKey(Autor,
blank=True, blank=True,
null=True, null=True,
@ -298,3 +298,30 @@ class TramitacaoAdministrativo(models.Model):
return _('%(documento)s - %(status)s') % { return _('%(documento)s - %(status)s') % {
'documento': self.documento, 'status': self.status 'documento': self.documento, 'status': self.status
} }
@reversion.register()
class AcompanhamentoDocumento(models.Model):
usuario = models.CharField(max_length=50)
documento = models.ForeignKey(DocumentoAdministrativo, on_delete=models.CASCADE)
email = models.EmailField(
max_length=100, verbose_name=_('E-mail'))
data_cadastro = models.DateField(auto_now_add=True)
hash = models.CharField(max_length=8)
confirmado = models.BooleanField(default=False)
class Meta:
verbose_name = _('Acompanhamento de Documento')
verbose_name_plural = _('Acompanhamentos de Documento')
def __str__(self):
if self.data_cadastro is None:
return _('%(documento)s - %(email)s') % {
'documento': self.documento,
'email': self.email
}
else:
return _('%(documento)s - %(email)s - Registrado em: %(data)s') % {
'documento': self.documento,
'email': self.email,
'data': str(self.data_cadastro.strftime('%d/%m/%Y'))
}

65
sapl/protocoloadm/tests/test_docadm_email_templates.py

@ -0,0 +1,65 @@
from django.core import mail
from sapl.base.email_utils import enviar_emails, load_email_templates
def test_email_template_loading():
expected = "<html><body>Hello Django</body></html>"
emails = load_email_templates(['email/test_tramitacao.html'],
context={"name": "Django"})
# strip \n and \r to compare with expected
actual = emails[0].replace('\n', '').replace('\r', '')
assert actual == expected
def test_html_email_body_with_materia():
templates = load_email_templates(['email/tramitacao.txt',
'email/tramitacao.html'],
{"image": 'img/logo.png',
"casa_legislativa":
"Assembléia Parlamentar",
"data_registro": "25/02/2016",
"cod_materia": "1",
"descricao_materia": "Assunto de teste",
"data": "25/02/2016",
"status": "Arquivado",
"texto_acao": "Deliberado",
"hash_txt": "abc01f",
"materia_id": "794",
"base_url": "http://localhost:8000",
"materia_url":
"/docadm/764/acompanhar-documento",
"excluir_url":
"/docadm/764/acompanhar-excluir"})
assert len(templates) == 2
def test_enviar_email_distintos():
NUM_MESSAGES = 10
messages = [{'recipient': 'user-' + str(i) + '@test.com',
'subject': 'subject: ' + str(i),
'txt_message': 'txt: ' + str(i),
'html_message': '<html></html>',
} for i in range(NUM_MESSAGES)]
recipients = [m['recipient'] for m in messages]
enviar_emails('test@sapl.com', recipients, messages)
assert len(mail.outbox) == NUM_MESSAGES
def test_enviar_same_email():
NUM_MESSAGES = 10
messages = [{'recipient': 'user-' + str(i) + '@test.com',
'subject': 'subject: ' + str(i),
'txt_message': 'txt: ' + str(i),
'html_message': '<html></html>',
} for i in range(NUM_MESSAGES)]
recipients = [m['recipient'] for m in messages]
enviar_emails('test@sapl.com', recipients, [messages[0]])
assert len(mail.outbox) == 1

14
sapl/protocoloadm/urls.py

@ -1,6 +1,9 @@
from django.conf.urls import include, url from django.conf.urls import include, url
from sapl.protocoloadm.views import (AnularProtocoloAdmView, from sapl.protocoloadm.views import (AcompanhamentoDocumentoView,
AcompanhamentoConfirmarView,
AcompanhamentoExcluirView,
AnularProtocoloAdmView,
ComprovanteProtocoloView, ComprovanteProtocoloView,
CriarDocumentoProtocolo, CriarDocumentoProtocolo,
DocumentoAcessorioAdministrativoCrud, DocumentoAcessorioAdministrativoCrud,
@ -56,6 +59,15 @@ urlpatterns_protocolo = [
url(r'^protocoloadm/(?P<pk>\d+)/protocolo-mostrar$', url(r'^protocoloadm/(?P<pk>\d+)/protocolo-mostrar$',
ProtocoloMostrarView.as_view(), name='protocolo_mostrar'), ProtocoloMostrarView.as_view(), name='protocolo_mostrar'),
url(r'^docadm/(?P<pk>\d+)/acompanhar-documento/$',
AcompanhamentoDocumentoView.as_view(), name='acompanhar_documento'),
url(r'^docadm/(?P<pk>\d+)/acompanhar-confirmar$',
AcompanhamentoConfirmarView.as_view(),
name='acompanhar_confirmar'),
url(r'^docadm/(?P<pk>\d+)/acompanhar-excluir$',
AcompanhamentoExcluirView.as_view(),
name='acompanhar_excluir'),
url(r'^protocoloadm/(?P<pk>\d+)/continuar$', url(r'^protocoloadm/(?P<pk>\d+)/continuar$',

184
sapl/protocoloadm/views.py

@ -1,3 +1,6 @@
from datetime import datetime
from random import choice
from string import ascii_letters, digits
from braces.views import FormValidMessageMixin from braces.views import FormValidMessageMixin
from django.contrib import messages from django.contrib import messages
@ -18,25 +21,30 @@ from django.views.generic.edit import FormView
from django_filters.views import FilterView from django_filters.views import FilterView
import sapl import sapl
from sapl.base.models import Autor from sapl.base.models import Autor, CasaLegislativa
from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao
from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.parlamentares.models import Legislatura, Parlamentar from sapl.parlamentares.models import Legislatura, Parlamentar
from sapl.protocoloadm.models import Protocolo from sapl.protocoloadm.models import Protocolo
from sapl.utils import (create_barcode, get_client_ip, from sapl.utils import (create_barcode, get_base_url, get_client_ip,
get_mime_type_from_file_extension, get_mime_type_from_file_extension,
show_results_filter_set) show_results_filter_set)
from sapl.base.email_utils import do_envia_email_confirmacao
from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm, from .forms import (AcompanhamentoDocumentoForm, AnularProcoloAdmForm,
DocumentoAcessorioAdministrativoForm,
DocumentoAdministrativoFilterSet, DocumentoAdministrativoFilterSet,
DocumentoAdministrativoForm, ProtocoloDocumentForm, DocumentoAdministrativoForm, ProtocoloDocumentForm,
ProtocoloFilterSet, ProtocoloMateriaForm, ProtocoloFilterSet, ProtocoloMateriaForm,
TramitacaoAdmEditForm, TramitacaoAdmForm, DesvincularDocumentoForm, DesvincularMateriaForm, TramitacaoAdmEditForm, TramitacaoAdmForm,
filtra_tramitacao_adm_destino_and_status, filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status) DesvincularDocumentoForm, DesvincularMateriaForm,
from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, filtra_tramitacao_adm_destino_and_status,
StatusTramitacaoAdministrativo, filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status)
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo, StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo, TramitacaoAdministrativo) TipoDocumentoAdministrativo, TramitacaoAdministrativo)
from sapl.base.signals import tramitacao_signal
TipoDocumentoAdministrativoCrud = CrudAux.build( TipoDocumentoAdministrativoCrud = CrudAux.build(
TipoDocumentoAdministrativo, '') TipoDocumentoAdministrativo, '')
@ -89,6 +97,136 @@ def doc_texto_integral(request, pk):
return response return response
raise Http404 raise Http404
class AcompanhamentoConfirmarView(TemplateView):
def get_redirect_url(self, email):
msg = _('Este documento está sendo acompanhado pelo e-mail: %s') % (
email)
messages.add_message(self.request, messages.SUCCESS, msg)
return reverse('sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': self.kwargs['pk']})
def get(self, request, *args, **kwargs):
documento_id = kwargs['pk']
hash_txt = request.GET.get('hash_txt', '')
try:
acompanhar = AcompanhamentoDocumento.objects.get(
documento_id=documento_id,
hash=hash_txt)
except ObjectDoesNotExist:
raise Http404()
# except MultipleObjectsReturned:
# A melhor solução deve ser permitir que a exceção
# (MultipleObjectsReturned) seja lançada e vá para o log,
# pois só poderá ser causada por um erro de desenvolvimente
acompanhar.confirmado = True
acompanhar.save()
return HttpResponseRedirect(self.get_redirect_url(acompanhar.email))
class AcompanhamentoExcluirView(TemplateView):
def get_success_url(self):
msg = _('Você parou de acompanhar este Documento.')
messages.add_message(self.request, messages.INFO, msg)
return reverse('sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': self.kwargs['pk']})
def get(self, request, *args, **kwargs):
documento_id = kwargs['pk']
hash_txt = request.GET.get('hash_txt', '')
try:
AcompanhamentoDocumento.objects.get(documento_id=documento_id,
hash=hash_txt).delete()
except ObjectDoesNotExist:
pass
return HttpResponseRedirect(self.get_success_url())
class AcompanhamentoDocumentoView(CreateView):
template_name = "protocoloadm/acompanhamento_documento.html"
def get_random_chars(self):
s = ascii_letters + digits
return ''.join(choice(s) for i in range(choice([6, 7])))
def get(self, request, *args, **kwargs):
pk = self.kwargs['pk']
documento = DocumentoAdministrativo.objects.get(id=pk)
return self.render_to_response(
{'form': AcompanhamentoDocumentoForm(),
'documento': documento})
def post(self, request, *args, **kwargs):
form = AcompanhamentoDocumentoForm(request.POST)
pk = self.kwargs['pk']
documento = DocumentoAdministrativo.objects.get(id=pk)
if form.is_valid():
email = form.cleaned_data['email']
usuario = request.user
hash_txt = self.get_random_chars()
acompanhar = AcompanhamentoDocumento.objects.get_or_create(
documento=documento,
email=form.data['email'])
# Se o segundo elemento do retorno do get_or_create for True
# quer dizer que o elemento não existia
if acompanhar[1]:
acompanhar = acompanhar[0]
acompanhar.hash = hash_txt
acompanhar.usuario = usuario.username
acompanhar.confirmado = False
acompanhar.save()
base_url = get_base_url(request)
destinatario = AcompanhamentoDocumento.objects.get(
documento=documento,
email=email,
confirmado=False)
casa = CasaLegislativa.objects.first()
do_envia_email_confirmacao(base_url,
casa,
"documento",
documento,
destinatario)
msg = _('Foi enviado um e-mail de confirmação. Confira sua caixa \
de mensagens e clique no link que nós enviamos para \
confirmar o acompanhamento deste documento.')
messages.add_message(request, messages.SUCCESS, msg)
# Caso esse Acompanhamento já exista
# avisa ao usuário que esse documento já está sendo acompanhado
else:
msg = _('Este e-mail já está acompanhando esse documento.')
messages.add_message(request, messages.INFO, msg)
return self.render_to_response(
{'form': form,
'documento': documento,
'error': _('Esse documento já está\
sendo acompanhada por este e-mail.')})
return HttpResponseRedirect(self.get_success_url())
else:
return self.render_to_response(
{'form': form,
'documento': documento})
def get_success_url(self):
return reverse('sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': self.kwargs['pk']})
class DocumentoAdministrativoMixin: class DocumentoAdministrativoMixin:
@ -686,8 +824,38 @@ class TramitacaoAdmCrud(MasterDetailCrud):
'unidade_tramitacao_local'].widget.attrs['disabled'] = True 'unidade_tramitacao_local'].widget.attrs['disabled'] = True
return context return context
def form_valid(self, form):
self.object = form.save()
try:
tramitacao_signal.send(sender=TramitacaoAdministrativo,
post=self.object,
request=self.request)
except Exception as e:
# TODO log error
msg = _('Tramitação criada, mas e-mail de acompanhamento '
'de documento não enviado. A não configuração do'
' servidor de e-mail impede o envio de aviso de tramitação')
messages.add_message(self.request, messages.WARNING, msg)
return HttpResponseRedirect(self.get_success_url())
return super().form_valid(form)
class UpdateView(MasterDetailCrud.UpdateView): class UpdateView(MasterDetailCrud.UpdateView):
form_class = TramitacaoAdmEditForm form_class = TramitacaoAdmEditForm
def form_valid(self, form):
self.object = form.save()
try:
tramitacao_signal.send(sender=TramitacaoAdministrativo,
post=self.object,
request=self.request)
except Exception as e:
# TODO log error
msg = _('Tramitação criada, mas e-mail de acompanhamento '
'de documento não enviado. A não configuração do'
' servidor de e-mail impede o envio de aviso de tramitação')
messages.add_message(self.request, messages.WARNING, msg)
return HttpResponseRedirect(self.get_success_url())
return super().form_valid(form)
class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView): class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView):

14
sapl/relatorios/templates/pdf_pauta_sessao_gerar.py

@ -122,8 +122,10 @@ def expediente_materia(lst_expediente_materia):
tmp += '<blockTable style="repeater" repeatRows="1">\n' tmp += '<blockTable style="repeater" repeatRows="1">\n'
tmp += '<tr><td >Matéria</td><td>Ementa</td><td>Situação</td></tr>\n' tmp += '<tr><td >Matéria</td><td>Ementa</td><td>Situação</td></tr>\n'
for expediente_materia in lst_expediente_materia: for expediente_materia in lst_expediente_materia:
tmp += '<tr><td><para style="P3"><b>' + str(expediente_materia['num_ordem']) + '</b> - ' + expediente_materia[ tmp += '<tr><td><para style="P3"><b>' + str(expediente_materia['num_ordem']) + '</b> - ' + \
'id_materia'] + '</para>\n' + '<para style="P3"><b>Autor: </b>' + expediente_materia['nom_autor'] + '</para></td>\n' expediente_materia["tipo_materia"] + ' No. ' + \
expediente_materia['id_materia'] + '</para>\n' + '<para style="P3"><b>Autor: </b>' + \
expediente_materia['nom_autor'] + '</para></td>\n'
txt_ementa = expediente_materia['txt_ementa'].replace('&', '&amp;') txt_ementa = expediente_materia['txt_ementa'].replace('&', '&amp;')
tmp += '<td><para style="P4">' + txt_ementa + '</para></td>\n' tmp += '<td><para style="P4">' + txt_ementa + '</para></td>\n'
tmp += '<td><para style="P3">' + \ tmp += '<td><para style="P3">' + \
@ -145,8 +147,12 @@ def votacao(lst_votacao):
tmp += '<blockTable style="repeater" repeatRows="1">\n' tmp += '<blockTable style="repeater" repeatRows="1">\n'
tmp += '<tr><td >Matéria</td><td >Ementa</td><td>Situação</td></tr>\n' tmp += '<tr><td >Matéria</td><td >Ementa</td><td>Situação</td></tr>\n'
for votacao in lst_votacao: for votacao in lst_votacao:
tmp += '<tr><td><para style="P3"><b>' + str(votacao['num_ordem']) + '</b> - ' + str(votacao['id_materia']) + '</para>\n' + '<para style="P3"><b>Processo: </b>' + str(votacao[ tmp += '<tr><td><para style="P3"><b>' + str(votacao['num_ordem']) + '</b> - ' + \
'des_numeracao']) + '</para>\n' + '<para style="P3"><b>Turno: </b>' + str(votacao['des_turno']) + '</para>\n' + '<para style="P3"><b>Autor: </b>' + str(votacao['nom_autor']) + '</para></td>\n' votacao["tipo_materia"] + ' No. ' + \
str(votacao['id_materia']) + '</para>\n' + '<para style="P3"><b>Processo: </b>' + \
str(votacao['des_numeracao']) + '</para>\n' + '<para style="P3"><b>Turno: </b>' + \
str(votacao['des_turno']) + '</para>\n' + '<para style="P3"><b>Autor: </b>' + \
str(votacao['nom_autor']) + '</para></td>\n'
tmp += '<td><para style="P4">' + \ tmp += '<td><para style="P4">' + \
str(votacao['txt_ementa']) + '</para></td>\n' str(votacao['txt_ementa']) + '</para></td>\n'
tmp += '<td><para style="P3">' + \ tmp += '<td><para style="P3">' + \

28
sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py

@ -6,6 +6,9 @@
""" """
import time import time
from django.template.defaultfilters import safe
from django.utils.html import strip_tags
from sapl.sessao.models import ResumoOrdenacao from sapl.sessao.models import ResumoOrdenacao
from trml2pdf import parseString from trml2pdf import parseString
@ -284,7 +287,25 @@ def oradores(lst_oradores):
return tmp return tmp
def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_mesa, lst_presenca_sessao, lst_expedientes, lst_expediente_materia, lst_oradores_expediente, lst_presenca_ordem_dia, lst_votacao, lst_oradores): def ocorrencias(lst_ocorrencias):
"""
"""
tmp = ''
tmp += '\t\t<para style="P1">Ocorrências da Sessão</para>\n'
tmp += '\t\t<para style="P2">\n'
tmp += '\t\t\t<font color="white"> </font>\n'
tmp += '\t\t</para>\n'
for idx, ocorrencia in enumerate(lst_ocorrencias):
tmp += '\t\t<para style="P3">' + \
str(ocorrencia.conteudo) + '</para>\n'
tmp += '\t\t<para style="P2">\n'
tmp += '\t\t\t<font color="white"> </font>\n'
tmp += '\t\t</para>\n'
return tmp
def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_mesa, lst_presenca_sessao, lst_expedientes, lst_expediente_materia, lst_oradores_expediente, lst_presenca_ordem_dia, lst_votacao, lst_oradores, lst_ocorrencias):
""" """
""" """
arquivoPdf = str(int(time.time() * 100)) + ".pdf" arquivoPdf = str(int(time.time() * 100)) + ".pdf"
@ -316,7 +337,8 @@ def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_me
'mat_o_d': votacao(lst_votacao), 'mat_o_d': votacao(lst_votacao),
'mesa_d': mesa(lst_mesa), 'mesa_d': mesa(lst_mesa),
'oradores_exped': oradores_expediente(lst_oradores_expediente), 'oradores_exped': oradores_expediente(lst_oradores_expediente),
'oradores_expli': oradores(lst_oradores) 'oradores_expli': oradores(lst_oradores),
'ocorr_sessao': ocorrencias(lst_ocorrencias)
} }
if ordenacao: if ordenacao:
@ -330,6 +352,7 @@ def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_me
tmp += dict_ord_template[ordenacao.oitavo] tmp += dict_ord_template[ordenacao.oitavo]
tmp += dict_ord_template[ordenacao.nono] tmp += dict_ord_template[ordenacao.nono]
tmp += dict_ord_template[ordenacao.decimo] tmp += dict_ord_template[ordenacao.decimo]
else: else:
tmp += inf_basicas(inf_basicas_dic) tmp += inf_basicas(inf_basicas_dic)
tmp += mesa(lst_mesa) tmp += mesa(lst_mesa)
@ -340,6 +363,7 @@ def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_me
tmp += presenca_ordem_dia(lst_presenca_ordem_dia) tmp += presenca_ordem_dia(lst_presenca_ordem_dia)
tmp += votacao(lst_votacao) tmp += votacao(lst_votacao)
tmp += oradores(lst_oradores) tmp += oradores(lst_oradores)
tmp += ocorrencias(lst_ocorrencias)
tmp += '\t</story>\n' tmp += '\t</story>\n'
tmp += '</document>\n' tmp += '</document>\n'

39
sapl/relatorios/views.py

@ -17,7 +17,7 @@ from sapl.protocoloadm.models import (DocumentoAdministrativo, Protocolo,
from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao, from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao,
IntegranteMesa, Orador, OradorExpediente, IntegranteMesa, Orador, OradorExpediente,
OrdemDia, PresencaOrdemDia, SessaoPlenaria, OrdemDia, PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca) SessaoPlenariaPresenca, OcorrenciaSessao)
from sapl.settings import STATIC_ROOT from sapl.settings import STATIC_ROOT
from sapl.utils import LISTA_DE_UFS, ExtraiTag, TrocaTag, filiacao_data from sapl.utils import LISTA_DE_UFS, ExtraiTag, TrocaTag, filiacao_data
@ -514,13 +514,13 @@ def get_sessao_plenaria(sessao, casa):
dic_presenca['sgl_partido'] = partido_sigla dic_presenca['sgl_partido'] = partido_sigla
lst_presenca_sessao.append(dic_presenca) lst_presenca_sessao.append(dic_presenca)
# Exibe os Expedientes # Exibe os Expedientes
lst_expedientes = [] lst_expedientes = []
expedientes = ExpedienteSessao.objects.filter( expedientes = ExpedienteSessao.objects.filter(
sessao_plenaria=sessao).order_by('tipo__nome') sessao_plenaria=sessao).order_by('tipo__nome')
for e in expedientes: for e in expedientes:
dic_expedientes = {} dic_expedientes = {}
dic_expedientes["nom_expediente"] = e.tipo.nome dic_expedientes["nom_expediente"] = e.tipo.nome
conteudo = e.conteudo conteudo = e.conteudo
@ -539,6 +539,7 @@ def get_sessao_plenaria(sessao, casa):
if dic_expedientes: if dic_expedientes:
lst_expedientes.append(dic_expedientes) lst_expedientes.append(dic_expedientes)
# Lista das matérias do Expediente, incluindo o resultado das votacoes # Lista das matérias do Expediente, incluindo o resultado das votacoes
lst_expediente_materia = [] lst_expediente_materia = []
for expediente_materia in ExpedienteMateria.objects.filter( for expediente_materia in ExpedienteMateria.objects.filter(
@ -727,6 +728,28 @@ def get_sessao_plenaria(sessao, casa):
dic_oradores['sgl_partido'] = sigla dic_oradores['sgl_partido'] = sigla
lst_oradores.append(dic_oradores) lst_oradores.append(dic_oradores)
# Ocorrências da Sessão
lst_ocorrencias = []
ocorrencias = OcorrenciaSessao.objects.filter(
sessao_plenaria=sessao)
for o in ocorrencias:
conteudo = o.conteudo
# unescape HTML codes
# https://github.com/interlegis/sapl/issues/1046
conteudo = re.sub('style=".*?"', '', conteudo)
conteudo = html.unescape(conteudo)
# escape special character '&'
# https://github.com/interlegis/sapl/issues/1009
conteudo = conteudo.replace('&', '&amp;')
o.conteudo = conteudo
lst_ocorrencias.append(o)
return (inf_basicas_dic, return (inf_basicas_dic,
lst_mesa, lst_mesa,
lst_presenca_sessao, lst_presenca_sessao,
@ -735,7 +758,8 @@ def get_sessao_plenaria(sessao, casa):
lst_oradores_expediente, lst_oradores_expediente,
lst_presenca_ordem_dia, lst_presenca_ordem_dia,
lst_votacao, lst_votacao,
lst_oradores) lst_oradores,
lst_ocorrencias)
def get_turno(dic, materia, sessao_data_inicio): def get_turno(dic, materia, sessao_data_inicio):
@ -785,7 +809,9 @@ def relatorio_sessao_plenaria(request, pk):
lst_oradores_expediente, lst_oradores_expediente,
lst_presenca_ordem_dia, lst_presenca_ordem_dia,
lst_votacao, lst_votacao,
lst_oradores) = get_sessao_plenaria(sessao, casa) lst_oradores,
lst_ocorrencias) = get_sessao_plenaria(sessao, casa)
for idx in range(len(lst_expedientes)): for idx in range(len(lst_expedientes)):
txt_expedientes = lst_expedientes[idx]['txt_expediente'] txt_expedientes = lst_expedientes[idx]['txt_expediente']
@ -806,7 +832,8 @@ def relatorio_sessao_plenaria(request, pk):
lst_oradores_expediente, lst_oradores_expediente,
lst_presenca_ordem_dia, lst_presenca_ordem_dia,
lst_votacao, lst_votacao,
lst_oradores) lst_oradores,
lst_ocorrencias)
response.write(pdf) response.write(pdf)
return response return response
@ -1034,6 +1061,7 @@ def get_pauta_sessao(sessao, casa):
id=expediente_materia.materia.id).first() id=expediente_materia.materia.id).first()
dic_expediente_materia = {} dic_expediente_materia = {}
dic_expediente_materia["tipo_materia"] = materia.tipo.sigla + ' - ' + materia.tipo.descricao
dic_expediente_materia["num_ordem"] = str( dic_expediente_materia["num_ordem"] = str(
expediente_materia.numero_ordem) expediente_materia.numero_ordem)
dic_expediente_materia["id_materia"] = str( dic_expediente_materia["id_materia"] = str(
@ -1086,6 +1114,7 @@ def get_pauta_sessao(sessao, casa):
id=votacao.materia.id).first() id=votacao.materia.id).first()
dic_votacao = {} dic_votacao = {}
dic_votacao["tipo_materia"] = materia.tipo.sigla + ' - ' + materia.tipo.descricao
dic_votacao["num_ordem"] = votacao.numero_ordem dic_votacao["num_ordem"] = votacao.numero_ordem
dic_votacao["id_materia"] = str( dic_votacao["id_materia"] = str(
materia.numero) + "/" + str(materia.ano) materia.numero) + "/" + str(materia.ano)

44
sapl/rules/map_rules.py

@ -1,23 +1,3 @@
from sapl.base import models as base
from sapl.comissoes import models as comissoes
from sapl.compilacao import models as compilacao
from sapl.lexml import models as lexml
from sapl.materia import models as materia
from sapl.norma import models as norma
from sapl.painel import models as painel
from sapl.parlamentares import models as parlamentares
from sapl.protocoloadm import models as protocoloadm
from sapl.audiencia import models as audiencia
from sapl.rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL, RP_LIST,
SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_ANONYMOUS,
SAPL_GROUP_AUTOR, SAPL_GROUP_COMISSOES,
SAPL_GROUP_GERAL, SAPL_GROUP_LOGIN_SOCIAL,
SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,
SAPL_GROUP_PAINEL, SAPL_GROUP_PARLAMENTAR,
SAPL_GROUP_PROTOCOLO, SAPL_GROUP_SESSAO,
SAPL_GROUP_VOTANTE)
from sapl.sessao import models as sessao
""" """
Todas as permissões do django framework seguem o padrão Todas as permissões do django framework seguem o padrão
@ -46,6 +26,26 @@ negócio trabalham com os cinco radiais de permissão
e com qualquer outro tipo de permissão customizada, nesta ordem de precedência. e com qualquer outro tipo de permissão customizada, nesta ordem de precedência.
""" """
from sapl.audiencia import models as audiencia
from sapl.base import models as base
from sapl.comissoes import models as comissoes
from sapl.compilacao import models as compilacao
from sapl.lexml import models as lexml
from sapl.materia import models as materia
from sapl.norma import models as norma
from sapl.painel import models as painel
from sapl.parlamentares import models as parlamentares
from sapl.protocoloadm import models as protocoloadm
from sapl.rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL, RP_LIST,
SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_ANONYMOUS,
SAPL_GROUP_AUTOR, SAPL_GROUP_COMISSOES,
SAPL_GROUP_GERAL, SAPL_GROUP_LOGIN_SOCIAL,
SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,
SAPL_GROUP_PAINEL, SAPL_GROUP_PARLAMENTAR,
SAPL_GROUP_PROTOCOLO, SAPL_GROUP_SESSAO,
SAPL_GROUP_VOTANTE)
from sapl.sessao import models as sessao
__base__ = [RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE] __base__ = [RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE]
__listdetailchange__ = [RP_LIST, RP_DETAIL, RP_CHANGE] __listdetailchange__ = [RP_LIST, RP_DETAIL, RP_CHANGE]
@ -116,6 +116,7 @@ rules_group_materia = {
(materia.Numeracao, __base__), (materia.Numeracao, __base__),
(materia.Tramitacao, __base__), (materia.Tramitacao, __base__),
(norma.LegislacaoCitada, __base__), (norma.LegislacaoCitada, __base__),
(norma.AutoriaNorma, __base__),
(compilacao.Dispositivo, __base__ + [ (compilacao.Dispositivo, __base__ + [
'change_dispositivo_edicao_dinamica', 'change_dispositivo_edicao_dinamica',
@ -136,6 +137,7 @@ rules_group_norma = {
(norma.NormaJuridica, __base__), (norma.NormaJuridica, __base__),
(norma.NormaRelacionada, __base__), (norma.NormaRelacionada, __base__),
(norma.AnexoNormaJuridica, __base__), (norma.AnexoNormaJuridica, __base__),
(norma.AutoriaNorma, __base__),
# Publicacao está com permissão apenas para norma e não para matéria # Publicacao está com permissão apenas para norma e não para matéria
# e proposições apenas por análise do contexto, não é uma limitação # e proposições apenas por análise do contexto, não é uma limitação
@ -159,6 +161,7 @@ rules_group_sessao = {
(sessao.SessaoPlenaria, __base__), (sessao.SessaoPlenaria, __base__),
(sessao.SessaoPlenariaPresenca, __base__), (sessao.SessaoPlenariaPresenca, __base__),
(sessao.ExpedienteMateria, __base__), (sessao.ExpedienteMateria, __base__),
(sessao.OcorrenciaSessao, __base__),
(sessao.IntegranteMesa, __base__), (sessao.IntegranteMesa, __base__),
(sessao.ExpedienteSessao, __base__), (sessao.ExpedienteSessao, __base__),
(sessao.Orador, __base__), (sessao.Orador, __base__),
@ -302,6 +305,7 @@ rules_group_anonymous = {
'group': SAPL_GROUP_ANONYMOUS, 'group': SAPL_GROUP_ANONYMOUS,
'rules': [ 'rules': [
(materia.AcompanhamentoMateria, [RP_ADD, RP_DELETE]), (materia.AcompanhamentoMateria, [RP_ADD, RP_DELETE]),
(protocoloadm.AcompanhamentoDocumento, [RP_ADD, RP_DELETE]),
] ]
} }

4
sapl/rules/tests/test_rules.py

@ -11,6 +11,7 @@ from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
TipoDispositivo, TipoDispositivo,
TipoDispositivoRelationship) TipoDispositivoRelationship)
from sapl.materia.models import AcompanhamentoMateria from sapl.materia.models import AcompanhamentoMateria
from sapl.protocoloadm.models import AcompanhamentoDocumento
from sapl.rules import SAPL_GROUPS, map_rules from sapl.rules import SAPL_GROUPS, map_rules
from sapl.test_urls import create_perms_post_migrate from sapl.test_urls import create_perms_post_migrate
from scripts.lista_permissions_in_decorators import \ from scripts.lista_permissions_in_decorators import \
@ -61,6 +62,7 @@ __fp__in__test_permission_of_models_in_rules_patterns = {
PerfilEstruturalTextoArticulado], PerfilEstruturalTextoArticulado],
map_rules.RP_CHANGE: [AcompanhamentoMateria, map_rules.RP_CHANGE: [AcompanhamentoMateria,
AcompanhamentoDocumento,
TipoDispositivo, TipoDispositivo,
TipoDispositivoRelationship, TipoDispositivoRelationship,
PerfilEstruturalTextoArticulado], PerfilEstruturalTextoArticulado],
@ -71,11 +73,13 @@ __fp__in__test_permission_of_models_in_rules_patterns = {
PerfilEstruturalTextoArticulado], PerfilEstruturalTextoArticulado],
map_rules.RP_LIST: [AcompanhamentoMateria, map_rules.RP_LIST: [AcompanhamentoMateria,
AcompanhamentoDocumento,
TipoDispositivo, TipoDispositivo,
TipoDispositivoRelationship, TipoDispositivoRelationship,
PerfilEstruturalTextoArticulado], PerfilEstruturalTextoArticulado],
map_rules.RP_DETAIL: [AcompanhamentoMateria, map_rules.RP_DETAIL: [AcompanhamentoMateria,
AcompanhamentoDocumento,
TipoDispositivo, TipoDispositivo,
TipoDispositivoRelationship, TipoDispositivoRelationship,
PerfilEstruturalTextoArticulado] PerfilEstruturalTextoArticulado]

17
sapl/sessao/forms.py

@ -20,9 +20,11 @@ from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES,
MateriaPesquisaOrderingFilter, autor_label, MateriaPesquisaOrderingFilter, autor_label,
autor_modal, timezone) autor_modal, timezone)
from .models import (Bancada, Bloco, ExpedienteMateria, JustificativaAusencia, from .models import (Bancada, Bloco, ExpedienteMateria, JustificativaAusencia,
Orador,OradorExpediente, OrdemDia, SessaoPlenaria, Orador, OradorExpediente, OrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, TipoJustificativa, TipoResultadoVotacao) SessaoPlenariaPresenca, TipoJustificativa, TipoResultadoVotacao,
OcorrenciaSessao)
def recupera_anos(): def recupera_anos():
@ -54,7 +56,8 @@ ORDENACAO_RESUMO = [('cont_mult', 'Conteúdo Multimídia'),
('mat_o_d', 'Matérias da Ordem do Dia'), ('mat_o_d', 'Matérias da Ordem do Dia'),
('mesa_d', 'Mesa Diretora'), ('mesa_d', 'Mesa Diretora'),
('oradores_exped', 'Oradores do Expediente'), ('oradores_exped', 'Oradores do Expediente'),
('oradores_expli', 'Oradores das Explicações Pessoais')] ('oradores_expli', 'Oradores das Explicações Pessoais'),
('ocorrencia_sessao', 'Ocorrências da Sessão')]
class SessaoPlenariaForm(ModelForm): class SessaoPlenariaForm(ModelForm):
@ -412,6 +415,12 @@ class MesaForm(forms.Form):
class ExpedienteForm(forms.Form): class ExpedienteForm(forms.Form):
conteudo = forms.CharField(required=False, widget=forms.Textarea) conteudo = forms.CharField(required=False, widget=forms.Textarea)
class OcorrenciaSessaoForm(ModelForm):
class Meta:
model = OcorrenciaSessao
fields = ['conteudo']
class VotacaoForm(forms.Form): class VotacaoForm(forms.Form):
votos_sim = forms.CharField(label='Sim') votos_sim = forms.CharField(label='Sim')

28
sapl/sessao/migrations/0024_ocorrenciasessao.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2018-09-18 13:44
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sessao', '0023_auto_20180914_1315'),
]
operations = [
migrations.CreateModel(
name='OcorrenciaSessao',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('conteudo', models.TextField(blank=True, verbose_name='Ocorrências da Sessão Plenária')),
('sessao_plenaria', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sessao.SessaoPlenaria')),
],
options={
'verbose_name_plural': 'Ocorrências da Sessão Plenaria',
'verbose_name': 'Ocorrência da Sessão Plenaria',
},
),
]

21
sapl/sessao/migrations/0025_auto_20180919_1116.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-09-19 14:16
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sessao', '0024_ocorrenciasessao'),
]
operations = [
migrations.AlterField(
model_name='ocorrenciasessao',
name='sessao_plenaria',
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='sessao.SessaoPlenaria'),
),
]

66
sapl/sessao/migrations/0026_auto_20181016_1944.py

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-10-16 22:44
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sessao', '0025_auto_20180919_1116'),
]
operations = [
migrations.AlterField(
model_name='expedientesessao',
name='sessao_plenaria',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sessao.SessaoPlenaria'),
),
migrations.AlterField(
model_name='integrantemesa',
name='sessao_plenaria',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sessao.SessaoPlenaria'),
),
migrations.AlterField(
model_name='orador',
name='sessao_plenaria',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sessao.SessaoPlenaria'),
),
migrations.AlterField(
model_name='oradorexpediente',
name='sessao_plenaria',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sessao.SessaoPlenaria'),
),
migrations.AlterField(
model_name='presencaordemdia',
name='sessao_plenaria',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sessao.SessaoPlenaria'),
),
migrations.AlterField(
model_name='registrovotacao',
name='expediente',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.ExpedienteMateria'),
),
migrations.AlterField(
model_name='registrovotacao',
name='materia',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='materia.MateriaLegislativa'),
),
migrations.AlterField(
model_name='registrovotacao',
name='ordem',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.OrdemDia'),
),
migrations.AlterField(
model_name='sessaoplenaria',
name='sessao_legislativa',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='parlamentares.SessaoLegislativa', verbose_name='Sessão Legislativa'),
),
migrations.AlterField(
model_name='sessaoplenariapresenca',
name='sessao_plenaria',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sessao.SessaoPlenaria'),
),
]

39
sapl/sessao/models.py

@ -121,7 +121,7 @@ class SessaoPlenaria(models.Model):
verbose_name=_('Tipo')) verbose_name=_('Tipo'))
sessao_legislativa = models.ForeignKey( sessao_legislativa = models.ForeignKey(
SessaoLegislativa, SessaoLegislativa,
on_delete=models.PROTECT, on_delete=models.CASCADE,
verbose_name=_('Sessão Legislativa')) verbose_name=_('Sessão Legislativa'))
legislatura = models.ForeignKey(Legislatura, legislatura = models.ForeignKey(Legislatura,
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -288,7 +288,7 @@ class TipoExpediente(models.Model):
@reversion.register() @reversion.register()
class ExpedienteSessao(models.Model): # ExpedienteSessaoPlenaria class ExpedienteSessao(models.Model): # ExpedienteSessaoPlenaria
sessao_plenaria = models.ForeignKey(SessaoPlenaria, sessao_plenaria = models.ForeignKey(SessaoPlenaria,
on_delete=models.PROTECT) on_delete=models.CASCADE)
tipo = models.ForeignKey(TipoExpediente, on_delete=models.PROTECT) tipo = models.ForeignKey(TipoExpediente, on_delete=models.PROTECT)
conteudo = models.TextField( conteudo = models.TextField(
blank=True, verbose_name=_('Conteúdo do expediente')) blank=True, verbose_name=_('Conteúdo do expediente'))
@ -301,10 +301,25 @@ class ExpedienteSessao(models.Model): # ExpedienteSessaoPlenaria
return '%s - %s' % (self.tipo, self.sessao_plenaria) return '%s - %s' % (self.tipo, self.sessao_plenaria)
@reversion.register()
class OcorrenciaSessao(models.Model): # OcorrenciaSessaoPlenaria
sessao_plenaria = models.OneToOneField(SessaoPlenaria,
on_delete=models.PROTECT)
conteudo = models.TextField(
blank=True, verbose_name=_('Ocorrências da Sessão Plenária'))
class Meta:
verbose_name = _('Ocorrência da Sessão Plenaria')
verbose_name_plural = _('Ocorrências da Sessão Plenaria')
def __str__(self):
return '%s - %s' % (self.sessao_plenaria, self.conteudo)
@reversion.register() @reversion.register()
class IntegranteMesa(models.Model): # MesaSessaoPlenaria class IntegranteMesa(models.Model): # MesaSessaoPlenaria
sessao_plenaria = models.ForeignKey(SessaoPlenaria, sessao_plenaria = models.ForeignKey(SessaoPlenaria,
on_delete=models.PROTECT) on_delete=models.CASCADE)
cargo = models.ForeignKey(CargoMesa, on_delete=models.PROTECT) cargo = models.ForeignKey(CargoMesa, on_delete=models.PROTECT)
parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT) parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT)
@ -319,7 +334,7 @@ class IntegranteMesa(models.Model): # MesaSessaoPlenaria
@reversion.register() @reversion.register()
class AbstractOrador(models.Model): # Oradores class AbstractOrador(models.Model): # Oradores
sessao_plenaria = models.ForeignKey(SessaoPlenaria, sessao_plenaria = models.ForeignKey(SessaoPlenaria,
on_delete=models.PROTECT) on_delete=models.CASCADE)
parlamentar = models.ForeignKey(Parlamentar, parlamentar = models.ForeignKey(Parlamentar,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Parlamentar')) verbose_name=_('Parlamentar'))
@ -372,7 +387,7 @@ class OrdemDia(AbstractOrdemDia):
@reversion.register() @reversion.register()
class PresencaOrdemDia(models.Model): # OrdemDiaPresenca class PresencaOrdemDia(models.Model): # OrdemDiaPresenca
sessao_plenaria = models.ForeignKey(SessaoPlenaria, sessao_plenaria = models.ForeignKey(SessaoPlenaria,
on_delete=models.PROTECT) on_delete=models.CASCADE)
parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT) parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT)
class Meta: class Meta:
@ -412,15 +427,15 @@ class RegistroVotacao(models.Model):
TipoResultadoVotacao, TipoResultadoVotacao,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Resultado da Votação')) verbose_name=_('Resultado da Votação'))
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.PROTECT) materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE)
ordem = models.ForeignKey(OrdemDia, ordem = models.ForeignKey(OrdemDia,
blank=True, blank=True,
null=True, null=True,
on_delete=models.PROTECT) on_delete=models.CASCADE)
expediente = models.ForeignKey(ExpedienteMateria, expediente = models.ForeignKey(ExpedienteMateria,
blank=True, blank=True,
null=True, null=True,
on_delete=models.PROTECT) on_delete=models.CASCADE)
numero_votos_sim = models.PositiveIntegerField(verbose_name=_('Sim')) numero_votos_sim = models.PositiveIntegerField(verbose_name=_('Sim'))
numero_votos_nao = models.PositiveIntegerField(verbose_name=_('Não')) numero_votos_nao = models.PositiveIntegerField(verbose_name=_('Não'))
numero_abstencoes = models.PositiveIntegerField( numero_abstencoes = models.PositiveIntegerField(
@ -461,7 +476,7 @@ class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar
''' '''
votacao = models.ForeignKey(RegistroVotacao, votacao = models.ForeignKey(RegistroVotacao,
blank=True, blank=True,
null=True) null=True,on_delete=models.CASCADE)
parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT) parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT)
voto = models.CharField(max_length=10) voto = models.CharField(max_length=10)
@ -481,10 +496,10 @@ class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar
ordem = models.ForeignKey(OrdemDia, ordem = models.ForeignKey(OrdemDia,
blank=True, blank=True,
null=True) null=True, on_delete=models.CASCADE)
expediente = models.ForeignKey(ExpedienteMateria, expediente = models.ForeignKey(ExpedienteMateria,
blank=True, blank=True,
null=True) null=True, on_delete=models.CASCADE)
class Meta: class Meta:
verbose_name = _('Registro de Votação de Parlamentar') verbose_name = _('Registro de Votação de Parlamentar')
@ -498,7 +513,7 @@ class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar
@reversion.register() @reversion.register()
class SessaoPlenariaPresenca(models.Model): class SessaoPlenariaPresenca(models.Model):
sessao_plenaria = models.ForeignKey(SessaoPlenaria, sessao_plenaria = models.ForeignKey(SessaoPlenaria,
on_delete=models.PROTECT) on_delete=models.CASCADE)
parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT) parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT)
data_sessao = models.DateField(blank=True, null=True) data_sessao = models.DateField(blank=True, null=True)

132
sapl/sessao/tests/test_sessao.py

@ -5,11 +5,14 @@ from django.utils.translation import ugettext_lazy as _
from model_mommy import mommy from model_mommy import mommy
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.parlamentares.models import Legislatura, Partido, SessaoLegislativa from sapl.parlamentares.models import Legislatura, Parlamentar, Partido,SessaoLegislativa
from sapl.sessao import forms from sapl.sessao import forms
from sapl.sessao.models import (ExpedienteMateria, OrdemDia, RegistroVotacao, from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao,
SessaoPlenaria, TipoSessaoPlenaria) IntegranteMesa, Orador, OrdemDia,
PresencaOrdemDia, RegistroVotacao,
SessaoPlenaria, SessaoPlenariaPresenca,
TipoResultadoVotacao, TipoSessaoPlenaria,
VotoParlamentar)
def test_valida_campos_obrigatorios_sessao_plenaria_form(): def test_valida_campos_obrigatorios_sessao_plenaria_form():
form = forms.SessaoPlenariaForm(data={}) form = forms.SessaoPlenariaForm(data={})
@ -170,3 +173,124 @@ def test_registro_votacao_tem_ordem_xor_expediente():
# a validação NÃO funciona quando ambos são preenchidos # a validação NÃO funciona quando ambos são preenchidos
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
registro_votacao_com(ordem, expediente).full_clean() registro_votacao_com(ordem, expediente).full_clean()
def create_sessao_plenaria():
legislatura = mommy.make(Legislatura)
sessao = mommy.make(SessaoLegislativa)
tipo = mommy.make(TipoSessaoPlenaria)
return mommy.make(SessaoPlenaria,
legislatura=legislatura,
sessao_legislativa=sessao,
tipo=tipo,
numero=1)
def create_materia_legislativa():
tipo_materia = mommy.make(TipoMateriaLegislativa)
return mommy.make(MateriaLegislativa, tipo=tipo_materia)
@pytest.mark.django_db(transaction=False)
def test_delete_sessao_plenaria_cascade_registro_votacao_ordemdia():
materia = create_materia_legislativa()
sessao_plenaria = create_sessao_plenaria()
ordem = mommy.make(OrdemDia,
sessao_plenaria=sessao_plenaria,
materia=materia,
tipo_votacao='2')
tipo_resultado_votacao = mommy.make(TipoResultadoVotacao,
nome='ok',
natureza="A")
registro = mommy.make(RegistroVotacao,
tipo_resultado_votacao=tipo_resultado_votacao,
materia=materia,
ordem=ordem)
presenca = mommy.make(PresencaOrdemDia,
sessao_plenaria=sessao_plenaria)
parlamentar = mommy.make(Parlamentar)
voto_parlamentar = mommy.make(VotoParlamentar,
votacao=registro,
parlamentar=parlamentar,
ordem=ordem)
sessao_plenaria.delete()
presenca_filter = PresencaOrdemDia.objects.filter(
sessao_plenaria=sessao_plenaria).exists()
ordem_filter = OrdemDia.objects.filter(
sessao_plenaria=sessao_plenaria).exists()
registro_filter = RegistroVotacao.objects.filter(
tipo_resultado_votacao=tipo_resultado_votacao,
materia=materia,
ordem=ordem).exists()
materia_filter = MateriaLegislativa.objects.filter(id=materia.id).exists()
parlamentar_filter = Parlamentar.objects.exists()
voto_parlamentar_filter = VotoParlamentar.objects.filter(
ordem=ordem).exists()
assert not registro_filter
assert not ordem_filter
assert not presenca_filter
assert not voto_parlamentar_filter
assert materia_filter # Não exclui materia
assert parlamentar_filter # Não exclui Parlamentar
@pytest.mark.django_db(transaction=False)
def test_delete_sessao_plenaria_cascade_registro_votacao_expediente():
materia = create_materia_legislativa()
sessao_plenaria = create_sessao_plenaria()
expediente = mommy.make(ExpedienteMateria,
sessao_plenaria=sessao_plenaria,
materia=materia,
tipo_votacao='2')
tipo_resultado_votacao = mommy.make(TipoResultadoVotacao,
nome='ok',
natureza="A")
registro = mommy.make(RegistroVotacao,
tipo_resultado_votacao=tipo_resultado_votacao,
materia=materia,
expediente=expediente)
presenca = mommy.make(SessaoPlenariaPresenca,
sessao_plenaria=sessao_plenaria)
parlamentar = mommy.make(Parlamentar)
voto_parlamentar = mommy.make(VotoParlamentar,
votacao=registro,
parlamentar=parlamentar,
expediente=expediente)
sessao_plenaria.delete()
expediente_filter = ExpedienteMateria.objects.filter(
sessao_plenaria=sessao_plenaria).exists()
registro_filter = RegistroVotacao.objects.filter(
tipo_resultado_votacao=tipo_resultado_votacao,
materia=materia,
expediente=expediente).exists()
presenca_filter = SessaoPlenariaPresenca.objects.filter(
sessao_plenaria=sessao_plenaria).exists()
parlamentar_filter = Parlamentar.objects.exists()
voto_parlamentar_filter = VotoParlamentar.objects.filter(
expediente=expediente).exists()
assert not registro_filter
assert not expediente_filter
assert not presenca_filter
assert not voto_parlamentar_filter
assert parlamentar_filter # Não exclui Parlamentar
@pytest.mark.django_db(transaction=False)
def test_delete_sessao_plenaria_cascade_integrante_mesa():
sessao_plenaria = create_sessao_plenaria()
mesa = mommy.make(IntegranteMesa,sessao_plenaria=sessao_plenaria)
sessao_plenaria.delete()
mesa_filter = IntegranteMesa.objects.filter(sessao_plenaria=sessao_plenaria).exists()
assert not mesa_filter
@pytest.mark.django_db(transaction=False)
def test_delete_sessao_plenaria_cascade_expedientesessao():
sessao_plenaria = create_sessao_plenaria()
expediente_sessao = mommy.make(ExpedienteSessao, sessao_plenaria=sessao_plenaria)
sessao_plenaria.delete()
expediente_sessao_filter = ExpedienteSessao.objects.filter(sessao_plenaria=sessao_plenaria).exists()
assert not expediente_sessao_filter
@pytest.mark.django_db(transaction=False)
def test_delete_sessao_plenaria_cascade_orador():
sessao_plenaria = create_sessao_plenaria()
expediente_sessao = mommy.make(Orador, sessao_plenaria=sessao_plenaria)
sessao_plenaria.delete()
orador_filter = Orador.objects.filter(sessao_plenaria=sessao_plenaria).exists()
assert not orador_filter

10
sapl/sessao/urls.py

@ -3,9 +3,9 @@ from django.conf.urls import include, url
from sapl.sessao.views import (AdicionarVariasMateriasExpediente, from sapl.sessao.views import (AdicionarVariasMateriasExpediente,
AdicionarVariasMateriasOrdemDia, BancadaCrud, AdicionarVariasMateriasOrdemDia, BancadaCrud,
BlocoCrud, CargoBancadaCrud, BlocoCrud, CargoBancadaCrud,
ExpedienteMateriaCrud, ExpedienteView, ExpedienteMateriaCrud, ExpedienteView, JustificativaAusenciaCrud,
JustificativaAusenciaCrud, MateriaOrdemDiaCrud, MesaView, OcorrenciaSessaoView, MateriaOrdemDiaCrud, MesaView, OradorCrud,
OradorCrud, OradorExpedienteCrud, PainelView, OradorExpedienteCrud, PainelView,
PautaSessaoDetailView, PautaSessaoView, PautaSessaoDetailView, PautaSessaoView,
PesquisarPautaSessaoView, PesquisarPautaSessaoView,
PesquisarSessaoPlenariaView, PesquisarSessaoPlenariaView,
@ -103,6 +103,8 @@ urlpatterns = [
# Subnav sessão # Subnav sessão
url(r'^sessao/(?P<pk>\d+)/expediente$', url(r'^sessao/(?P<pk>\d+)/expediente$',
ExpedienteView.as_view(), name='expediente'), ExpedienteView.as_view(), name='expediente'),
url(r'^sessao/(?P<pk>\d+)/ocorrencia_sessao$',
OcorrenciaSessaoView.as_view(), name='ocorrencia_sessao'),
url(r'^sessao/(?P<pk>\d+)/presenca$', url(r'^sessao/(?P<pk>\d+)/presenca$',
PresencaView.as_view(), name='presenca'), PresencaView.as_view(), name='presenca'),
url(r'^sessao/(?P<pk>\d+)/painel$', url(r'^sessao/(?P<pk>\d+)/painel$',
@ -113,7 +115,7 @@ urlpatterns = [
url(r'^sessao/(?P<pk>\d+)/resumo$', url(r'^sessao/(?P<pk>\d+)/resumo$',
ResumoView.as_view(), name='resumo'), ResumoView.as_view(), name='resumo'),
url(r'^sessao/(?P<pk>\d+)/resumo_ata$', url(r'^sessao/(?P<pk>\d+)/resumo_ata$',
ResumoAtaView.as_view(), name='resumo_ata'), ResumoAtaView.as_view(), name='resumo_ata'),
url(r'^sessao/pesquisar-sessao$', url(r'^sessao/pesquisar-sessao$',
PesquisarSessaoPlenariaView.as_view(), name='pesquisar_sessao'), PesquisarSessaoPlenariaView.as_view(), name='pesquisar_sessao'),
url(r'^sessao/(?P<pk>\d+)/matordemdia/votnom/(?P<oid>\d+)/(?P<mid>\d+)$', url(r'^sessao/(?P<pk>\d+)/matordemdia/votnom/(?P<oid>\d+)/(?P<mid>\d+)$',

76
sapl/sessao/views.py

@ -37,14 +37,14 @@ from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm
from sapl.utils import show_results_filter_set, remover_acentos from sapl.utils import show_results_filter_set, remover_acentos
from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, BlocoForm, from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, BlocoForm,
JustificativaAusenciaForm, ExpedienteForm, ListMateriaForm, MesaForm, ExpedienteForm, JustificativaAusenciaForm, OcorrenciaSessaoForm, ListMateriaForm,
OradorExpedienteForm, OradorForm, PautaSessaoFilterSet, MesaForm, OradorExpedienteForm, OradorForm, PautaSessaoFilterSet,
PresencaForm, ResumoOrdenacaoForm, SessaoPlenariaFilterSet, PresencaForm, ResumoOrdenacaoForm, SessaoPlenariaFilterSet,
SessaoPlenariaForm, VotacaoEditForm, VotacaoForm, SessaoPlenariaForm, VotacaoEditForm, VotacaoForm,
VotacaoNominalForm) VotacaoNominalForm)
from .models import (Bancada, Bloco, CargoBancada, CargoMesa, from .models import (Bancada, Bloco, CargoBancada, CargoMesa, ExpedienteMateria,
ExpedienteMateria, ExpedienteSessao, JustificativaAusencia, ExpedienteSessao, JustificativaAusencia, OcorrenciaSessao, IntegranteMesa,
IntegranteMesa, MateriaLegislativa, Orador, OradorExpediente, OrdemDia, MateriaLegislativa, Orador, OradorExpediente, OrdemDia,
PresencaOrdemDia, RegistroVotacao, ResumoOrdenacao, PresencaOrdemDia, RegistroVotacao, ResumoOrdenacao,
SessaoPlenaria, SessaoPlenariaPresenca, TipoExpediente, SessaoPlenaria, SessaoPlenariaPresenca, TipoExpediente,
TipoJustificativa, TipoResultadoVotacao, TipoSessaoPlenaria, TipoJustificativa, TipoResultadoVotacao, TipoSessaoPlenaria,
@ -1177,7 +1177,8 @@ class ResumoOrdenacaoView(PermissionRequiredMixin, FormView):
'setimo': ordenacao.setimo, 'setimo': ordenacao.setimo,
'oitavo': ordenacao.oitavo, 'oitavo': ordenacao.oitavo,
'nono': ordenacao.nono, 'nono': ordenacao.nono,
'decimo': ordenacao.decimo}) 'decimo': ordenacao.decimo,
'decimo_primeiro': ordenacao.decimo_primeiro})
return initial return initial
def form_valid(self, form): def form_valid(self, form):
@ -1193,6 +1194,7 @@ class ResumoOrdenacaoView(PermissionRequiredMixin, FormView):
ordenacao.oitavo = form.cleaned_data['oitavo'] ordenacao.oitavo = form.cleaned_data['oitavo']
ordenacao.nono = form.cleaned_data['nono'] ordenacao.nono = form.cleaned_data['nono']
ordenacao.decimo = form.cleaned_data['decimo'] ordenacao.decimo = form.cleaned_data['decimo']
ordenacao.decimo_primeiro = form.cleaned_data['decimo_primeiro']
ordenacao.save() ordenacao.save()
@ -1282,6 +1284,7 @@ class ResumoView(DetailView):
ex = {'tipo': tipo, 'conteudo': conteudo} ex = {'tipo': tipo, 'conteudo': conteudo}
expedientes.append(ex) expedientes.append(ex)
context.update({'expedientes': expedientes}) context.update({'expedientes': expedientes})
# ===================================================================== # =====================================================================
# Matérias Expediente # Matérias Expediente
materias = ExpedienteMateria.objects.filter( materias = ExpedienteMateria.objects.filter(
@ -1415,6 +1418,12 @@ class ResumoView(DetailView):
oradores_explicacoes.append(oradores) oradores_explicacoes.append(oradores)
context.update({'oradores_explicacoes': oradores_explicacoes}) context.update({'oradores_explicacoes': oradores_explicacoes})
# =====================================================================
# Ocorrẽncias da Sessão
ocorrencias_sessao = OcorrenciaSessao.objects.filter(sessao_plenaria_id=self.object.id)
context.update({'ocorrencias_da_sessao': ocorrencias_sessao})
# ===================================================================== # =====================================================================
# Indica a ordem com a qual o template será renderizado # Indica a ordem com a qual o template será renderizado
ordenacao = ResumoOrdenacao.objects.first() ordenacao = ResumoOrdenacao.objects.first()
@ -1428,7 +1437,8 @@ class ResumoView(DetailView):
'mat_o_d': 'materias_ordem_dia.html', 'mat_o_d': 'materias_ordem_dia.html',
'mesa_d': 'mesa_diretora.html', 'mesa_d': 'mesa_diretora.html',
'oradores_exped': 'oradores_expediente.html', 'oradores_exped': 'oradores_expediente.html',
'oradores_expli': 'oradores_explicacoes.html' 'oradores_expli': 'oradores_explicacoes.html',
'ocorr_sessao': 'ocorrencias_da_sessao.html'
} }
if ordenacao: if ordenacao:
@ -1454,12 +1464,16 @@ class ResumoView(DetailView):
'setimo_ordenacao': dict_ord_template['oradores_exped'], 'setimo_ordenacao': dict_ord_template['oradores_exped'],
'oitavo_ordenacao': dict_ord_template['lista_p_o_d'], 'oitavo_ordenacao': dict_ord_template['lista_p_o_d'],
'nono_ordenacao': dict_ord_template['mat_o_d'], 'nono_ordenacao': dict_ord_template['mat_o_d'],
'decimo_ordenacao': dict_ord_template['oradores_expli']}) 'decimo_ordenacao': dict_ord_template['oradores_expli'],
'decimo_primeiro_ordenacao': dict_ord_template['ocorr_sessao']})
return self.render_to_response(context) return self.render_to_response(context)
class ResumoAtaView(ResumoView): class ResumoAtaView(ResumoView):
template_name = 'sessao/resumo_ata.html' template_name = 'sessao/resumo_ata.html'
class ExpedienteView(FormMixin, DetailView): class ExpedienteView(FormMixin, DetailView):
template_name = 'sessao/expediente.html' template_name = 'sessao/expediente.html'
form_class = ExpedienteForm form_class = ExpedienteForm
@ -1539,6 +1553,52 @@ class ExpedienteView(FormMixin, DetailView):
return reverse('sapl.sessao:expediente', kwargs={'pk': pk}) return reverse('sapl.sessao:expediente', kwargs={'pk': pk})
class OcorrenciaSessaoView(FormMixin, DetailView):
template_name = 'sessao/ocorrencia_sessao.html'
form_class = OcorrenciaSessaoForm
model = SessaoPlenaria
def delete(self):
OcorrenciaSessao.objects.filter(sessao_plenaria=self.object).delete()
msg = _('Registro deletado com sucesso')
messages.add_message(self.request, messages.SUCCESS, msg)
def save(self,form):
conteudo = form.cleaned_data['conteudo']
OcorrenciaSessao.objects.filter(sessao_plenaria=self.object).delete()
ocorrencia = OcorrenciaSessao()
ocorrencia.sessao_plenaria_id = self.object.id
ocorrencia.conteudo = conteudo
ocorrencia.save()
msg = _('Registro salvo com sucesso')
messages.add_message(self.request, messages.SUCCESS, msg)
@method_decorator(permission_required('sessao.add_ocorrenciasessao'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = OcorrenciaSessaoForm(request.POST)
if not form.is_valid():
return self.form_invalid(form)
if request.POST.get('delete'):
self.delete()
elif request.POST.get('save'):
self.save(form)
return self.form_valid(form)
def get_success_url(self):
pk = self.kwargs['pk']
return reverse('sapl.sessao:ocorrencia_sessao', kwargs={'pk': pk})
class VotacaoEditView(SessaoPermissionMixin): class VotacaoEditView(SessaoPermissionMixin):
''' '''

171
sapl/templates/404.html

@ -0,0 +1,171 @@
{% load i18n staticfiles sass_tags menus %}
{% load common_tags %}
<!DOCTYPE html>
<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" lang="pt-br">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block head_content %}
<link rel="icon" href="{% static 'img/favicon.ico' %}" type="image/png" >
{# Styles #}
<link rel="stylesheet" href="{% static 'components-font-awesome/css/font-awesome.css' %}">
<link rel="stylesheet" href="{% sass_src 'bootstrap-sass/assets/stylesheets/_bootstrap.scss' %}" type="text/css">
<link rel="stylesheet" href="{% static 'drunken-parrot-flat-ui/css/drunken-parrot.css' %}">
<link rel="stylesheet" href="{% sass_src 'styles/app.scss' %}" type="text/css">
<link rel="stylesheet" href="{% static 'jquery-ui/themes/cupertino/jquery-ui.min.css' %}">
<script type="text/javascript" src="{% static 'jquery/dist/jquery.min.js' %}"></script>
{# Scripts #}
{# modernizr must be in head (see http://modernizr.com/docs/#installing) #}
{% endblock %}
</head>
<body>
<div class="page fadein">
{% if not request|has_iframe %}
{% block navigation %}
<nav class="navbar navbar-inverse navbar-static-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div id="navbar" class="navbar-collapse collapse">
</div><!--/.nav-collapse -->
</div>
</nav>
{% endblock navigation %}
{# Header #}
{% block main_header %}
<header class="masthead">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">
<img src="{% if logotipo %}{{ MEDIA_URL }}{{ logotipo }}{% else %}{% static 'img/logo.png' %}{% endif %}"
alt="Logo" class="img-responsive visible-md-inline-block visible-lg-inline-block" >
<span class="vcenter">
{# XXX Make better use of translation tags in html blocks ie. actually use the proper blocktrans tag efficiently #}
<br/><small>{% trans 'Sistema de Apoio ao Processo Legislativo' %}</small>
</span>
</a>
</div>
<div class="hidden-print">
{% block sections_nav %} {% subnav %} {% endblock sections_nav %}
</div>
</div>
</header>
{% endblock main_header %}
{% else %}
<div class="btn-cancel-iframe">
<a href="?iframe=0" target="_blank"><i class="fa fa-2x fa-arrows-alt"></i></a>
</div>
<header class="masthead">
<div class="container">
<div class="hidden-print">
{% subnav %}
</div>
</div>
</header>
{% endif %}
<div class="container">
<span class="text-center">
<br/><h1><big>{% trans 'Página não encontrada! Erro 404' %}</big></h1>
</span>
</div>
{% block base_content %}
{% endblock %}
{% if not request|has_iframe %}
{% block footer_container %}
<footer id="footer" class="footer page__row hidden-print">
<div class="container">
<div class="row">
<div class="col-md-4">
<a class="footer__logo" href="#">
<a href="http://www.interlegis.leg.br/">
<img src="{% static 'img/logo_interlegis.png' %}" alt="{% trans 'Logo do Interlegis' %} ">
</a>
</a>
<p>
<small>
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small>
</p>
</div>
<div class="col-md-4">
<a class="footer__logo" href="#">
<img src="{% static 'img/logo_cc.png' %}" alt="{% trans 'Logo do Creative Commons BY SA' %}">
</a>
<p>
<small>
Conteúdo e dados sob licença <a href="https://creativecommons.org">Creative Commons</a> 4.0 <a href="https://creativecommons.org/licenses/by/4.0/">Atribuir Fonte - Compartilhar Igual</a>
</small>
</p>
</div>
</div>
</div>
</footer>
</div>
{% endblock footer_container %}
{% endif %}
{% block foot_js %}
<!-- Bootstrap core JavaScript ================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="{% static 'bootstrap-sass/assets/javascripts/bootstrap.min.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery-ui/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery-ui/ui/i18n/datepicker-pt-BR.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.runner.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery-mask-plugin/dist/jquery.mask.js' %}"></script>
<script src="{% static 'tinymce/tinymce.min.js' %}"></script>
<script type="text/javascript" src="{% static 'jsdiff/diff.min.js' %}"></script>
<script type="text/javascript" src="{% static 'drunken-parrot-flat-ui/js/checkbox.js' %}"></script>
<script type="text/javascript" src="{% static 'drunken-parrot-flat-ui/js/radio.js' %}"></script>
<script type="text/javascript" src="{% static 'js/app.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery-query-object/jquery.query-object.js' %}"></script>
{% block extra_js %}{% endblock %}
<script type="text/javascript" >
function inIframe () {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
$(document).ready(function(){
let iframe_set_backend = {{ request|has_iframe|lower }}
if (iframe_set_backend && !inIframe() ) {
location.href = location.origin + '?iframe=0'
}
});
</script>
{% endblock foot_js %}
</body>
</html>

135
sapl/templates/500.html

@ -0,0 +1,135 @@
{% load i18n staticfiles sass_tags menus %}
{% load common_tags %}
<!DOCTYPE html>
<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" lang="pt-br">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block head_content %}
<link rel="icon" href="{% static 'img/favicon.ico' %}" type="image/png" >
{# Styles #}
<link rel="stylesheet" href="{% static 'components-font-awesome/css/font-awesome.css' %}">
<link rel="stylesheet" href="{% sass_src 'bootstrap-sass/assets/stylesheets/_bootstrap.scss' %}" type="text/css">
<link rel="stylesheet" href="{% static 'drunken-parrot-flat-ui/css/drunken-parrot.css' %}">
<link rel="stylesheet" href="{% sass_src 'styles/app.scss' %}" type="text/css">
<link rel="stylesheet" href="{% static 'jquery-ui/themes/cupertino/jquery-ui.min.css' %}">
<script type="text/javascript" src="{% static 'jquery/dist/jquery.min.js' %}"></script>
{# Scripts #}
{# modernizr must be in head (see http://modernizr.com/docs/#installing) #}
{% endblock %}
</head>
<body>
<div class="page fadein">
{% block navigation %}
<nav class="navbar navbar-inverse navbar-static-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div id="navbar" class="navbar-collapse collapse">
</div><!--/.nav-collapse -->
</div>
</nav>
{% endblock navigation %}
{# Header #}
{% block main_header %}
<header class="masthead">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">
<span class="text-center">
<br/><small>{% trans 'Sistema de Apoio ao Processo Legislativo' %}</small>
</span>
</a>
</div>
<div class="hidden-print">
{% block sections_nav %} {% subnav %} {% endblock sections_nav %}
</div>
</div>
</header>
{% endblock main_header %}
<div class="container">
<span class="text-center">
<br/><h1><big>{% trans 'Ocorreu um erro inesperado! Erro 500' %}</big></h1>
</span>
</div>
{% block base_content %}
{% endblock %}
{% block footer_container %}
<footer id="footer" class="footer page__row hidden-print">
<div class="container">
<div class="row">
<div class="col-md-4">
<a class="footer__logo" href="#">
<a href="http://www.interlegis.leg.br/">
<img src="{% static 'img/logo_interlegis.png' %}" alt="{% trans 'Logo do Interlegis' %} ">
</a>
</a>
<p>
<small>
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small>
</p>
</div>
<div class="col-md-4">
<a class="footer__logo" href="#">
<img src="{% static 'img/logo_cc.png' %}" alt="{% trans 'Logo do Creative Commons BY SA' %}">
</a>
<p>
<small>
Conteúdo e dados sob licença <a href="https://creativecommons.org">Creative Commons</a> 4.0 <a href="https://creativecommons.org/licenses/by/4.0/">Atribuir Fonte - Compartilhar Igual</a>
</small>
</p>
</div>
</div>
</div>
</footer>
</div>
{% endblock footer_container %}
{% block foot_js %}
<!-- Bootstrap core JavaScript ================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="{% static 'bootstrap-sass/assets/javascripts/bootstrap.min.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery-ui/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery-ui/ui/i18n/datepicker-pt-BR.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.runner.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery-mask-plugin/dist/jquery.mask.js' %}"></script>
<script src="{% static 'tinymce/tinymce.min.js' %}"></script>
<script type="text/javascript" src="{% static 'jsdiff/diff.min.js' %}"></script>
<script type="text/javascript" src="{% static 'drunken-parrot-flat-ui/js/checkbox.js' %}"></script>
<script type="text/javascript" src="{% static 'drunken-parrot-flat-ui/js/radio.js' %}"></script>
<script type="text/javascript" src="{% static 'js/app.js' %}"></script>
<script type="text/javascript" src="{% static 'jquery-query-object/jquery.query-object.js' %}"></script>
{% block extra_js %}{% endblock %}
{% endblock foot_js %}
</body>
</html>

10
sapl/templates/base.html

@ -1,6 +1,6 @@
<!DOCTYPE html>
{% load i18n staticfiles sass_tags menus %} {% load i18n staticfiles sass_tags menus %}
{% load common_tags %} {% load common_tags %}
<!DOCTYPE html>
<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!--[if gt IE 8]><!--> <!--[if gt IE 8]><!-->
<html class="no-js" lang="pt-br"> <html class="no-js" lang="pt-br">
@ -29,7 +29,7 @@
<body> <body>
<div class="page fadein"> <div class="page fadein">
{% if not request|has_iframe %} {% if not request|has_iframe %}
{% block navigation %} {% block navigation %}
<nav class="navbar navbar-inverse navbar-static-top"> <nav class="navbar navbar-inverse navbar-static-top">
@ -184,7 +184,7 @@
<small> <small>
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto. Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small> </small>
<span>Release: 3.1.121</span> <span>Release: 3.1.128</span>
</p> </p>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
@ -252,7 +252,7 @@
<script type="text/javascript" > <script type="text/javascript" >
function inIframe () { function inIframe () {
try { try {
return window.self !== window.top; return window.self !== window.top;
@ -264,7 +264,7 @@
let iframe_set_backend = {{ request|has_iframe|lower }} let iframe_set_backend = {{ request|has_iframe|lower }}
if (iframe_set_backend && !inIframe() ) { if (iframe_set_backend && !inIframe() ) {
location.href = location.origin + '?iframe=0' location.href = location.origin + '?iframe=0'
} }
}); });
</script> </script>

9
sapl/templates/compilacao/dispositivo_form_search_fragment.html

@ -1,5 +1,14 @@
{% load i18n compilacao_filters %} {% load i18n compilacao_filters %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}{{ message.tags }}{% endif %} alert-dismissible fade in" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
{{ message|safe }}
</div>
{% endfor %}
{% if object_list.count >= 100 %} {% if object_list.count >= 100 %}
<div class="alert-box success radius"> <div class="alert-box success radius">
{% trans 'Use argumentos para simplificar listagem...' %} {% trans 'Use argumentos para simplificar listagem...' %}

10
sapl/templates/compilacao/messages.html

@ -0,0 +1,10 @@
{% load i18n compilacao_filters %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}{{ message.tags }}{% endif %} alert-dismissible fade in" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
{{ message|safe }}
</div>
{% endfor %}

16
sapl/templates/crud/detail.html

@ -65,7 +65,21 @@
<div id="div_id_{{ column.id }}" class="form-group"> <div id="div_id_{{ column.id }}" class="form-group">
<p class="control-label">{{ column.verbose_name }}</p> <p class="control-label">{{ column.verbose_name }}</p>
<div class="controls"> <div class="controls">
{% if column.text|url %} {% if column.text|audio_url %}
<div class="form-control-static">
<audio controls>
<source src="{{ column.text|safe }}" type="audio/{{ column.text|file_extension }}">
<p>Este navegador não suporta o elemento áudio.</p>
</audio>
</div>
{% elif column.text|video_url %}
<div class="form-control-static">
<video width="320" height="120" controls>
<source src="{{ column.text|safe }}" type="video/{{ column.text|file_extension }}">
<p>Este navegador não suporta o elemento vídeo.</p>
</video>
</div>
{% elif column.text|url %}
<div class="form-control-static"><a href="{{ column.text|safe }}"> {{ column.text|safe|default:"" }} </a></div> <div class="form-control-static"><a href="{{ column.text|safe }}"> {{ column.text|safe|default:"" }} </a></div>
{% else %} {% else %}
<div class="form-control-static">{{ column.text|safe|default:"" }}</div> <div class="form-control-static">{{ column.text|safe|default:"" }}</div>

25
sapl/templates/email/acompanhar_documento.html

@ -0,0 +1,25 @@
{% load i18n %}
{% load static %}
<html><head></head><body bgcolor='#ffffff'>
<h2 align='center'><b>{{casa_legislativa}}</b>
<br/>
Sistema de Apoio ao Processo Legislativo
</h2>
<p>Registramos seu pedido para acompanhamento por e-mail do documento administrativo identificado a seguir:</p>
<a href="{{base_url}}{{documento_url}}">{{documento}}<b> - {{descricao_documento}}</b></a><br/>
{{assunto}}<br/>
</h4>
<p></p>
<p>Para garantia de sua privacidade, solicitamos que ative o recebimento das futuras mensagens clicando no link:</p>
<h4>
<a href="{{base_url}}{{confirmacao_url}}?hash_txt={{hash_txt}}">{{base_url}}{{confirmacao_url}}?hash_txt={{hash_txt}}</a>
</h4>
<br/>
<hr>
<p>Caso não tenha realizado o cadastramento em nosso sistema, favor desconsiderar a presente mensagem<br/>
Esta é uma mensagem automática. Por favor, não responda.</p>
</body>
</html>

16
sapl/templates/email/acompanhar_documento.txt

@ -0,0 +1,16 @@
{{casa_legislativa}}
Sistema de Apoio ao Processo Legislativo
>Registramos seu pedido para acompanhamento por e-mail do documento administrativo identificado a seguir:
{{base_url}}{{documento_url}} - {{documento}} - {{descricao_documento}}
{{assunto}}
Para garantia de sua privacidade, solicitamos que ative o recebimento das futuras mensagens acessando no link:
{{base_url}}{{url_confirmar}}?hash_txt={{hash_txt}}
Caso não tenha realizado o cadastramento em nosso sistema, favor desconsiderar a presente mensagem
Esta é uma mensagem automática. Por favor, não responda.

2
sapl/templates/email/tramitacao.html

@ -13,10 +13,12 @@
<h4> <h4>
<a href="{{base_url}}{{materia_url}}"><b>{{materia}} - {{descricao_materia}}</b></a> <a href="{{base_url}}{{materia_url}}"><b>{{materia}} - {{descricao_materia}}</b></a>
<br/><br/> <br/><br/>
{% if autoria %}
<b>Autoria:</b></br> <b>Autoria:</b></br>
{% for autor in autoria %} {% for autor in autoria %}
{{ autor }}</br> {{ autor }}</br>
{% endfor %} {% endfor %}
{% endif %}
</h4> </h4>
<p></p> <p></p>
<p> <p>

4
sapl/templates/email/tramitacao.txt

@ -8,12 +8,12 @@ A seguinte matéria, de seu interesse, sofreu Tramitação registrada em {{data_
Matéria: {{materia}} - {{descricao_materia}} Matéria: {{materia}} - {{descricao_materia}}
{{url_materia}} {{url_materia}}
{% if autoria %}
Autoria: Autoria:
{% for autor in autoria %} {% for autor in autoria %}
{{ autor }} {{ autor }}
{% endfor %} {% endfor %}
{% endif %}
Data da ação: {{data}} Data da ação: {{data}}
Status: {{status}} Status: {{status}}

15
sapl/templates/materia/materialegislativa_detail.html

@ -10,6 +10,21 @@
{% endblock sub_actions %} {% endblock sub_actions %}
{% block detail_content %} {% block detail_content %}
{{ block.super }} {{ block.super }}
{% if object.registrovotacao_set.exists %}
<strong>Data Votação:</strong>
{% for rv in object.registrovotacao_set.all %}
{% if rv.ordem %}
<a href="{% url 'sapl.sessao:ordemdia_list' rv.ordem.sessao_plenaria_id %}">
{{ rv.ordem.sessao_plenaria.data_inicio }}
</a>
{% elif rv.expediente %}
<a href="{% url 'sapl.sessao:expedientemateria_list' rv.expediente.sessao_plenaria_id %}">
{{ rv.expediente.sessao_plenaria.data_inicio }}
</a>
{% endif %}
</br>
{% endfor %}
{% endif %}
{% if object.normajuridica_set.last %} {% if object.normajuridica_set.last %}
<p class="control-label">&emsp; Norma Jurídica Relacionada</p> <p class="control-label">&emsp; Norma Jurídica Relacionada</p>
<div class="actions btn-group btn-group-sm" role="group"> <div class="actions btn-group btn-group-sm" role="group">

47
sapl/templates/norma/autorianorma_form.html

@ -0,0 +1,47 @@
{% extends "crud/form.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load common_tags %}
{% block extra_js %}
<script language="Javascript">
function compare(a, b) {
if (a.text < b.text)
return -1;
if (a.text > b.text)
return 1;
return 0;
}
$(document).ready(function() {
$("#id_tipo_autor").change(function() {
var tipo_selecionado = $("#id_tipo_autor").val();
var autor_selecionado = $("#id_autor").val();
$("#id_autor option").remove()
if (tipo_selecionado !== undefined && tipo_selecionado !== null) {
var json_data = {
tipo : tipo_selecionado,
data_relativa : $("#id_data_relativa").val()
}
$.getJSON("/api/autor/possiveis", json_data, function(data){
if (data) {
var results = data.sort(compare);
if (results.length > 1) {
$("#id_autor").append("<option>-----</option>");
}
$.each(results, function(idx, obj) {
$("#id_autor")
.append($("<option></option>")
.attr("value", obj.value)
.text(obj.text));
});
$("#id_autor").val(autor_selecionado);
}
});
}
});
$("#id_tipo_autor").trigger('change');
});
</script>
{% endblock %}

4
sapl/templates/norma/layouts.yaml

@ -70,3 +70,7 @@ NormaRelacionadaDetail:
{% trans 'Norma Relacionada' %}: {% trans 'Norma Relacionada' %}:
- norma_relacionada - norma_relacionada
- tipo_vinculo - tipo_vinculo
AutoriaNorma:
{% trans 'Autoria' %}:
- autor primeiro_autor

3
sapl/templates/norma/subnav.yaml

@ -7,7 +7,8 @@
check_permission: norma.list_normarelacionada check_permission: norma.list_normarelacionada
- title: {% trans 'Anexos da Norma' %} - title: {% trans 'Anexos da Norma' %}
url: anexonormajuridica_list url: anexonormajuridica_list
- title: {% trans 'Autoria' %}
url: autorianorma_list
# Opção adicionada para chamar o TextoArticulado da norma. # Opção adicionada para chamar o TextoArticulado da norma.
# para integração foram necessárias apenas criar a url norma_ta em urls.py # para integração foram necessárias apenas criar a url norma_ta em urls.py
# e a view NormaTaView(IntegracaoTaView) em views.py # e a view NormaTaView(IntegracaoTaView) em views.py

21
sapl/templates/protocoloadm/acompanhamento_documento.html

@ -0,0 +1,21 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block actions %} {% endblock %}
{% block detail_content %}
<h1>Acompanhamento de Documento</h1>
<hr>
<div class="row">
<div class="col-md-4"><b>Tipo:</b> {{documento.tipo.sigla}} - {{documento.tipo.descricao}}</div>
<div class="col-md-4"><b>Número:</b> {{documento.numero}}</div>
<div class="col-md-4"><b>Ano:</b> {{documento.ano}}</div>
</div>
<div class="row">
<div class="col-md-12"><b>Assunto:</b> {{documento.assunto|safe}}</div>
</div>
{% if error %} <h5 align="center"><font color="#FF0000">{{ error }}</font></h5> {% endif %}
{% crispy form %}
{% endblock %}

3
sapl/templates/protocoloadm/documentoadministrativo_filter.html

@ -63,6 +63,9 @@
{% if d.texto_integral %} {% if d.texto_integral %}
<strong><a href="{{ d.texto_integral.url }}">Texto Integral</a></strong></br> <strong><a href="{{ d.texto_integral.url }}">Texto Integral</a></strong></br>
{% endif %} {% endif %}
{% if d.tramitacao %}
<a href="{% url 'sapl.protocoloadm:acompanhar_documento' d.id %}">Acompanhar Documento</a>
{% endif %}
</td> </td>
</tr> </tr>

6
sapl/templates/sessao/blocos_ata/ocorrencias_da_sessao.html

@ -0,0 +1,6 @@
<fieldset>
<p align="justify">
<strong>Ocorrências da Sessão: </strong>
{{object.ocorrenciasessao.conteudo|striptags|safe}}
</p>
</fieldset>

6
sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html

@ -0,0 +1,6 @@
<fieldset>
<legend>Ocorrências da Sessão</legend>
<div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);">
<p>{{object.ocorrenciasessao.conteudo|safe}}</p>
</div>
</fieldset>

32
sapl/templates/sessao/ocorrencia_sessao.html

@ -0,0 +1,32 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load common_tags %}
{% block actions %}{% endblock %}
{% block title %}<font size="6" face="SourceSansProSemiBold" color="#364347" >Ocorrências da Sessão </font> <b><small><font face="SourceSansProSemiBold" size="5" color="#93a4aa"> ({{ object }}) </font> </small></b>{% endblock %}
{% block detail_content %}
{% if perms|get_add_perm:view %}
<form method="post" accept-charset="ISO-8859-1">
{% csrf_token %}
<fieldset class="form-group">
<textarea rows="5" cols="50" name="conteudo" id="conteudo">{{object.ocorrenciasessao.conteudo}}</textarea>
</fieldset>
<input type="submit" name="save" value="Salvar" class="btn btn-primary"/>
<input type="submit" name="delete" value="Apagar" class="btn btn-danger" />
</form>
{% else %}
{{object.ocorrenciasessao.conteudo|safe}}
{% endif %}
{% endblock detail_content %}
<!-- Texto RICO -->
{% block extra_js %}
{% if perms|get_add_perm:view %}
<script language="JavaScript">
initTinymce(null);
</script>
{% endif %}
{% endblock %}

2
sapl/templates/sessao/resumo.html

@ -50,5 +50,7 @@
{% include 'sessao/blocos_resumo/'|add:decimo_ordenacao %} {% include 'sessao/blocos_resumo/'|add:decimo_ordenacao %}
<br /><br /><br /> <br /><br /><br />
{% include 'sessao/blocos_resumo/'|add:decimo_primeiro_ordenacao %}
<br /><br /><br />
{% endblock detail_content %} {% endblock detail_content %}

1
sapl/templates/sessao/resumo_ata.html

@ -19,5 +19,6 @@
{% include 'sessao/blocos_ata/'|add:oitavo_ordenacao %} {% include 'sessao/blocos_ata/'|add:oitavo_ordenacao %}
{% include 'sessao/blocos_ata/'|add:nono_ordenacao %} {% include 'sessao/blocos_ata/'|add:nono_ordenacao %}
{% include 'sessao/blocos_ata/'|add:decimo_ordenacao %} {% include 'sessao/blocos_ata/'|add:decimo_ordenacao %}
{% include 'sessao/blocos_ata/'|add:decimo_primeiro_ordenacao %}
{% include 'sessao/blocos_ata/assinaturas.html' %} {% include 'sessao/blocos_ata/assinaturas.html' %}
{% endblock detail_content %} {% endblock detail_content %}

2
sapl/templates/sessao/subnav.yaml

@ -12,6 +12,8 @@
url: justificativaausencia_list url: justificativaausencia_list
- title: {% trans 'Explicações Pessoais' %} - title: {% trans 'Explicações Pessoais' %}
url: orador_list url: orador_list
- title: {% trans 'Ocorrências da Sessão' %}
url: ocorrencia_sessao
- title: {% trans 'Expedientes' %} - title: {% trans 'Expedientes' %}
children: children:

2
setup.py

@ -49,7 +49,7 @@ install_requires = [
] ]
setup( setup(
name='interlegis-sapl', name='interlegis-sapl',
version='3.1.122', version='3.1.128',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007', license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007',

Loading…
Cancel
Save