Browse Source

Merge branch '3.1.x' into logging

pull/2307/head
Edward Ribeiro 7 years ago
parent
commit
b38627ac1e
  1. 4
      docker-compose.yml
  2. 6
      release.sh
  3. 12
      sapl/api/views.py
  4. 126
      sapl/base/email_utils.py
  5. 42
      sapl/base/receivers.py
  6. 13
      sapl/base/search_indexes.py
  7. 0
      sapl/base/signals.py
  8. 13
      sapl/base/templatetags/common_tags.py
  9. 5
      sapl/base/templatetags/menus.py
  10. 59
      sapl/base/views.py
  11. 4
      sapl/comissoes/forms.py
  12. 25
      sapl/comissoes/migrations/0018_auto_20180924_1724.py
  13. 5
      sapl/comissoes/models.py
  14. 12
      sapl/compilacao/apps.py
  15. 20
      sapl/compilacao/migrations/0008_auto_20180924_1724.py
  16. 20
      sapl/compilacao/migrations/0009_auto_20180926_1015.py
  17. 20
      sapl/compilacao/migrations/0010_auto_20181004_1939.py
  18. 12
      sapl/compilacao/models.py
  19. 18
      sapl/compilacao/views.py
  20. 6
      sapl/crud/base.py
  21. 6
      sapl/legacy/management/commands/ressuscitar_deps.py
  22. 9
      sapl/legacy/migracao.py
  23. 148
      sapl/legacy/migracao_dados.py
  24. 7
      sapl/legacy/migracao_documentos.py
  25. 3
      sapl/legacy/scripts/.flake8
  26. 18
      sapl/legacy/scripts/exporta_zope/exporta_zope.py
  27. 113
      sapl/legacy/scripts/ressuscita_dependencias.py
  28. 19
      sapl/legacy_migration_settings.py
  29. 2
      sapl/materia/apps.py
  30. 22
      sapl/materia/forms.py
  31. 25
      sapl/materia/migrations/0031_auto_20180924_1724.py
  32. 6
      sapl/materia/models.py
  33. 2
      sapl/materia/tests/test_email_templates.py
  34. 6
      sapl/materia/tests/test_materia_form.py
  35. 36
      sapl/materia/views.py
  36. 56
      sapl/norma/forms.py
  37. 40
      sapl/norma/migrations/0014_auto_20181008_1655.py
  38. 29
      sapl/norma/models.py
  39. 5
      sapl/norma/urls.py
  40. 38
      sapl/norma/views.py
  41. 6
      sapl/parlamentares/forms.py
  42. 20
      sapl/parlamentares/migrations/0025_auto_20180924_1724.py
  43. 2
      sapl/parlamentares/models.py
  44. 3
      sapl/protocoloadm/apps.py
  45. 33
      sapl/protocoloadm/forms.py
  46. 25
      sapl/protocoloadm/migrations/0007_auto_20180924_1724.py
  47. 32
      sapl/protocoloadm/migrations/0008_acompanhamentodocumento.py
  48. 20
      sapl/protocoloadm/migrations/0008_auto_20181009_1741.py
  49. 16
      sapl/protocoloadm/migrations/0009_merge.py
  50. 34
      sapl/protocoloadm/models.py
  51. 65
      sapl/protocoloadm/tests/test_docadm_email_templates.py
  52. 14
      sapl/protocoloadm/urls.py
  53. 189
      sapl/protocoloadm/views.py
  54. 16
      sapl/relatorios/templates/pdf_pauta_sessao_gerar.py
  55. 23
      sapl/relatorios/views.py
  56. 3
      sapl/rules/map_rules.py
  57. 4
      sapl/rules/tests/test_rules.py
  58. 3
      sapl/sessao/forms.py
  59. 4
      sapl/sessao/tests/test_sessao.py
  60. 7
      sapl/sessao/views.py
  61. 6
      sapl/settings.py
  62. 7
      sapl/static/styles/_header.scss
  63. 188
      sapl/static/styles/_home_index.scss
  64. 652
      sapl/static/styles/app.scss
  65. 3
      sapl/static/styles/compilacao.scss
  66. 171
      sapl/templates/404.html
  67. 135
      sapl/templates/500.html
  68. 5
      sapl/templates/base.html
  69. 4
      sapl/templates/comissoes/composicao_list.html
  70. 4
      sapl/templates/compilacao/dispositivo_form_search_fragment.html
  71. 2
      sapl/templates/compilacao/layout/dispositivo_radio.html
  72. 86
      sapl/templates/compilacao/text_list__embedded.html
  73. 2
      sapl/templates/compilacao/text_notificacoes.html
  74. 16
      sapl/templates/crud/detail.html
  75. 4
      sapl/templates/crud/detail_detail.html
  76. 25
      sapl/templates/email/acompanhar_documento.html
  77. 16
      sapl/templates/email/acompanhar_documento.txt
  78. 2
      sapl/templates/email/tramitacao.html
  79. 4
      sapl/templates/email/tramitacao.txt
  80. 59
      sapl/templates/index.html
  81. 47
      sapl/templates/norma/autorianorma_form.html
  82. 4
      sapl/templates/norma/layouts.yaml
  83. 28
      sapl/templates/norma/normajuridica_detail.html
  84. 3
      sapl/templates/norma/subnav.yaml
  85. 21
      sapl/templates/protocoloadm/acompanhamento_documento.html
  86. 3
      sapl/templates/protocoloadm/documentoadministrativo_filter.html
  87. 3
      sapl/templates/sessao/sessaoplenaria_filter.html
  88. 20
      sapl/test_general.py
  89. 16
      sapl/utils.py
  90. 2
      setup.py

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.118 image: interlegis/sapl:3.1.126
restart: always restart: always
environment: environment:
ADMIN_PASSWORD: interlegis ADMIN_PASSWORD: interlegis

6
release.sh

@ -13,11 +13,15 @@ function bump_version {
sed -e s/$VERSION/$NEXT_VERSION/g setup.py > tmp2 sed -e s/$VERSION/$NEXT_VERSION/g setup.py > tmp2
mv tmp2 setup.py mv tmp2 setup.py
sed -e s/$VERSION/$NEXT_VERSION/g sapl/templates/base.html > tmp3
mv tmp3 sapl/templates/base.html
} }
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

12
sapl/api/views.py

@ -20,7 +20,7 @@ from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer,
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
from sapl.materia.models import MateriaLegislativa from sapl.materia.models import MateriaLegislativa
from sapl.sessao.models import SessaoPlenaria from sapl.sessao.models import SessaoPlenaria
from sapl.utils import SaplGenericRelation, sapl_logger from sapl.utils import SaplGenericRelation
class ModelChoiceView(ListAPIView): class ModelChoiceView(ListAPIView):
@ -142,19 +142,15 @@ class AutorListView(ListAPIView):
try: try:
tr = int(self.request.GET.get tr = int(self.request.GET.get
('tr', AutorListView.TR_AUTOR_CHOICE_SERIALIZER)) ('tr', AutorListView.TR_AUTOR_CHOICE_SERIALIZER))
if tr not in (AutorListView.TR_AUTOR_CHOICE_SERIALIZER,
assert tr in ( AutorListView.TR_AUTOR_SERIALIZER):
AutorListView.TR_AUTOR_CHOICE_SERIALIZER, return AutorListView.TR_AUTOR_CHOICE_SERIALIZER
AutorListView.TR_AUTOR_SERIALIZER), sapl_logger.info(
_("Tipo do Resultado a ser fornecido não existe!"))
except Exception as e: except Exception as e:
logger.error("- " + str(e)) logger.error("- " + str(e))
return AutorListView.TR_AUTOR_CHOICE_SERIALIZER return AutorListView.TR_AUTOR_CHOICE_SERIALIZER
else:
return tr return tr
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if self.tr == AutorListView.TR_AUTOR_SERIALIZER: if self.tr == AutorListView.TR_AUTOR_SERIALIZER:
self.serializer_class = AutorSerializer self.serializer_class = AutorSerializer
self.permission_classes = (IsAuthenticated,) self.permission_classes = (IsAuthenticated,)

126
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))
materia_url = reverse('sapl.materia:materialegislativa_detail', if tipo == "materia":
kwargs={'pk': materia.id}) doc_mat_url = reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': doc_mat.id})
confirmacao_url = reverse('sapl.materia:acompanhar_confirmar', confirmacao_url = reverse('sapl.materia:acompanhar_confirmar',
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()]
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 = ""
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})
autores = [] ementa = doc_mat.ementa
for autoria in materia.autoria_set.all(): autores = [autoria.autor.nome for autoria in doc_mat.autoria_set.all()]
autores.append(autoria.autor.nome) tramitacao = doc_mat.tramitacao_set.last()
tramitacao = materia.tramitacao_set.last() else:
doc_mat_url = reverse('sapl.protocoloadm:tramitacaoadministrativo_list',
kwargs={'pk': doc_mat.id})
url_excluir = reverse('sapl.protocoloadm:acompanhar_excluir',
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":
destinatarios = AcompanhamentoMateria.objects.filter(materia=doc_mat,
confirmado=True) 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,

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

13
sapl/base/search_indexes.py

@ -1,30 +1,25 @@
import logging
import os.path import os.path
import re import re
import string import string
import textract
from django.db.models import F, Q, Value from django.db.models import F, Q, Value
from django.db.models.fields import TextField from django.db.models.fields import TextField
from django.db.models.fields.files import FieldFile
from django.db.models.functions import Concat from django.db.models.functions import Concat
from django.template import loader from django.template import loader
from haystack.constants import Indexable from haystack.constants import Indexable
from haystack.fields import CharField from haystack.fields import CharField
from haystack.indexes import SearchIndex from haystack.indexes import SearchIndex
from haystack.utils import get_model_ct_tuple from haystack.utils import get_model_ct_tuple
import textract
from textract.exceptions import ExtensionNotSupported from textract.exceptions import ExtensionNotSupported
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC, from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC,
STATUS_TA_PUBLIC, Dispositivo, STATUS_TA_PUBLIC, Dispositivo)
TextoArticulado)
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa
from sapl.norma.models import NormaJuridica from sapl.norma.models import NormaJuridica
from sapl.settings import BASE_DIR, SOLR_URL from sapl.settings import SOLR_URL
from sapl.utils import RemoveTag from sapl.utils import RemoveTag
logger = logging.getLogger(BASE_DIR.name)
class TextExtractField(CharField): class TextExtractField(CharField):
@ -68,7 +63,6 @@ class TextExtractField(CharField):
msg = 'Erro inesperado processando arquivo: %s' % ( msg = 'Erro inesperado processando arquivo: %s' % (
arquivo.path) arquivo.path)
print(msg) print(msg)
logger.error(msg)
def file_extractor(self, arquivo): def file_extractor(self, arquivo):
if not os.path.exists(arquivo.path) or \ if not os.path.exists(arquivo.path) or \
@ -89,7 +83,6 @@ class TextExtractField(CharField):
return self.whoosh_extraction(arquivo) return self.whoosh_extraction(arquivo)
except ExtensionNotSupported as e: except ExtensionNotSupported as e:
print(str(e)) print(str(e))
logger.error(str(e))
except Exception as e2: except Exception as e2:
print(str(e2)) print(str(e2))
self.print_error(arquivo) self.print_error(arquivo)

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

5
sapl/base/templatetags/menus.py

@ -1,9 +1,8 @@
import yaml
from django import template from django import template
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import yaml
from sapl.utils import sapl_logger
register = template.Library() register = template.Library()
@ -85,7 +84,7 @@ def nav_run(context, path=None):
menu = yaml.load(rendered) menu = yaml.load(rendered)
resolve_urls_inplace(menu, root_pk, rm, context) resolve_urls_inplace(menu, root_pk, rm, context)
except Exception as e: except Exception as e:
sapl_logger.error(_("""Erro na conversão do yaml %s. App: %s. print(_("""Erro na conversão do yaml %s. App: %s.
Erro: Erro:
%s %s
""") % ( """) % (

59
sapl/base/views.py

@ -14,8 +14,8 @@ from django.template import TemplateDoesNotExist
from django.template.loader import get_template from django.template.loader import get_template
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
from django.views.generic import (CreateView, DeleteView, FormView, ListView, from django.views.generic import (CreateView, DeleteView, FormView, ListView,
UpdateView) UpdateView)
from django.views.generic.base import RedirectView, TemplateView from django.views.generic.base import RedirectView, TemplateView
@ -23,16 +23,16 @@ from django_filters.views import FilterView
from haystack.views import SearchView from haystack.views import SearchView
from sapl import settings from sapl import settings
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
from sapl.crud.base import CrudAux, make_pagination
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.comissoes.models import Reuniao, Comissao from sapl.comissoes.models import Reuniao, Comissao
from sapl.crud.base import CrudAux, make_pagination
from sapl.materia.models import (Autoria, MateriaLegislativa, from sapl.materia.models import (Autoria, MateriaLegislativa,
TipoMateriaLegislativa, StatusTramitacao, UnidadeTramitacao) TipoMateriaLegislativa, StatusTramitacao, UnidadeTramitacao)
from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria, from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca) SessaoPlenariaPresenca)
from sapl.utils import (parlamentares_ativos, sapl_logger, from sapl.utils import (parlamentares_ativos,
show_results_filter_set) show_results_filter_set)
from .forms import (AlterarSenhaForm, CasaLegislativaForm, from .forms import (AlterarSenhaForm, CasaLegislativaForm,
@ -53,9 +53,11 @@ def filtra_url_materias_em_tramitacao(qr, qs, campo_url, local_ou_status):
id_materias = [] id_materias = []
filtro_url = qr[campo_url] filtro_url = qr[campo_url]
if local_ou_status == 'local': if local_ou_status == 'local':
id_materias = [item.id for item in qs if item.tramitacao_set.order_by('-id').first().unidade_tramitacao_destino_id == int(filtro_url)] id_materias = [item.id for item in qs if item.tramitacao_set.order_by(
'-id').first().unidade_tramitacao_destino_id == int(filtro_url)]
elif local_ou_status == 'status': elif local_ou_status == 'status':
id_materias = [item.id for item in qs if item.tramitacao_set.order_by('-id').first().status_id == int(filtro_url)] id_materias = [item.id for item in qs if item.tramitacao_set.order_by(
'-id').first().status_id == int(filtro_url)]
return qs.filter(em_tramitacao=True, id__in=id_materias) return qs.filter(em_tramitacao=True, id__in=id_materias)
@ -197,8 +199,7 @@ class AutorCrud(CrudAux):
fail_silently=False) fail_silently=False)
except: except:
logger.error('- Erro no envio de email na edição de Autores.') logger.error('- Erro no envio de email na edição de Autores.')
sapl_logger.error( print(_('Erro no envio de email na edição de Autores.'))
_('Erro no envio de email na edição de Autores.'))
return url_reverse return url_reverse
class CreateView(CrudAux.CreateView): class CreateView(CrudAux.CreateView):
@ -250,7 +251,7 @@ class AutorCrud(CrudAux):
send_mail(assunto, mensagem, remetente, destinatario, send_mail(assunto, mensagem, remetente, destinatario,
fail_silently=False) fail_silently=False)
except: except:
sapl_logger.error( print(
_('Erro no envio de email na criação de Autores.')) _('Erro no envio de email na criação de Autores.'))
logger.error('- Erro no envio de email na criação de Autores.') logger.error('- Erro no envio de email na criação de Autores.')
@ -404,17 +405,20 @@ class RelatorioHistoricoTramitacaoView(FilterView):
self.request.GET['tramitacao__data_tramitacao_1']) self.request.GET['tramitacao__data_tramitacao_1'])
if self.request.GET['tipo']: if self.request.GET['tipo']:
tipo = self.request.GET['tipo'] tipo = self.request.GET['tipo']
context['tipo'] = (str(TipoMateriaLegislativa.objects.get(id=tipo))) context['tipo'] = (
str(TipoMateriaLegislativa.objects.get(id=tipo)))
else: else:
context['tipo'] = '' context['tipo'] = ''
if self.request.GET['tramitacao__status']: if self.request.GET['tramitacao__status']:
tramitacao_status = self.request.GET['tramitacao__status'] tramitacao_status = self.request.GET['tramitacao__status']
context['tramitacao__status'] = (str(StatusTramitacao.objects.get(id=tramitacao_status))) context['tramitacao__status'] = (
str(StatusTramitacao.objects.get(id=tramitacao_status)))
else: else:
context['tramitacao__status'] = '' context['tramitacao__status'] = ''
if self.request.GET['tramitacao__unidade_tramitacao_local']: if self.request.GET['tramitacao__unidade_tramitacao_local']:
context['tramitacao__unidade_tramitacao_local'] = \ context['tramitacao__unidade_tramitacao_local'] = \
(str(UnidadeTramitacao.objects.get(id=self.request.GET['tramitacao__unidade_tramitacao_local']))) (str(UnidadeTramitacao.objects.get(
id=self.request.GET['tramitacao__unidade_tramitacao_local'])))
else: else:
context['tramitacao__unidade_tramitacao_destino'] = '' context['tramitacao__unidade_tramitacao_destino'] = ''
@ -441,22 +445,26 @@ class RelatorioDataFimPrazoTramitacaoView(FilterView):
self.request.GET['tramitacao__data_fim_prazo_1']) self.request.GET['tramitacao__data_fim_prazo_1'])
if self.request.GET['tipo']: if self.request.GET['tipo']:
tipo = self.request.GET['tipo'] tipo = self.request.GET['tipo']
context['tipo'] = (str(TipoMateriaLegislativa.objects.get(id=tipo))) context['tipo'] = (
str(TipoMateriaLegislativa.objects.get(id=tipo)))
else: else:
context['tipo'] = '' context['tipo'] = ''
if self.request.GET['tramitacao__status']: if self.request.GET['tramitacao__status']:
tramitacao_status = self.request.GET['tramitacao__status'] tramitacao_status = self.request.GET['tramitacao__status']
context['tramitacao__status'] = (str(StatusTramitacao.objects.get(id=tramitacao_status))) context['tramitacao__status'] = (
str(StatusTramitacao.objects.get(id=tramitacao_status)))
else: else:
context['tramitacao__status'] = '' context['tramitacao__status'] = ''
if self.request.GET['tramitacao__unidade_tramitacao_local']: if self.request.GET['tramitacao__unidade_tramitacao_local']:
context['tramitacao__unidade_tramitacao_local'] = \ context['tramitacao__unidade_tramitacao_local'] = \
(str(UnidadeTramitacao.objects.get(id=self.request.GET['tramitacao__unidade_tramitacao_local']))) (str(UnidadeTramitacao.objects.get(
id=self.request.GET['tramitacao__unidade_tramitacao_local'])))
else: else:
context['tramitacao__unidade_tramitacao_destino'] = '' context['tramitacao__unidade_tramitacao_destino'] = ''
return context return context
class RelatorioReuniaoView(FilterView): class RelatorioReuniaoView(FilterView):
model = Reuniao model = Reuniao
filterset_class = RelatorioReuniaoFilterSet filterset_class = RelatorioReuniaoFilterSet
@ -489,6 +497,7 @@ class RelatorioReuniaoView(FilterView):
return context return context
class RelatorioAudienciaView(FilterView): class RelatorioAudienciaView(FilterView):
model = AudienciaPublica model = AudienciaPublica
filterset_class = RelatorioAudienciaFilterSet filterset_class = RelatorioAudienciaFilterSet
@ -522,7 +531,6 @@ class RelatorioAudienciaView(FilterView):
return context return context
class RelatorioMateriasTramitacaoView(FilterView): class RelatorioMateriasTramitacaoView(FilterView):
model = MateriaLegislativa model = MateriaLegislativa
filterset_class = RelatorioMateriasTramitacaoilterSet filterset_class = RelatorioMateriasTramitacaoilterSet
@ -541,9 +549,11 @@ class RelatorioMateriasTramitacaoView(FilterView):
qs = qs.filter(em_tramitacao=True) qs = qs.filter(em_tramitacao=True)
if qr.get('tramitacao__unidade_tramitacao_destino'): if qr.get('tramitacao__unidade_tramitacao_destino'):
qs = filtra_url_materias_em_tramitacao(qr, qs, 'tramitacao__unidade_tramitacao_destino', 'local') qs = filtra_url_materias_em_tramitacao(
qr, qs, 'tramitacao__unidade_tramitacao_destino', 'local')
if qr.get('tramitacao__status'): if qr.get('tramitacao__status'):
qs = filtra_url_materias_em_tramitacao(qr, qs, 'tramitacao__status', 'status') qs = filtra_url_materias_em_tramitacao(
qr, qs, 'tramitacao__status', 'status')
context['object_list'] = qs context['object_list'] = qs
@ -557,17 +567,19 @@ class RelatorioMateriasTramitacaoView(FilterView):
context['ano'] = (self.request.GET['ano']) context['ano'] = (self.request.GET['ano'])
if self.request.GET['tipo']: if self.request.GET['tipo']:
tipo = self.request.GET['tipo'] tipo = self.request.GET['tipo']
context['tipo'] = (str(TipoMateriaLegislativa.objects.get(id=tipo))) context['tipo'] = (
str(TipoMateriaLegislativa.objects.get(id=tipo)))
else: else:
context['tipo'] = '' context['tipo'] = ''
if self.request.GET['tramitacao__status']: if self.request.GET['tramitacao__status']:
tramitacao_status = self.request.GET['tramitacao__status'] tramitacao_status = self.request.GET['tramitacao__status']
context['tramitacao__status'] = (str(StatusTramitacao.objects.get(id=tramitacao_status))) context['tramitacao__status'] = (
str(StatusTramitacao.objects.get(id=tramitacao_status)))
else: else:
context['tramitacao__status'] = '' context['tramitacao__status'] = ''
if self.request.GET['tramitacao__unidade_tramitacao_destino']: if self.request.GET['tramitacao__unidade_tramitacao_destino']:
context['tramitacao__unidade_tramitacao_destino'] = (str(UnidadeTramitacao.objects.get(id= context['tramitacao__unidade_tramitacao_destino'] = (str(UnidadeTramitacao.objects.get(
self.request.GET['tramitacao__unidade_tramitacao_destino']))) id=self.request.GET['tramitacao__unidade_tramitacao_destino'])))
else: else:
context['tramitacao__unidade_tramitacao_destino'] = '' context['tramitacao__unidade_tramitacao_destino'] = ''
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
@ -694,7 +706,8 @@ class RelatorioMateriasPorAutorView(FilterView):
context['show_results'] = show_results_filter_set(qr) context['show_results'] = show_results_filter_set(qr)
if self.request.GET['tipo']: if self.request.GET['tipo']:
tipo = int(self.request.GET['tipo']) tipo = int(self.request.GET['tipo'])
context['tipo'] = (str(TipoMateriaLegislativa.objects.get(id=tipo))) context['tipo'] = (
str(TipoMateriaLegislativa.objects.get(id=tipo)))
else: else:
context['tipo'] = '' context['tipo'] = ''
if self.request.GET['autoria__autor']: if self.request.GET['autoria__autor']:

4
sapl/comissoes/forms.py

@ -78,9 +78,9 @@ class PeriodoForm(forms.ModelForm):
if not data_fim: if not data_fim:
data_fim = data_inicio data_fim = data_inicio
legislatura = Legislatura.objects.filter(Q(data_inicio__lte=data_inicio, legislatura = Legislatura.objects.filter(data_inicio__lte=data_inicio,
data_fim__gte=data_fim, data_fim__gte=data_fim,
)) )
if not legislatura: if not legislatura:
logger.error(' - O período informado ' logger.error(' - O período informado '

25
sapl/comissoes/migrations/0018_auto_20180924_1724.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-09-24 20:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comissoes', '0017_auto_20180717_0827'),
]
operations = [
migrations.AlterField(
model_name='cargocomissao',
name='unico',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True, verbose_name='Único'),
),
migrations.AlterField(
model_name='comissao',
name='unidade_deliberativa',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Unidade Deliberativa'),
),
]

5
sapl/comissoes/models.py

@ -75,7 +75,8 @@ class Comissao(models.Model):
verbose_name=_('E-mail')) verbose_name=_('E-mail'))
unidade_deliberativa = models.BooleanField( unidade_deliberativa = models.BooleanField(
choices=YES_NO_CHOICES, choices=YES_NO_CHOICES,
verbose_name=_('Unidade Deliberativa')) verbose_name=_('Unidade Deliberativa'),
default=False)
ativa = models.BooleanField( ativa = models.BooleanField(
default=False, default=False,
choices=YES_NO_CHOICES, choices=YES_NO_CHOICES,
@ -121,7 +122,7 @@ class Periodo(models.Model): # PeriodoCompComissao
class CargoComissao(models.Model): class CargoComissao(models.Model):
nome = models.CharField(max_length=50, verbose_name=_('Cargo')) nome = models.CharField(max_length=50, verbose_name=_('Cargo'))
unico = models.BooleanField( unico = models.BooleanField(
choices=YES_NO_CHOICES, verbose_name=_('Único')) choices=YES_NO_CHOICES, verbose_name=_('Único'), default=True)
class Meta: class Meta:
verbose_name = _('Cargo de Comissão') verbose_name = _('Cargo de Comissão')

12
sapl/compilacao/apps.py

@ -1,15 +1,10 @@
import logging
from django import apps from django import apps
from django.conf import settings from django.conf import settings
from django.db import connection, models from django.db import connection, models
from django.db.utils import DEFAULT_DB_ALIAS, IntegrityError from django.db.utils import DEFAULT_DB_ALIAS, IntegrityError
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
from sapl.settings import BASE_DIR
logger = logging.getLogger(BASE_DIR.name)
class AppConfig(apps.AppConfig): class AppConfig(apps.AppConfig):
@ -42,7 +37,7 @@ class AppConfig(apps.AppConfig):
cursor.execute(line) cursor.execute(line)
except IntegrityError as e: except IntegrityError as e:
if not settings.DEBUG: if not settings.DEBUG:
logger.error( print(
string_concat( string_concat(
_('Ocorreu erro na importação: '), _('Ocorreu erro na importação: '),
line, line,
@ -77,8 +72,7 @@ class AppConfig(apps.AppConfig):
tipo.save() tipo.save()
except IntegrityError as e: except IntegrityError as e:
if not settings.DEBUG: if not settings.DEBUG:
logger.error( print(string_concat(
string_concat(
_('Ocorreu erro na criação tipo de ta: '), _('Ocorreu erro na criação tipo de ta: '),
str(e))) str(e)))

20
sapl/compilacao/migrations/0008_auto_20180924_1724.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-09-24 20:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0007_auto_20180911_1600'),
]
operations = [
migrations.AlterField(
model_name='tipodispositivo',
name='contagem_continua',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Contagem contínua'),
),
]

20
sapl/compilacao/migrations/0009_auto_20180926_1015.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-09-26 13:15
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0008_auto_20180924_1724'),
]
operations = [
migrations.AlterField(
model_name='tipodispositivo',
name='class_css',
field=models.CharField(blank=True, max_length=256, verbose_name='Classe CSS'),
),
]

20
sapl/compilacao/migrations/0010_auto_20181004_1939.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-10-04 22:39
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0009_auto_20180926_1015'),
]
operations = [
migrations.AlterField(
model_name='textoarticulado',
name='numero',
field=models.CharField(max_length=8, verbose_name='Número'),
),
]

12
sapl/compilacao/models.py

@ -186,7 +186,8 @@ class TextoArticulado(TimestampedMixin):
data = models.DateField(blank=True, null=True, verbose_name=_('Data')) data = models.DateField(blank=True, null=True, verbose_name=_('Data'))
ementa = models.TextField(verbose_name=_('Ementa')) ementa = models.TextField(verbose_name=_('Ementa'))
observacao = models.TextField(blank=True, verbose_name=_('Observação')) observacao = models.TextField(blank=True, verbose_name=_('Observação'))
numero = models.PositiveIntegerField(verbose_name=_('Número')) numero = models.CharField(
max_length=8,verbose_name=_('Número'))
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano')) ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'))
tipo_ta = models.ForeignKey( tipo_ta = models.ForeignKey(
TipoTextoArticulado, TipoTextoArticulado,
@ -266,10 +267,13 @@ class TextoArticulado(TimestampedMixin):
user.has_perm( user.has_perm(
'compilacao.change_your_dispositivo_edicao_dinamica')) 'compilacao.change_your_dispositivo_edicao_dinamica'))
def has_view_permission(self, request): def has_view_permission(self, request=None):
if self.privacidade in (STATUS_TA_IMMUTABLE_PUBLIC, STATUS_TA_PUBLIC): if self.privacidade in (STATUS_TA_IMMUTABLE_PUBLIC, STATUS_TA_PUBLIC):
return True return True
if not request:
return False
if request.user in self.owners.all(): if request.user in self.owners.all():
return True return True
@ -599,7 +603,7 @@ class TipoDispositivo(BaseModel):
max_length=50, unique=True, verbose_name=_('Nome')) max_length=50, unique=True, verbose_name=_('Nome'))
class_css = models.CharField( class_css = models.CharField(
blank=True, blank=True,
max_length=20, max_length=256,
verbose_name=_('Classe CSS')) verbose_name=_('Classe CSS'))
rotulo_prefixo_html = models.TextField( rotulo_prefixo_html = models.TextField(
blank=True, blank=True,
@ -654,7 +658,7 @@ class TipoDispositivo(BaseModel):
blank=True, blank=True,
verbose_name=_('Sufixo html da nota automática')) verbose_name=_('Sufixo html da nota automática'))
contagem_continua = models.BooleanField( contagem_continua = models.BooleanField(
choices=YES_NO_CHOICES, verbose_name=_('Contagem contínua')) choices=YES_NO_CHOICES, verbose_name=_('Contagem contínua'), default=False)
dispositivo_de_articulacao = models.BooleanField( dispositivo_de_articulacao = models.BooleanField(
choices=YES_NO_CHOICES, choices=YES_NO_CHOICES,
default=False, default=False,

18
sapl/compilacao/views.py

@ -1,6 +1,5 @@
from collections import OrderedDict from collections import OrderedDict
from datetime import timedelta from datetime import timedelta
import logging
import sys import sys
from braces.views import FormMessagesMixin from braces.views import FormMessagesMixin
@ -11,9 +10,8 @@ from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.signing import Signer from django.core.signing import Signer
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
from django.db import connection, transaction from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.db.utils import IntegrityError
from django.http.response import (HttpResponse, HttpResponseRedirect, from django.http.response import (HttpResponse, HttpResponseRedirect,
JsonResponse, Http404) JsonResponse, Http404)
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
@ -58,8 +56,6 @@ VeiculoPublicacaoCrud = CrudAux.build(VeiculoPublicacao, 'veiculo_publicacao')
TipoDispositivoCrud = CrudAux.build( TipoDispositivoCrud = CrudAux.build(
TipoDispositivo, 'tipo_dispositivo') TipoDispositivo, 'tipo_dispositivo')
logger = logging.getLogger(BASE_DIR.name)
def choice_models_in_extenal_views(): def choice_models_in_extenal_views():
integrations_view_names = get_integrations_view_names() integrations_view_names = get_integrations_view_names()
@ -119,7 +115,7 @@ class IntegracaoTaView(TemplateView):
tipo_ta.save() tipo_ta.save()
except Exception as e: except Exception as e:
logger.error( print(
string_concat( string_concat(
_('Ocorreu erro na importação do arquivo base dos Tipos de' _('Ocorreu erro na importação do arquivo base dos Tipos de'
'Dispositivos, entre outras informações iniciais.'), 'Dispositivos, entre outras informações iniciais.'),
@ -903,6 +899,8 @@ class TextView(CompMixin, ListView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'print' in request.GET: if 'print' in request.GET:
self.template_name = 'compilacao/text_list__print_version.html' self.template_name = 'compilacao/text_list__print_version.html'
if 'embedded' in request.GET:
self.template_name = 'compilacao/text_list__embedded.html'
return ListView.get(self, request, *args, **kwargs) return ListView.get(self, request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -2615,13 +2613,7 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
history = dispositivo_a_alterar.history() history = dispositivo_a_alterar.history()
for d in history: for d in history:
"""FIXME: A comparação "<" deverá ser mudada para if d.inicio_vigencia <= bloco_alteracao.inicio_vigencia:
"<=" caso seja necessário permitir duas alterações
com mesmo inicio_vigencia no mesmo dispositivo. Neste Caso,
a sequencia correta ficará a cargo dos reposicionamentos e
(a ser implementado) entre dispositivos de mesmo nível,
"""
if d.inicio_vigencia < bloco_alteracao.inicio_vigencia:
dispositivo_a_alterar = d dispositivo_a_alterar = d
break break

6
sapl/crud/base.py

@ -1,4 +1,3 @@
import logging
from braces.views import FormMessagesMixin from braces.views import FormMessagesMixin
from compressor.utils.decorators import cached_property from compressor.utils.decorators import cached_property
@ -30,9 +29,6 @@ from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL,
from sapl.settings import BASE_DIR from sapl.settings import BASE_DIR
from sapl.utils import normalize from sapl.utils import normalize
logger = logging.getLogger(BASE_DIR.name)
ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
'list', 'create', 'detail', 'update', 'delete' 'list', 'create', 'detail', 'update', 'delete'
@ -588,7 +584,7 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
# print(ordering) # print(ordering)
except Exception as e: except Exception as e:
logger.error(string_concat(_( print(string_concat(_(
'ERRO: construção da tupla de ordenação.'), str(e))) 'ERRO: construção da tupla de ordenação.'), str(e)))
# print(queryset.query) # print(queryset.query)

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)

148
sapl/legacy/migracao_dados.py

@ -33,7 +33,8 @@ from sapl.comissoes.models import Comissao, Composicao, Participacao, Reuniao
from sapl.legacy.models import NormaJuridica as OldNormaJuridica from sapl.legacy.models import NormaJuridica as OldNormaJuridica
from sapl.legacy.models import TipoNumeracaoProtocolo from sapl.legacy.models import TipoNumeracaoProtocolo
from sapl.legacy_migration_settings import (DIR_DADOS_MIGRACAO, DIR_REPO, from sapl.legacy_migration_settings import (DIR_DADOS_MIGRACAO, DIR_REPO,
NOME_BANCO_LEGADO) NOME_BANCO_LEGADO, PYTZ_TIMEZONE,
SIGLA_CASA)
from sapl.materia.models import (AcompanhamentoMateria, DocumentoAcessorio, from sapl.materia.models import (AcompanhamentoMateria, DocumentoAcessorio,
MateriaLegislativa, Proposicao, MateriaLegislativa, Proposicao,
StatusTramitacao, TipoDocumento, StatusTramitacao, TipoDocumento,
@ -50,7 +51,6 @@ from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao, OrdemDia,
from sapl.utils import normalize from sapl.utils import normalize
from .scripts.normaliza_dump_mysql import normaliza_dump_mysql from .scripts.normaliza_dump_mysql import normaliza_dump_mysql
from .timezonesbrasil import get_timezone
# YAML SETUP ############################################################### # YAML SETUP ###############################################################
@ -539,6 +539,12 @@ PROPAGACOES_DE_EXCLUSAO = [
('parlamentar', 'mandato', 'cod_parlamentar'), ('parlamentar', 'mandato', 'cod_parlamentar'),
('parlamentar', 'composicao_mesa', 'cod_parlamentar'), ('parlamentar', 'composicao_mesa', 'cod_parlamentar'),
('parlamentar', 'composicao_comissao', 'cod_parlamentar'), ('parlamentar', 'composicao_comissao', 'cod_parlamentar'),
# no 2.5 os parlamentares excluídos não são listados na presença da sessão
('parlamentar', 'sessao_plenaria_presenca', 'cod_parlamentar'),
# ... nem na presença da ordem do dia
('parlamentar', 'ordem_dia_presenca', 'cod_parlamentar'),
# ... nem na mesa da sessão
('parlamentar', 'mesa_sessao_plenaria', 'cod_parlamentar'),
# coligacao # coligacao
('coligacao', 'composicao_coligacao', 'cod_coligacao'), ('coligacao', 'composicao_coligacao', 'cod_coligacao'),
@ -553,6 +559,9 @@ PROPAGACOES_DE_EXCLUSAO = [
('sessao_plenaria', 'expediente_sessao_plenaria', 'cod_sessao_plen'), ('sessao_plenaria', 'expediente_sessao_plenaria', 'cod_sessao_plen'),
('sessao_plenaria', 'sessao_plenaria_presenca', 'cod_sessao_plen'), ('sessao_plenaria', 'sessao_plenaria_presenca', 'cod_sessao_plen'),
('sessao_plenaria', 'ordem_dia_presenca', 'cod_sessao_plen'), ('sessao_plenaria', 'ordem_dia_presenca', 'cod_sessao_plen'),
('sessao_plenaria', 'mesa_sessao_plenaria', 'cod_sessao_plen'),
('sessao_plenaria', 'oradores', 'cod_sessao_plen'),
('sessao_plenaria', 'oradores_expediente', 'cod_sessao_plen'),
# as consultas no código do sapl 2.5 # as consultas no código do sapl 2.5
# votacao_ordem_dia_obter_zsql e votacao_expediente_materia_obter_zsql # votacao_ordem_dia_obter_zsql e votacao_expediente_materia_obter_zsql
@ -575,6 +584,8 @@ PROPAGACOES_DE_EXCLUSAO = [
('materia_legislativa', 'despacho_inicial', 'cod_materia'), ('materia_legislativa', 'despacho_inicial', 'cod_materia'),
('materia_legislativa', 'legislacao_citada', 'cod_materia'), ('materia_legislativa', 'legislacao_citada', 'cod_materia'),
('materia_legislativa', 'relatoria', 'cod_materia'), ('materia_legislativa', 'relatoria', 'cod_materia'),
('materia_legislativa', 'materia_assunto', 'cod_materia'),
# norma # norma
('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referente'), ('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referente'),
@ -612,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()
@ -705,6 +746,14 @@ sessao_plenaria_presenca | dat_sessao = NULL | dat_sessao = 0
anula_tipos_origem_externa_invalidos() anula_tipos_origem_externa_invalidos()
corrige_unidades_tramitacao_destino_vazia_como_anterior() corrige_unidades_tramitacao_destino_vazia_como_anterior()
# matérias inexistentes não são mostradas em norma jurídica => apagamos
exec_legado('''update norma_juridica set cod_materia = NULL
where cod_materia not in (
select cod_materia from materia_legislativa
where ind_excluido <> 1);''')
apaga_ref_a_mats_e_docs_inexistentes_em_proposicoes()
class Record: class Record:
pass pass
@ -790,19 +839,6 @@ def reinicia_sequence(model, id):
REPO = git.Repo.init(DIR_REPO) REPO = git.Repo.init(DIR_REPO)
# configura timezone de migração
match = re.match('sapl_cm_(.*)', NOME_BANCO_LEGADO)
sigla_casa = match.group(1)
PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml')
with open(PATH_TABELA_TIMEZONES, 'r') as arq:
tabela_timezones = yaml.load(arq)
municipio, uf, nome_timezone = tabela_timezones[sigla_casa]
if nome_timezone:
timezone = pytz.timezone(nome_timezone)
else:
timezone = get_timezone(municipio, uf)
def populate_renamed_fields(new, old): def populate_renamed_fields(new, old):
renames = field_renames[type(new)] renames = field_renames[type(new)]
@ -822,16 +858,17 @@ def populate_renamed_fields(new, old):
and value in [None, 'None']): and value in [None, 'None']):
value = '' value = ''
# adiciona timezone faltante aos campos com tempo # ajusta tempos segundo timezone
# os campos TIMESTAMP do mysql são gravados em UTC # os campos TIMESTAMP do mysql são gravados em UTC
# os DATETIME e TIME não têm timezone # os DATETIME e TIME não têm timezone
def campo_tempo_sem_timezone(tipo):
return (field_type == tipo if field_type == 'DateTimeField' and value:
and value and not value.tzinfo) # as datas armazenadas no legado na verdade são naive
if campo_tempo_sem_timezone('DateTimeField'): sem_tz = value.replace(tzinfo=None)
value = timezone.localize(value) value = PYTZ_TIMEZONE.localize(sem_tz).astimezone(pytz.utc)
if campo_tempo_sem_timezone('TimeField'):
value = value.replace(tzinfo=timezone) if field_type == 'TimeField' and value:
value = value.replace(tzinfo=PYTZ_TIMEZONE)
setattr(new, field.name, value) setattr(new, field.name, value)
@ -843,7 +880,7 @@ def roda_comando_shell(cmd):
def get_arquivo_ajustes_pre_migracao(): def get_arquivo_ajustes_pre_migracao():
return DIR_DADOS_MIGRACAO.child( return DIR_DADOS_MIGRACAO.child(
'ajustes_pre_migracao', '{}.sql'.format(sigla_casa)) 'ajustes_pre_migracao', '{}.sql'.format(SIGLA_CASA))
def migrar_dados(apagar_do_legado=False): def migrar_dados(apagar_do_legado=False):
@ -1026,65 +1063,34 @@ def adjust_acompanhamentomateria(new, old):
new.confirmado = True new.confirmado = True
NOTA_DOCADM = '''
## NOTA DE MIGRAÇÃO DE DADOS DO SAPL 2.5 ##
O número de protocolo original deste documento era [{num_protocolo}], ano {ano_original}.
'''.strip() # noqa
def adjust_documentoadministrativo(new, old): def adjust_documentoadministrativo(new, old):
if old.num_protocolo: if old.num_protocolo:
nota = None numero, ano = old.num_protocolo, new.ano
ano_original = new.ano # False < True => o primeiro será o protocolo não anulado
protocolo = Protocolo.objects.filter( protocolos = Protocolo.objects.filter(
numero=old.num_protocolo, ano=new.ano) numero=numero, ano=ano).order_by('anulado')
if not protocolo: if protocolos:
# tentamos encontrar o protocolo no ano seguinte new.protocolo = protocolos[0]
ano_novo = ano_original + 1
protocolo = Protocolo.objects.filter(numero=old.num_protocolo,
ano=ano_novo)
if protocolo:
nota = NOTA_DOCADM + '''
O protocolo vinculado é o de mesmo número, porém do ano seguinte ({ano_novo}),
pois não existe protocolo no sistema com este número no ano {ano_original}.
'''
nota = nota.strip().format(num_protocolo=old.num_protocolo,
ano_original=ano_original,
ano_novo=ano_novo)
msg = 'PROTOCOLO ENCONTRADO APENAS PARA O ANO SEGUINTE!!!!! '\
'DocumentoAdministrativo: {cod_documento}, '\
'numero_protocolo: {num_protocolo}, '\
'ano doc adm: {ano_original}'
warn('protocolo_ano_seguinte', msg,
{'cod_documento': old.cod_documento,
'num_protocolo': old.num_protocolo,
'ano_original': ano_original,
'nota': nota})
else: else:
# Se não achamos mesmo no ano anteriro # Se não achamos o protocolo registramos no número externo
# colocamos no número externo new.numero_externo = numero
new.numero_externo = old.num_protocolo
nota = '''
## NOTA DE MIGRAÇÃO DE DADOS DO SAPL 2.5 ##
O número de protocolo original deste documento era [{numero}], ano [{ano}].
nota = NOTA_DOCADM + '''
Não existe no sistema nenhum protocolo com estes dados Não existe no sistema nenhum protocolo com estes dados
e portanto nenhum protocolo foi vinculado a este documento. e portanto nenhum protocolo foi vinculado a este documento.
Colocamos então o número de protocolo no campo "número externo". Colocamos então o número de protocolo no campo "número externo".
''' '''
nota = nota.format( nota = nota.strip().format(numero=numero, ano=ano)
num_protocolo=old.num_protocolo, msg = 'Protocolo {numero} faltando (referenciado ' \
ano_original=ano_original)
msg = 'Protocolo {num_protocolo} faltando (referenciado ' \
'no documento administrativo {cod_documento})' 'no documento administrativo {cod_documento})'
warn('protocolo_faltando', msg, warn('protocolo_faltando', msg,
{'num_protocolo': old.num_protocolo, {'numero': numero,
'cod_documento': old.cod_documento, 'cod_documento': old.cod_documento,
'nota': nota}) 'nota': nota})
if protocolo:
assert len(protocolo) == 1, 'mais de um protocolo encontrado'
[new.protocolo] = protocolo
# adiciona nota ao final da observação
if nota:
new.observacao += ('\n\n' if new.observacao else '') + nota new.observacao += ('\n\n' if new.observacao else '') + nota
@ -1163,7 +1169,7 @@ def adjust_protocolo_antes_salvar(new, old):
def get_arquivo_resolve_registro_votacao(): def get_arquivo_resolve_registro_votacao():
return DIR_DADOS_MIGRACAO.child( return DIR_DADOS_MIGRACAO.child(
'ajustes_pre_migracao', 'ajustes_pre_migracao',
'{}_resolve_registro_votacao_ambiguo.yaml'.format(sigla_casa)) '{}_resolve_registro_votacao_ambiguo.yaml'.format(SIGLA_CASA))
def get_como_resolver_registro_votacao_ambiguo(): def get_como_resolver_registro_votacao_ambiguo():

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)

3
sapl/legacy/scripts/.flake8

@ -0,0 +1,3 @@
[flake8]
ignore = E501

18
sapl/legacy/scripts/exporta_zope/exporta_zope.py

@ -298,6 +298,8 @@ DUMP_FUNCTIONS = {
'Image': dump_file, 'Image': dump_file,
'DTML Method': partial(dump_file, 'DTML Method': partial(dump_file,
get_conteudo=get_conteudo_dtml_method), get_conteudo=get_conteudo_dtml_method),
'DTMLMethod': partial(dump_file,
get_conteudo=get_conteudo_dtml_method),
'Folder': partial(dump_folder, enum=enumerate_folder), 'Folder': partial(dump_folder, enum=enumerate_folder),
'BTreeFolder2': partial(dump_folder, enum=enumerate_btree), 'BTreeFolder2': partial(dump_folder, enum=enumerate_btree),
'SDE-Document': partial(dump_sde, tipo='sde.document'), 'SDE-Document': partial(dump_sde, tipo='sde.document'),
@ -381,18 +383,30 @@ def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar, mtimes):
sapl = find_sapl(app) sapl = find_sapl(app)
# extrai usuários com suas senhas e perfis # extrai usuários com suas senhas e perfis
dump_usuarios(sapl, destino, salvar) dump_usuarios(sapl, destino, salvar)
# extrai folhas XSLT (primeira tentativa)
if 'XSLT' in sapl:
dump_folder(br(sapl['XSLT']), destino, salvar, mtimes)
finally: finally:
close_db() close_db()
app, close_db = get_app(documentos_fs_path) app, close_db = get_app(documentos_fs_path)
try: try:
sapl = find_sapl(app) sapl = find_sapl(app)
# extrai folhas XSLT if sapl == {'id': 'sapl'}:
# em algumas instalações sapl_documentos está direto na raiz
docs = br(app['sapl_documentos'])
else:
# caso mais comum
docs = br(sapl['sapl_documentos'])
# extrai folhas XSLT (segunda tentativa)
if 'XSLT' in sapl: if 'XSLT' in sapl:
dump_folder(br(sapl['XSLT']), destino, salvar, mtimes) dump_folder(br(sapl['XSLT']), destino, salvar, mtimes)
# extrai documentos # extrai documentos
docs = br(sapl['sapl_documentos'])
with logando_nao_identificados(): with logando_nao_identificados():
dump_folder(docs, destino, salvar, mtimes) dump_folder(docs, destino, salvar, mtimes)
dump_propriedades(docs, destino, salvar) dump_propriedades(docs, destino, salvar)

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

@ -1,3 +1,4 @@
from collections import OrderedDict
from textwrap import dedent from textwrap import dedent
import texttable import texttable
@ -19,6 +20,7 @@ def stripsplit(ll):
def _tab_legado(model): def _tab_legado(model):
return models_novos_para_antigos[model]._meta.db_table return models_novos_para_antigos[model]._meta.db_table
fks_legado = { fks_legado = {
(_tab_legado(m), campos_novos_para_antigos[f]): _tab_legado(f.related_model) # noqa (_tab_legado(m), campos_novos_para_antigos[f]): _tab_legado(f.related_model) # noqa
for m in models_novos_para_antigos for m in models_novos_para_antigos
@ -56,7 +58,19 @@ documento_administrativo /docadm
tipo_materia_legislativa /sistema/materia/tipo tipo_materia_legislativa /sistema/materia/tipo
tipo_norma_juridica /sistema/norma/tipo tipo_norma_juridica /sistema/norma/tipo
comissao /comissao comissao /comissao
registro_votacao ????????? assunto_materia /sistema/assunto-materia
coligacao /sistema/coligacao
nivel_instrucao /sistema/parlamentar/nivel-instrucao
partido /sistema/parlamentar/partido
regime_tramitacao /sistema/materia/regime-tramitacao
tipo_comissao /sistema/comissao/tipo
tipo_documento_administrativo /sistema/tipo-documento-adm
registro_votacao /admin/sessao/registrovotacao
tipo_dependente /sistema/parlamentar/tipo-dependente
origem /sistema/materia/origem
documento_acessorio /materia/documentoacessorio
tipo_fim_relatoria /sistema/materia/tipo-fim-relatoria
tipo_situacao_militar /sistema/parlamentar/tipo-militar
''' '''
urls = dict(stripsplit(urls)) urls = dict(stripsplit(urls))
@ -77,6 +91,7 @@ CAMPOS_ORIGEM_PARA_ALVO = {
'cod_unid_tram_dest': 'cod_unid_tramitacao', 'cod_unid_tram_dest': 'cod_unid_tramitacao',
'cod_unid_tram_local': 'cod_unid_tramitacao', 'cod_unid_tram_local': 'cod_unid_tramitacao',
'tip_id_basica': 'tip_materia', 'tip_id_basica': 'tip_materia',
'cod_local_origem_externa': 'cod_origem',
} }
@ -180,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')
@ -214,6 +229,21 @@ def get_dependencias_a_ressucitar(slug):
return preambulo, desexcluir, criar return preambulo, desexcluir, criar
# deve ser idempotente pois é usada na criação de autor
# por isso o ON DUPLICATE KEY UPDATE
SQL_INSERT_TIPO_AUTOR = '''
insert into tipo_autor (tip_autor, des_tipo_autor, ind_excluido)
values ({}, "DESCONHECIDO", 0) ON DUPLICATE KEY UPDATE ind_excluido = 0;
'''
# deve ser idempotente pois é usada na criação de comissao
# por isso o ON DUPLICATE KEY UPDATE
SQL_INSERT_TIPO_COMISSAO = '''
insert into tipo_comissao (tip_comissao, nom_tipo_comissao, sgl_natureza_comissao, sgl_tipo_comissao, des_dispositivo_regimental, ind_excluido)
values ({}, "DESCONHECIDO", "P", "DESC", NULL, 0)
ON DUPLICATE KEY UPDATE ind_excluido = 0;
'''
SQLS_CRIACAO = [ SQLS_CRIACAO = [
('tipo_proposicao', ''' ('tipo_proposicao', '''
insert into tipo_materia_legislativa ( insert into tipo_materia_legislativa (
@ -232,13 +262,54 @@ SQLS_CRIACAO = [
tip_resultado_votacao, nom_resultado, ind_excluido) tip_resultado_votacao, nom_resultado, ind_excluido)
values ({}, "DESCONHECIDO", 0); values ({}, "DESCONHECIDO", 0);
'''), '''),
('tipo_autor', ''' ('tipo_autor', SQL_INSERT_TIPO_AUTOR),
insert into tipo_autor (tip_autor, des_tipo_autor, ind_excluido) ('unidade_tramitacao', '''
insert into unidade_tramitacao (
cod_unid_tramitacao, cod_comissao, cod_orgao, cod_parlamentar, ind_excluido)
values ({}, NULL, NULL, 0, 0);
'''),
('autor', SQL_INSERT_TIPO_AUTOR.format(0) + '''
insert into autor (
cod_autor, cod_partido, cod_comissao, cod_parlamentar, tip_autor,
nom_autor, des_cargo, col_username, ind_excluido)
values ({}, 0, 0, 0, 0, "DESCONHECIDO", "DESCONHECIDO", NULL, 0);
'''),
('tipo_documento', '''
insert into tipo_documento (tip_documento, des_tipo_documento, ind_excluido)
values ({}, "DESCONHECIDO", 0); values ({}, "DESCONHECIDO", 0);
'''), '''),
('unidade_tramitacao', ''' ('partido', '''
insert into unidade_tramitacao (cod_unid_tramitacao, cod_comissao, cod_orgao, cod_parlamentar, ind_excluido) insert into partido (cod_partido, sgl_partido, nom_partido, dat_criacao, dat_extincao, ind_excluido)
values ({}, NULL, NULL, NULL, 0); values ({}, "DESC", "DESCONHECIDO", NULL, NULL, 0);
'''),
('legislatura', '''
insert into legislatura (num_legislatura, dat_inicio, dat_fim, dat_eleicao, ind_excluido)
values ({}, "1/1/1", "1/1/1", "1/1/1", 0);
'''),
('cargo_mesa', '''
insert into cargo_mesa (cod_cargo, des_cargo, ind_unico, ind_excluido)
values ({}, "DESCONHECIDO", 0, 0);
'''),
('orgao', '''
insert into orgao (cod_orgao, nom_orgao, sgl_orgao, ind_unid_deliberativa, end_orgao, num_tel_orgao, ind_excluido)
values ({}, "DESCONHECIDO", "DESC", 0, NULL, NULL, 0);
'''),
('origem', '''
insert into origem (cod_origem, sgl_origem, nom_origem, ind_excluido)
values ({}, "DESC", "DESCONHECIDO", 0);
'''),
('tipo_comissao', SQL_INSERT_TIPO_COMISSAO),
('comissao', SQL_INSERT_TIPO_COMISSAO.format(0) + '''
insert into comissao (cod_comissao, tip_comissao, nom_comissao, sgl_comissao, dat_criacao,
ind_unid_deliberativa, ind_excluido)
values ({}, 0, "DESCONHECIDO", "DESC", "1-1-1", 0, 0);
'''),
('parlamentar', '''
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);
'''),
('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)
@ -282,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
@ -307,6 +378,10 @@ def get_url(slug):
return 'sapl.{}.leg.br'.format(slug.replace('-', '.')) return 'sapl.{}.leg.br'.format(slug.replace('-', '.'))
def sem_repeticoes_mantendo_ordem(sequencia):
return OrderedDict.fromkeys(sequencia).keys()
def get_sqls_desexcluir_criar(preambulo, desexcluir, criar, slug): def get_sqls_desexcluir_criar(preambulo, desexcluir, criar, slug):
sqls_links = [get_sql(*(args + (slug,))) sqls_links = [get_sql(*(args + (slug,)))
for itens, get_sql in ((desexcluir, get_sql_desexcluir), for itens, get_sql in ((desexcluir, get_sql_desexcluir),
@ -316,13 +391,21 @@ def get_sqls_desexcluir_criar(preambulo, desexcluir, criar, slug):
return '' return ''
else: else:
sqls, links = zip(*sqls_links) sqls, links = zip(*sqls_links)
links = [l for ll in links for l in ll] # flatten
sqls = [dedent(s.strip()) + ';'
for sql in sqls
for s in sql.split(';') if s.strip()]
sqls = sem_repeticoes_mantendo_ordem(sqls)
links = (l for ll in links for l in ll) # flatten
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)
@ -334,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()

19
sapl/legacy_migration_settings.py

@ -1,10 +1,14 @@
import os import os
import re
import pytz
import yaml
from decouple import Config, RepositoryEnv from decouple import Config, RepositoryEnv
from dj_database_url import parse as db_url from dj_database_url import parse as db_url
from sapl.legacy.scripts.exporta_zope.variaveis_comuns import \ from sapl.legacy.scripts.exporta_zope.variaveis_comuns import \
DIR_DADOS_MIGRACAO DIR_DADOS_MIGRACAO
from sapl.legacy.timezonesbrasil import get_timezone
from .settings import * # flake8: noqa from .settings import * # flake8: noqa
@ -43,3 +47,18 @@ NOME_BANCO_LEGADO = DATABASES['legacy']['NAME']
DIR_REPO = Path(DIR_DADOS_MIGRACAO, 'repos', NOME_BANCO_LEGADO) DIR_REPO = Path(DIR_DADOS_MIGRACAO, 'repos', NOME_BANCO_LEGADO)
MEDIA_ROOT = DIR_REPO MEDIA_ROOT = DIR_REPO
# configura timezone de migração
match = re.match('sapl_cm_(.*)', NOME_BANCO_LEGADO)
SIGLA_CASA = match.group(1)
_PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml')
with open(_PATH_TABELA_TIMEZONES, 'r') as arq:
tabela_timezones = yaml.load(arq)
municipio, uf, nome_timezone = tabela_timezones[SIGLA_CASA]
if nome_timezone:
PYTZ_TIMEZONE = pytz.timezone(nome_timezone)
else:
PYTZ_TIMEZONE = get_timezone(municipio, uf)
TIME_ZONE = PYTZ_TIMEZONE.zone

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

22
sapl/materia/forms.py

@ -297,6 +297,20 @@ class UnidadeTramitacaoForm(ModelForm):
raise ValidationError(msg) raise ValidationError(msg)
return cleaned_data return cleaned_data
def save(self, commit=False):
unidade = super(UnidadeTramitacaoForm, self).save(commit)
cd = self.cleaned_data
if not cd.get('orgao'):
unidade.orgao = None
if not cd.get('parlamentar'):
unidade.parlamentar = None
if not cd.get('comissao'):
unidade.comissao = None
unidade.save()
return unidade
class AcompanhamentoMateriaForm(ModelForm): class AcompanhamentoMateriaForm(ModelForm):
@ -365,6 +379,11 @@ class RelatoriaForm(ModelForm):
class TramitacaoForm(ModelForm): class TramitacaoForm(ModelForm):
urgente = forms.ChoiceField(required=True,
choices=YES_NO_CHOICES,
initial=False,
label=_("Urgente?"))
class Meta: class Meta:
model = Tramitacao model = Tramitacao
fields = ['data_tramitacao', fields = ['data_tramitacao',
@ -1914,14 +1933,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']

25
sapl/materia/migrations/0031_auto_20180924_1724.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-09-24 20:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0030_tramitacao_timestamp'),
]
operations = [
migrations.AlterField(
model_name='orgao',
name='unidade_deliberativa',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Unidade Deliberativa'),
),
migrations.AlterField(
model_name='tramitacao',
name='urgente',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Urgente ?'),
),
]

6
sapl/materia/models.py

@ -557,7 +557,8 @@ class Orgao(models.Model):
sigla = models.CharField(max_length=10, verbose_name=_('Sigla')) sigla = models.CharField(max_length=10, verbose_name=_('Sigla'))
unidade_deliberativa = models.BooleanField( unidade_deliberativa = models.BooleanField(
choices=YES_NO_CHOICES, choices=YES_NO_CHOICES,
verbose_name=(_('Unidade Deliberativa'))) verbose_name=(_('Unidade Deliberativa')),
default=False)
endereco = models.CharField( endereco = models.CharField(
max_length=100, blank=True, verbose_name=_('Endereço')) max_length=100, blank=True, verbose_name=_('Endereço'))
telefone = models.CharField( telefone = models.CharField(
@ -929,7 +930,8 @@ class Tramitacao(models.Model):
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Unidade Destino')) verbose_name=_('Unidade Destino'))
urgente = models.BooleanField(verbose_name=_('Urgente ?'), urgente = models.BooleanField(verbose_name=_('Urgente ?'),
choices=YES_NO_CHOICES) choices=YES_NO_CHOICES,
default=False)
turno = models.CharField( turno = models.CharField(
max_length=1, blank=True, verbose_name=_('Turno'), max_length=1, blank=True, verbose_name=_('Turno'),
choices=TURNO_CHOICES) choices=TURNO_CHOICES)

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():

6
sapl/materia/tests/test_materia_form.py

@ -196,8 +196,9 @@ def test_valida_campos_obrigatorios_tramitacao_form():
assert errors['status'] == [_('Este campo é obrigatório.')] assert errors['status'] == [_('Este campo é obrigatório.')]
assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')] assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')]
assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')] assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')]
assert errors['urgente'] == [_('Este campo é obrigatório.')]
assert len(errors) == 5 assert len(errors) == 6
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
@ -213,8 +214,9 @@ def test_valida_campos_obrigatorios_tramitacao_update_form():
assert errors['status'] == [_('Este campo é obrigatório.')] assert errors['status'] == [_('Este campo é obrigatório.')]
assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')] assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')]
assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')] assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')]
assert errors['urgente'] == [_('Este campo é obrigatório.')]
assert len(errors) == 5 assert len(errors) == 6
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)

36
sapl/materia/views.py

@ -47,7 +47,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,
@ -66,7 +66,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')
@ -496,10 +496,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
@ -507,7 +510,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(
@ -515,8 +518,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):
@ -1154,10 +1161,10 @@ 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.')
logger.error('- Tramitação criada, mas e-mail de acompanhamento ' logger.warning('- 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)
@ -1186,10 +1193,10 @@ 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.')
logger.error('- Tramitação atualizada, mas e-mail de acompanhamento ' logger.warning('- 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)
@ -1738,6 +1745,7 @@ class AcompanhamentoMateriaView(CreateView):
do_envia_email_confirmacao(base_url, do_envia_email_confirmacao(base_url,
casa, casa,
"materia",
materia, materia,
destinatario) destinatario)
@ -1936,9 +1944,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'])

56
sapl/norma/forms.py

@ -6,17 +6,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():
@ -134,6 +135,12 @@ class NormaJuridicaForm(ModelForm):
if not self.is_valid(): if not self.is_valid():
return cleaned_data return cleaned_data
import re
has_digits = re.sub('[^0-9]', '', cleaned_data['numero'])
if not has_digits:
raise ValidationError('Número de norma não pode conter somente letras')
if self.instance.numero != cleaned_data['numero']: if self.instance.numero != cleaned_data['numero']:
norma = NormaJuridica.objects.filter(ano=cleaned_data['ano'], norma = NormaJuridica.objects.filter(ano=cleaned_data['ano'],
numero=cleaned_data['numero'], numero=cleaned_data['numero'],
@ -196,6 +203,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'),

38
sapl/norma/views.py

@ -22,9 +22,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, '')
@ -240,6 +240,7 @@ class NormaCrud(Crud):
initial['tipo_materia'] = norma.materia.tipo initial['tipo_materia'] = norma.materia.tipo
initial['ano_materia'] = norma.materia.ano initial['ano_materia'] = norma.materia.ano
initial['numero_materia'] = norma.materia.numero initial['numero_materia'] = norma.materia.numero
initial['esfera_federacao'] = norma.esfera_federacao
return initial return initial
@ -280,6 +281,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', )

6
sapl/parlamentares/forms.py

@ -142,7 +142,7 @@ class LegislaturaForm(ModelForm):
pk = self.instance.pk pk = self.instance.pk
ultima_legislatura = Legislatura.objects.filter(data_inicio__lte=data_inicio ultima_legislatura = Legislatura.objects.filter(data_inicio__lt=data_inicio
).order_by('-data_inicio').first() ).order_by('-data_inicio').first()
proxima_legislatura = Legislatura.objects.filter(data_fim__gt=data_fim proxima_legislatura = Legislatura.objects.filter(data_fim__gt=data_fim
).order_by('data_fim').first() ).order_by('data_fim').first()
@ -348,12 +348,12 @@ class FrenteForm(ModelForm):
fields = '__all__' fields = '__all__'
def clean(self): def clean(self):
frente = super(FrenteForm, self).clean() super(FrenteForm, self).clean()
cd = self.cleaned_data cd = self.cleaned_data
if not self.is_valid(): if not self.is_valid():
return self.cleaned_data return self.cleaned_data
if cd['data_criacao'] >= cd['data_extincao']: if cd['data_extincao'] and cd['data_criacao'] >= cd['data_extincao']:
raise ValidationError(_("Data Dissolução não pode ser anterior a Data Criação")) raise ValidationError(_("Data Dissolução não pode ser anterior a Data Criação"))
return cd return cd

20
sapl/parlamentares/migrations/0025_auto_20180924_1724.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-09-24 20:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('parlamentares', '0024_auto_20180814_1237'),
]
operations = [
migrations.AlterField(
model_name='cargomesa',
name='unico',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True, verbose_name='Cargo Único'),
),
]

2
sapl/parlamentares/models.py

@ -475,7 +475,7 @@ class CargoMesa(models.Model):
descricao = models.CharField( descricao = models.CharField(
max_length=50, verbose_name=_('Cargo na Mesa')) max_length=50, verbose_name=_('Cargo na Mesa'))
unico = models.BooleanField( unico = models.BooleanField(
choices=YES_NO_CHOICES, verbose_name=_('Cargo Único')) choices=YES_NO_CHOICES, verbose_name=_('Cargo Único'), default=True)
class Meta: class Meta:
verbose_name = _('Cargo na Mesa') verbose_name = _('Cargo na Mesa')

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

33
sapl/protocoloadm/forms.py

@ -3,7 +3,7 @@ import django_filters
import logging import logging
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)
@ -20,7 +20,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)
@ -40,6 +41,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):
@ -920,7 +943,7 @@ class DesvincularMateriaForm(forms.Form):
def pega_ultima_tramitacao_adm(): def pega_ultima_tramitacao_adm():
return TramitacaoAdministrativo.objects.values( return TramitacaoAdministrativo.objects.values(
'materia_id').annotate(data_encaminhamento=Max( 'documento_id').annotate(data_encaminhamento=Max(
'data_encaminhamento'), 'data_encaminhamento'),
id=Max('id')).values_list('id', flat=True) id=Max('id')).values_list('id', flat=True)
@ -937,7 +960,7 @@ def filtra_tramitacao_adm_destino(destino):
return TramitacaoAdministrativo.objects.filter( return TramitacaoAdministrativo.objects.filter(
id__in=lista, id__in=lista,
unidade_tramitacao_destino=destino).distinct().values_list( unidade_tramitacao_destino=destino).distinct().values_list(
'materia_id', flat=True) 'documento_id', flat=True)
def filtra_tramitacao_adm_destino_and_status(status, destino): def filtra_tramitacao_adm_destino_and_status(status, destino):
@ -946,4 +969,4 @@ def filtra_tramitacao_adm_destino_and_status(status, destino):
id__in=lista, id__in=lista,
status=status, status=status,
unidade_tramitacao_destino=destino).distinct().values_list( unidade_tramitacao_destino=destino).distinct().values_list(
'materia_id', flat=True) 'documento_id', flat=True)

25
sapl/protocoloadm/migrations/0007_auto_20180924_1724.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-09-24 20:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0006_documentoadministrativo_restrito'),
]
operations = [
migrations.AlterField(
model_name='documentoadministrativo',
name='tramitacao',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Em Tramitação?'),
),
migrations.AlterField(
model_name='protocolo',
name='anulado',
field=models.BooleanField(default=False),
),
]

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 = [
]

34
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,
@ -89,7 +89,7 @@ class Protocolo(models.Model):
blank=True, null=True, verbose_name=_('Número de Páginas')) blank=True, null=True, verbose_name=_('Número de Páginas'))
observacao = models.TextField( observacao = models.TextField(
blank=True, verbose_name=_('Observação')) blank=True, verbose_name=_('Observação'))
anulado = models.BooleanField() anulado = models.BooleanField(default=False)
user_anulacao = models.CharField(max_length=20, blank=True) user_anulacao = models.CharField(max_length=20, blank=True)
ip_anulacao = models.CharField(max_length=15, blank=True) ip_anulacao = models.CharField(max_length=15, blank=True)
justificativa_anulacao = models.CharField( justificativa_anulacao = models.CharField(
@ -135,7 +135,8 @@ class DocumentoAdministrativo(models.Model):
blank=True, null=True, verbose_name=_('Data Fim Prazo')) blank=True, null=True, verbose_name=_('Data Fim Prazo'))
tramitacao = models.BooleanField( tramitacao = models.BooleanField(
verbose_name=_('Em Tramitação?'), verbose_name=_('Em Tramitação?'),
choices=YES_NO_CHOICES) choices=YES_NO_CHOICES,
default=False)
assunto = models.TextField(verbose_name=_('Assunto')) assunto = models.TextField(verbose_name=_('Assunto'))
numero_externo = models.PositiveIntegerField( numero_externo = models.PositiveIntegerField(
blank=True, blank=True,
@ -297,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$',

189
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
@ -19,25 +22,30 @@ from django_filters.views import FilterView
import sapl import sapl
import logging import logging
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, '')
@ -93,6 +101,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):
materia_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:
@ -585,9 +723,10 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
kwargs = {'data': self.request.GET or None} kwargs = {'data': self.request.GET or None}
status_tramitacao = self.request.GET.get('tramitacao__status') status_tramitacao = self.request.GET.get(
'tramitacaoadministrativo__status')
unidade_destino = self.request.GET.get( unidade_destino = self.request.GET.get(
'tramitacao__unidade_tramitacao_destino') 'tramitacaoadministrativo__unidade_tramitacao_destino')
qs = self.get_queryset() qs = self.get_queryset()
@ -702,8 +841,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):

16
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,10 +147,14 @@ 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> - ' + votacao['id_materia'] + '</para>\n' + '<para style="P3"><b>Processo: </b>' + votacao[ tmp += '<tr><td><para style="P3"><b>' + str(votacao['num_ordem']) + '</b> - ' + \
'des_numeracao'] + '</para>\n' + '<para style="P3"><b>Turno: </b>' + votacao['des_turno'] + '</para>\n' + '<para style="P3"><b>Autor: </b>' + 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">' + \
votacao['txt_ementa'] + '</para></td>\n' str(votacao['txt_ementa']) + '</para></td>\n'
tmp += '<td><para style="P3">' + \ tmp += '<td><para style="P3">' + \
str(votacao['des_situacao']) + '</para></td></tr>\n' str(votacao['des_situacao']) + '</para></td></tr>\n'

23
sapl/relatorios/views.py

@ -766,16 +766,7 @@ def get_sessao_plenaria(sessao, casa):
def get_turno(dic, materia, sessao_data_inicio): def get_turno(dic, materia, sessao_data_inicio):
descricao_turno = ' ' descricao_turno = ' '
descricao_tramitacao = ' ' descricao_tramitacao = ' '
tramitacao = Tramitacao.objects.filter(materia=materia, tramitacao = None
turno__isnull=False,
data_tramitacao__lte=sessao_data_inicio,
).exclude(turno__exact=''
).select_related(
'materia',
'status',
'materia__tipo').order_by(
'-data_tramitacao'
).first()
if tramitacao is None: if tramitacao is None:
tramitacao = materia.tramitacao_set.last() tramitacao = materia.tramitacao_set.last()
@ -784,7 +775,7 @@ def get_turno(dic, materia, sessao_data_inicio):
if t[0] == tramitacao.turno: if t[0] == tramitacao.turno:
descricao_turno = t[1] descricao_turno = t[1]
break break
descricao_tramitacao = tramitacao.status.descricao if tramitacao.status else ' ' descricao_tramitacao = tramitacao.status.descricao if tramitacao.status else 'Não informada'
return (descricao_turno, descricao_tramitacao) return (descricao_turno, descricao_tramitacao)
@ -1058,7 +1049,7 @@ def get_pauta_sessao(sessao, casa):
inf_basicas_dic["nom_sessao"] = sessao.tipo.nome inf_basicas_dic["nom_sessao"] = sessao.tipo.nome
inf_basicas_dic["num_sessao_plen"] = sessao.numero inf_basicas_dic["num_sessao_plen"] = sessao.numero
inf_basicas_dic["num_legislatura"] = sessao.legislatura inf_basicas_dic["num_legislatura"] = sessao.legislatura
inf_basicas_dic["num_sessao_leg"] = sessao.legislatura inf_basicas_dic["num_sessao_leg"] = sessao.sessao_legislativa.numero
inf_basicas_dic["dat_inicio_sessao"] = sessao.data_inicio inf_basicas_dic["dat_inicio_sessao"] = sessao.data_inicio
inf_basicas_dic["hr_inicio_sessao"] = sessao.hora_inicio inf_basicas_dic["hr_inicio_sessao"] = sessao.hora_inicio
inf_basicas_dic["dat_fim_sessao"] = sessao.data_fim inf_basicas_dic["dat_fim_sessao"] = sessao.data_fim
@ -1073,6 +1064,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(
@ -1125,6 +1117,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)
@ -1133,10 +1126,8 @@ def get_pauta_sessao(sessao, casa):
dic_votacao["des_numeracao"] = ' ' dic_votacao["des_numeracao"] = ' '
numeracao = Numeracao.objects.filter( numeracao = Numeracao.objects.filter(materia=votacao.materia).first()
materia=votacao.materia).first() if numeracao:
if numeracao is not None:
numeracao = numeracao.first()
dic_votacao["des_numeracao"] = str( dic_votacao["des_numeracao"] = str(
numeracao.numero_materia) + '/' + str(numeracao.ano_materia) numeracao.numero_materia) + '/' + str(numeracao.ano_materia)

3
sapl/rules/map_rules.py

@ -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
@ -300,6 +302,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]

3
sapl/sessao/forms.py

@ -87,7 +87,8 @@ class SessaoPlenariaForm(ModelForm):
sessoes = SessaoPlenaria.objects.filter(numero=num, sessoes = SessaoPlenaria.objects.filter(numero=num,
sessao_legislativa=sl, sessao_legislativa=sl,
legislatura=leg, legislatura=leg,
tipo=tipo).\ tipo=tipo,
data_inicio__year=abertura.year).\
values_list('id', flat=True) values_list('id', flat=True)
qtd_sessoes = len(sessoes) qtd_sessoes = len(sessoes)

4
sapl/sessao/tests/test_sessao.py

@ -51,7 +51,7 @@ def test_numero_duplicado_sessao_plenaria_form():
legislatura = mommy.make(Legislatura) legislatura = mommy.make(Legislatura)
sessao = mommy.make(SessaoLegislativa) sessao = mommy.make(SessaoLegislativa)
tipo = mommy.make(TipoSessaoPlenaria) tipo = mommy.make(TipoSessaoPlenaria)
mommy.make(SessaoPlenaria, sessao_plenaria = mommy.make(SessaoPlenaria,
legislatura=legislatura, legislatura=legislatura,
sessao_legislativa=sessao, sessao_legislativa=sessao,
tipo=tipo, tipo=tipo,
@ -61,7 +61,7 @@ def test_numero_duplicado_sessao_plenaria_form():
'numero': '1', 'numero': '1',
'tipo': str(tipo.pk), 'tipo': str(tipo.pk),
'sessao_legislativa': str(sessao.pk), 'sessao_legislativa': str(sessao.pk),
'data_inicio': '10/11/2017', 'data_inicio': sessao_plenaria.data_inicio,
'hora_inicio': '10:10' 'hora_inicio': '10:10'
}) })

7
sapl/sessao/views.py

@ -1477,6 +1477,7 @@ class ResumoAtaView(ResumoView):
logger.info('- Gerando Resumo.') logger.info('- Gerando Resumo.')
class ExpedienteView(FormMixin, DetailView): class ExpedienteView(FormMixin, DetailView):
template_name = 'sessao/expediente.html' template_name = 'sessao/expediente.html'
form_class = ExpedienteForm form_class = ExpedienteForm
@ -2863,9 +2864,6 @@ class AdicionarVariasMateriasExpediente(PermissionRequiredForAppCrudMixin,
expediente = ExpedienteMateria() expediente = ExpedienteMateria()
expediente.sessao_plenaria_id = self.kwargs['pk'] expediente.sessao_plenaria_id = self.kwargs['pk']
expediente.materia_id = materia.id expediente.materia_id = materia.id
# TODO: o campo observacao deve ser uma copia de ML.ementa?
expediente.observacao = MateriaLegislativa.objects.get(
pk=materia.id).ementa
if lista_materias_expediente: if lista_materias_expediente:
posicao = lista_materias_expediente.last().numero_ordem + 1 posicao = lista_materias_expediente.last().numero_ordem + 1
expediente.numero_ordem = posicao expediente.numero_ordem = posicao
@ -2939,9 +2937,6 @@ class AdicionarVariasMateriasOrdemDia(AdicionarVariasMateriasExpediente):
ordem_dia = OrdemDia() ordem_dia = OrdemDia()
ordem_dia.sessao_plenaria_id = self.kwargs['pk'] ordem_dia.sessao_plenaria_id = self.kwargs['pk']
ordem_dia.materia_id = materia.id ordem_dia.materia_id = materia.id
# TODO: o campo observacao deve ser uma copia de ML.ementa?
ordem_dia.observacao = MateriaLegislativa.objects.get(
pk=materia.id).ementa
if lista_materias_ordem_dia: if lista_materias_ordem_dia:
posicao = lista_materias_ordem_dia.last().numero_ordem + 1 posicao = lista_materias_ordem_dia.last().numero_ordem + 1
ordem_dia.numero_ordem = posicao ordem_dia.numero_ordem = posicao

6
sapl/settings.py

@ -13,7 +13,6 @@ Quick-start development settings - unsuitable for production
See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
""" """
import logging
from decouple import config from decouple import config
from dj_database_url import parse as db_url from dj_database_url import parse as db_url
@ -240,7 +239,8 @@ LANGUAGES = (
TIME_ZONE = config('TZ', default='America/Sao_Paulo') TIME_ZONE = config('TZ', default='America/Sao_Paulo')
if not TIME_ZONE: if not TIME_ZONE:
raise ValueError('TIMEZONE env variable undefined in .env settings file! Leaving...') raise ValueError(
'TIMEZONE env variable undefined in .env settings file! Leaving...')
USE_I18N = True USE_I18N = True
USE_L10N = True USE_L10N = True
@ -348,7 +348,7 @@ def excepthook(*args):
logging.getLogger(BASE_DIR.name).error( logging.getLogger(BASE_DIR.name).error(
'Uncaught exception:', exc_info=args) 'Uncaught exception:', exc_info=args)
# sys.excepthook = excepthook # sys.excepthook = excepthook"""
PASSWORD_HASHERS = [ PASSWORD_HASHERS = [

7
sapl/static/styles/_header.scss

@ -2,9 +2,4 @@
$logo-height: 0.8 * $navbar-height; $logo-height: 0.8 * $navbar-height;
$logo-margin: ($navbar-height - $logo-height) / 2; $logo-margin: ($navbar-height - $logo-height) / 2;
.logo img { $footer-height : 140px;
width: $logo-height;
height: $logo-height;
margin: $logo-margin $navbar-padding-horizontal;
}

188
sapl/static/styles/_home_index.scss

@ -0,0 +1,188 @@
.container-home {
position: relative;
padding: 2em 1.5em 1.5em 1.5em;
max-width: 1000px;
margin: 0 auto;
a:hover {
color: #444;
-webkit-transition: 0.3s ease-in;
-moz-transition: 0.3s ease-in;
-o-transition: 0.3s ease-in
}
#homeIndex {
text-align: center;
}
.homeBanner span {
color: white;
font-size: 32px;
font-weight: 600;
display: inline-block;
vertical-align: middle;
padding: 2px 45px 4px;
border: 2px solid;
}
.homeBanner::after {
display: inline-block;
vertical-align: middle;
height: 100%;
}
.homeBlock {
display: inline-block;
position: relative;
background-color: #F3F3F3;
width: 190px;
height: 260px;
margin: 3px;
text-align: center;
font-size: 0;
overflow: hidden;
}
.homeBlock > a {
display: block;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.homeBlock::after {
content: '';
display: inline-block;
vertical-align: middle;
height: 100%;
overflow: visible;
clear: none;
visibility: initial;
}
.homeContent {
position: relative;
padding: 10px;
text-align: justify;
font-size: 14px;
color: #FFF;
opacity: 0;
transition: opacity 0.5s ease;
display: inline-block;
vertical-align: middle;
}
.homeContent p {
display: block;
line-height: 13px;
font-size: 80%;
color: white;
}
.homeIcon {
position: relative;
display: inline-block;
width: 105px;
height: 105px;
border-radius: 50%;
background: #364347;
z-index: 1;
}
.homeIcon::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background: #364347;
top: 0;
left: 0;
transform: scale(0.95);
transition: transform 0.6s ease;
}
.homeIcon img {
position: absolute;
margin: auto;
top: 0;
bottom: 0;
right: 0;
left: 0;
transition: opacity 0.4s 0.4s ease;
}
.homeFront {
position: absolute;
top: 46%;
width: 100%;
font-size: 0;
transform: translateY(-60%);
}
.homeFront h2 {
position: absolute;
margin-top: 18px;
font-size: 22px;
font-weight: 700;
color: #595959 !important;
width: 100%;
padding: 0 6%;
z-index: 0;
}
.homeTitle {
display: block;
height: 32px;
text-align: center;
width: 100%;
opacity: 0;
transition: opacity 0.4s ease;
}
.homeTitle::before {
content: '';
display: inline-block;
vertical-align: middle;
height: 100%;
}
.homeTitle h2 {
display: inline-block;
vertical-align: middle;
max-width: 110px;
font-size: 14px;
color: white !important;
line-height: 1em;
}
.homeTitle img {
display: inline-block;
vertical-align: middle;
height: 30px;
margin-right: 5px;
}
.homeBlock:hover .homeIcon::before {
transform: scale(3.6) translateY(7px);
}
.homeBlock:hover .homeContent{
opacity: 1;
transition-delay: 0.2s;
}
.homeBlock:hover .homeIcon img {
opacity: 0;
transition-duration: 0.2s;
transition-delay: 0s;
}
.homeBlock:hover .homeTitle {
opacity: 1;
}
}

652
sapl/static/styles/app.scss

@ -1,73 +1,21 @@
@import "bootstrap/variables.scss"; @import "bootstrap/variables.scss";
@import "header"; @import "header";
.vcenter { @import "home_index";
display: inline-block;
vertical-align: middle;
float: none;
padding: 10px;
}
nav { html {
&.navbar { position: relative;
border-radius: 0; min-height: 100%;
font-size: 15px;
}
.navbar-nav {
& > li {
& > a {
padding-top: 0px;
padding-bottom: 0px;
line-height: $grid-gutter-width * 2.5;
&:hover {
background-color: $link-hover-color;
}
}
&:nth-child(2) {
& > .dropdown-menu {
right: auto;
}
}
}
&:last-child {
& > li:last-child {
a {
padding-right: 0px;
}
}
}
}
}
.masthead {
padding: 10px;
.nav {
clear:both;
}
.navbar-brand {
color: $headings-color;
font-size: 24px;
img.img-responsive {
height: 95px;
margin-right: $navbar-padding-horizontal;
}
small {
color: #93A4AA;
font-size: 75%;
line-height: 25px;
}
}
} }
body {
.navbar { margin-bottom: 160px;
margin-bottom: 0;
} }
.navbar-brand { h1, h2, h3, h4, h5, h6, form, dl, dt, dd, p, div, img, a {
padding: 0px; margin: 0;
padding: 0;
} }
// ADJUST DRUNKEN PARROT STYLES ########################################
h1, .h1 { h1, .h1 {
font-size: 30px; font-size: 30px;
} }
@ -87,21 +35,41 @@ h6, .h6 {
font-size: 12px; font-size: 12px;
} }
.page-header { p {
margin: 20px 0px 10px; margin: 0.5em 0;
.control-label {
font-weight: bold;
}
} }
.btn:hover, .btn:focus { label {
color: inherit; margin-bottom: 0;
line-height: 1;
}
fieldset {
fieldset {
font-size: 95%;
legend {
font-size: 18px;
}
}
}
.page-header {
margin: 20px 0px 10px;
} }
.caret { .caret {
/* Por padrão caret aponta para baixo*/
&.top { &.top {
transform: rotate(180deg); transform: rotate(180deg);
} }
} }
.btn:hover, .btn:focus {
color: inherit;
}
.btn-default { .btn-default {
&.btn-excluir { &.btn-excluir {
color: $btn-danger-bg; color: $btn-danger-bg;
@ -113,68 +81,19 @@ h6, .h6 {
} }
} }
.controls-file { .btn-cancel-iframe {
padding: 10px; position: relative;
border: 1px solid #d6e1e5; text-align: right;
border-radius: 4px; opacity: 0.5;
label.checkbox-inline {
margin: 0px;
display: block;
}
}
.help-block-danger {
margin: $grid-gutter-width / 2;
padding: $grid-gutter-width / 2;
border: 2px dashed #f00;
}
.controls-radio-checkbox {
padding: 0px;
border: 1px solid #d6e1e5;
border-radius: 4px;
min-height: 20px;
.help-block {
margin: $grid-gutter-width / 2;
padding: $grid-gutter-width / 2;
border: 2px dashed #d6e1e5;
}
label {
padding: 5px;
.icons {
top: 5px;
left: 8px;
}
&.checkbox-inline, &.radio-inline {
padding: 8px;
padding-left: 36px;
.icons {
top: 8px;
left: 8px;
}
}
}
.checkbox, .radio, .checkbox-inline, .radio-inline {
margin: 0;
&:hover { &:hover {
background-color: #d6e1e5; opacity: 1;
} }
a {
padding: 10px;
display: inline-block;
} }
} }
// #### CRUD DETAIL ########################################
p.control-label {
font-weight: bold;
}
// copied from bootstrap _forms.scss legend
// using @extend would require importing parts of bootstrap again and overriding drunken parrot css
// @import "bootstrap/mixins.scss";
// @import "bootstrap/forms.scss";
.legend { .legend {
display: block; display: block;
width: 100%; width: 100%;
@ -190,42 +109,27 @@ p.control-label {
.grid-gutter-width-right { .grid-gutter-width-right {
margin-right: $grid-gutter-width / 2; margin-right: $grid-gutter-width / 2;
} }
// #### footer ###########################################
// based on http://getbootstrap.com/examples/sticky-footer
$footer-height : 140px; .controls-file {
html { padding: 10px;
position: relative; border: 1px solid #d6e1e5;
min-height: 100%; border-radius: 4px;
} label.checkbox-inline {
body { margin: 0px;
margin-bottom: $footer-height + 20px; display: block;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
/* Set the fixed height of the footer here */
height: $footer-height;
background: #364347 none repeat scroll 0% 0%;
color: white;
text-align: center;
p {
color: white;
margin-top: 10px;
}
.container {
padding-top: 25px;
} }
} }
label { .help-block-danger {
margin-bottom: 0; margin: $grid-gutter-width / 2;
line-height: 1; padding: $grid-gutter-width / 2;
border: 2px dashed #f00;
} }
.control-label { .control-label {
margin: 0; margin: 0;
} }
.form-control-static { .form-control-static {
padding-top: 0; padding-top: 0;
min-height: auto; min-height: auto;
@ -234,8 +138,6 @@ label {
} }
} }
// #### pagination ########################################
.pagination { .pagination {
padding-top: 25px; padding-top: 25px;
} }
@ -246,15 +148,6 @@ label {
} }
} }
fieldset {
fieldset {
font-size: 95%;
legend {
font-size: 18px;
}
}
}
.avatar-parlamentar { .avatar-parlamentar {
height: 128px; height: 128px;
width: 128px; width: 128px;
@ -262,229 +155,121 @@ fieldset {
display: table; display: table;
} }
/* INDEX */
#conteudo {
position: relative;
padding: 2em 1.5em 1.5em 1.5em;
overflow: hidden;
font-size: 100%;
text-align: left;
min-height: 350px;
max-width: 1000px;
margin: 0 auto;
}
#conteudo a:hover {
color: #444;
-webkit-transition: 0.3s ease-in;
-moz-transition: 0.3s ease-in;
-o-transition: 0.3s ease-in
}
#homeIndex {
text-align: center;
}
.homeBanner span {
color: white;
font-size: 32px;
font-weight: 600;
display: inline-block;
vertical-align: middle;
padding: 2px 45px 4px;
border: 2px solid;
}
.homeBanner::after {
display: inline-block;
vertical-align: middle;
height: 100%;
}
.homeBlock {
display: inline-block;
position: relative;
background-color: #F3F3F3;
width: 190px;
height: 260px;
margin: 3px;
text-align: center;
font-size: 0;
overflow: hidden;
}
.homeBlock > a {
display: block;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.homeBlock::after {
content: '';
display: inline-block;
vertical-align: middle;
height: 100%;
overflow: visible;
clear: none;
visibility: initial;
}
.homeContent { .masthead {
position: relative;
padding: 10px; padding: 10px;
text-align: justify; .nav {
font-size: 14px; clear:both;
color: #FFF; }
opacity: 0; .navbar-brand {
transition: opacity 0.5s ease; padding: 0px;
display: inline-block; color: $headings-color;
vertical-align: middle; font-size: 24px;
} img.img-responsive {
height: 95px;
.homeContent p { margin-right: $navbar-padding-horizontal;
display: block; }
line-height: 13px; small {
font-size: 80%; color: #93A4AA;
color: white; font-size: 75%;
} line-height: 25px;
}
.homeIcon { .vcenter {
position: relative;
display: inline-block;
width: 105px;
height: 105px;
border-radius: 50%;
background: #364347;
z-index: 1;
}
.homeIcon::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background: #364347;
top: 0;
left: 0;
transform: scale(0.95);
transition: transform 0.6s ease;
}
.homeIcon img {
position: absolute;
margin: auto;
top: 0;
bottom: 0;
right: 0;
left: 0;
transition: opacity 0.4s 0.4s ease;
}
.homeFront {
position: absolute;
top: 46%;
width: 100%;
font-size: 0;
transform: translateY(-60%);
}
.homeFront h2 {
position: absolute;
margin-top: 18px;
font-size: 22px;
font-weight: 700;
color: #595959 !important;
width: 100%;
padding: 0 6%;
z-index: 0;
}
.homeTitle {
display: block;
height: 32px;
text-align: center;
width: 100%;
opacity: 0;
transition: opacity 0.4s ease;
}
.homeTitle::before {
content: '';
display: inline-block;
vertical-align: middle;
height: 100%;
}
.homeTitle h2 {
display: inline-block;
vertical-align: middle;
max-width: 110px;
font-size: 14px;
color: white !important;
line-height: 1em;
}
.homeTitle img {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
height: 30px; float: none;
margin-right: 5px; padding: 10px;
} }
}
.homeBlock:hover .homeIcon::before {
transform: scale(3.6) translateY(7px);
} }
.homeBlock:hover .homeContent{ nav {
opacity: 1; &.navbar {
transition-delay: 0.2s; margin-bottom: 0;
} border-radius: 0;
font-size: 15px;
}
.navbar-nav {
& > li {
& > a {
padding-top: 0px;
padding-bottom: 0px;
line-height: $grid-gutter-width * 2.5;
&:hover {
background-color: $link-hover-color;
}
}
&:nth-child(2) {
& > .dropdown-menu {
right: auto;
}
}
.homeBlock:hover .homeIcon img { }
opacity: 0; &:last-child {
transition-duration: 0.2s; & > li:last-child {
transition-delay: 0s; a {
padding-right: 0px;
}
}
}
}
} }
.homeBlock:hover .homeTitle { .controls-radio-checkbox {
opacity: 1; padding: 0px;
} border: 1px solid #d6e1e5;
border-radius: 4px;
min-height: 20px;
.help-block {
margin: $grid-gutter-width / 2;
padding: $grid-gutter-width / 2;
border: 2px dashed #d6e1e5;
}
h1, h2, h3, h4, h5, h6, form, dl, dt, dd, p, div, img, a { label {
padding: 5px;
.icons {
top: 5px;
left: 8px;
}
&.checkbox-inline, &.radio-inline {
padding: 8px;
padding-left: 36px;
.icons {
top: 8px;
left: 8px;
}
}
}
.checkbox, .radio, .checkbox-inline, .radio-inline {
margin: 0; margin: 0;
padding: 0; &:hover {
} background-color: #d6e1e5;
}
p { }
margin: 0.5em 0;
} }
/* FIM INDEX */
/* TEMPLATE AJUDA */ .manual {
.manual li { &, & ul {
padding-left: 1.5em;
list-style-type: none;
margin-top: 0;
font-size: 100%;
}
li {
display: list-item; display: list-item;
line-height: 1.5em; line-height: 1.5em;
padding-right: 0; padding-right: 0;
} a {
.manual li a {
background-color: transparent; background-color: transparent;
border: none; border: none;
border-radius: none; border-radius: none;
padding: 0; padding: 0;
}
}
} }
.manual, .manual ul {
padding-left: 1.5em;
list-style-type: none;
margin-top: 0;
font-size: 100%;
}
/* FIM TEMPLATE AJUDA */
.container-tabaux { .container-tabaux {
.sidebar-tabaux { .sidebar-tabaux {
background: #fafafa; background: #fafafa;
@ -558,29 +343,46 @@ p {
} }
} }
.btn-cancel-iframe { #styleparlamentar {
position: relative; border: 0px solid #d6e1e5;
text-align: right; border-top-color: rgb(214, 225, 229);
opacity: 0.5; border-right-color: rgb(214, 225, 229);
&:hover { border-bottom-color: rgb(214, 225, 229);
opacity: 1; border-left-color: rgb(214, 225, 229);
border-image-source: initial;
border-image-slice: initial;
border-image-repeat: initial;
font-size: 16px;
line-height: 1.467;
padding: 7px 12px;
height: 40px;
-webkit-appearance: none;
border-radius: 4px;
-webkit-box-shadow: none;
box-shadow: none;
margin-left: 1.0em;
}
.footer {
background: #364347;
color: white;
text-align: center;
position: absolute;
width: 100%;
bottom: 0px;
p {
color: white;
margin-top: 10px;
} }
a { .container {
padding: 10px; padding-top: 25px;
display: inline-block;
} }
} }
@media (max-width: 1199px) { @media (max-width: 1199px) {
.masthead { nav {
.navbar-brand { .container {
font-size: 22px; width: auto !important;
img.img-responsive {
height: 60px;
width: 60px;
margin-right: $navbar-padding-horizontal / 2;
}
} }
} }
.navbar-nav > li > a { .navbar-nav > li > a {
@ -589,14 +391,76 @@ p {
} }
} }
@media (max-width: 1091px) { @media (max-width: 1091px) {
.container {
width: auto;
}
.navbar-nav > li > a { .navbar-nav > li > a {
padding-left: $grid-gutter-width / 4; padding-left: $grid-gutter-width / 4;
padding-right: $grid-gutter-width / 4; padding-right: $grid-gutter-width / 4;
} }
.masthead {
.navbar-brand {
font-size: 22px;
img.img-responsive {
height: 60px;
margin-right: $navbar-padding-horizontal / 2;
}
}
}
} }
@media (max-width: 991px) {
body {
margin: 0;
}
.footer {
position: relative;
}
.caret {
margin-left: 1px;
}
.navbar-nav > li > a {
padding-left: 4px;
padding-right: 4px;
}
}
@media (max-width: 767px) {
nav {
.navbar-nav > li > a {
line-height: 2.5;
}
.navbar-right {
position: absolute;
top: 0;
margin: 10px;
& > li {
vertical-align: top;
display: inline-block;
a {
padding-left: 10px;
padding-right: 10px;
}
}
.pesquisa.open {
ul {
position: absolute;
}
}
.navbar-form {
margin: 8px 0;
}
}
}
.table{
width: auto;
white-space: normal;
display:block;
overflow-x: auto;
}
}
@media (min-width: 1092px) and (max-width: 1199px) { @media (min-width: 1092px) and (max-width: 1199px) {
.container { .container {
@ -604,24 +468,8 @@ p {
} }
} }
/* Estilização da Listagem de Votos em sessões plenárias */ @media print {
a[href]:after {
#styleparlamentar { content: none !important;
border: 0px solid #d6e1e5; }
border-top-color: rgb(214, 225, 229);
border-right-color: rgb(214, 225, 229);
border-bottom-color: rgb(214, 225, 229);
border-left-color: rgb(214, 225, 229);
border-image-source: initial;
border-image-slice: initial;
border-image-repeat: initial;
font-size: 16px;
line-height: 1.467;
padding: 7px 12px;
height: 40px;
-webkit-appearance: none;
border-radius: 4px;
-webkit-box-shadow: none;
box-shadow: none;
margin-left: 1.0em;
} }

3
sapl/static/styles/compilacao.scss

@ -290,6 +290,7 @@ a:link:after, a:visited:after {
margin-top: 0.6em; margin-top: 0.6em;
font-size: 1.15em; font-size: 1.15em;
} }
.page-break { page-break-before: always; }
.bloco_alteracao { .bloco_alteracao {
padding-left: 10%; padding-left: 10%;
@ -303,9 +304,9 @@ a:link:after, a:visited:after {
a, table, table td { a, table, table td {
color: #018 !important; color: #018 !important;
} }
} }
.dn { /* Notas de Dispositivo*/ .dn { /* Notas de Dispositivo*/
font-weight: normal; font-weight: normal;
position: relative; position: relative;

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>

5
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">
@ -184,6 +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.126</span>
</p> </p>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
@ -192,7 +193,7 @@
</a> </a>
<p> <p>
<small> <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> Conteúdo e dados sob licença <a href="https://creativecommons.org">Creative Commons</a> 4.0<br><a href="https://creativecommons.org/licenses/by/4.0/">Atribuir Fonte - Compartilhar Igual</a>
</small> </small>
</p> </p>
</div> </div>

4
sapl/templates/comissoes/composicao_list.html

@ -37,7 +37,9 @@
</a> </a>
</div> </div>
{% endif %} {% endif %}
<br />
<div class="clearfix"></div>
<div class="container-table"> <div class="container-table">
<table class="table table-striped table-hover table-link-ordering"> <table class="table table-striped table-hover table-link-ordering">

4
sapl/templates/compilacao/dispositivo_form_search_fragment.html

@ -64,7 +64,7 @@
<div class="{{ dpt.tipo_dispositivo.class_css }}"> <div class="{{ dpt.tipo_dispositivo.class_css }}">
<div class="dptt {% dispositivo_desativado dpt None None %}" id="dptt{{dpt.pk}}" > <div class="dptt {% dispositivo_desativado dpt None None %}" id="dptt{{dpt.pk}}" >
{{ dpt.tipo_dispositivo.rotulo_prefixo_html|safe }} {{ dpt.tipo_dispositivo.rotulo_prefixo_html|safe }}
{% if dpt.rotulo or dpt.nivel = 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}] - {% endif %} {% if dpt.rotulo or dpt.nivel == 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}] - {% endif %}
{{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }}
<span class="dtxt" <span class="dtxt"
@ -104,7 +104,7 @@
<div class="{{ df.tipo_dispositivo.class_css }}"> <div class="{{ df.tipo_dispositivo.class_css }}">
<div class="dptt {% dispositivo_desativado dpt None None %}" id="dptt{{df.pk}}" > <div class="dptt {% dispositivo_desativado dpt None None %}" id="dptt{{df.pk}}" >
{{ df.tipo_dispositivo.rotulo_prefixo_html|safe }} {{ df.tipo_dispositivo.rotulo_prefixo_html|safe }}
<a name="{{df.pk}}" title="{{df.pk}}">{% if df.rotulo or df.nivel = 1%}{{ df.rotulo }}{%else%}[{{ df|nomenclatura}} {% trans "de" %} {{ df.dispositivo_pai.rotulo }}] - {% endif %}</a> <a name="{{df.pk}}" title="{{df.pk}}">{% if df.rotulo or df.nivel == 1%}{{ df.rotulo }}{%else%}[{{ df|nomenclatura}} {% trans "de" %} {{ df.dispositivo_pai.rotulo }}] - {% endif %}</a>
{{ df.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ df.tipo_dispositivo.rotulo_sufixo_html|safe }}
<span class="dtxt" id="d{% if not df.dispositivo_subsequente_id and df.dispositivo_substituido_id %}a{% endif %}{{df.pk}}" pks="{{df.dispositivo_substituido_id|default:''}}" pk="{{df.pk}}">{{ df.tipo_dispositivo.texto_prefixo_html|safe }}{%if df.texto %}{{ df.texto|safe }}{%else%}{%if not df.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span> <span class="dtxt" id="d{% if not df.dispositivo_subsequente_id and df.dispositivo_substituido_id %}a{% endif %}{{df.pk}}" pks="{{df.dispositivo_substituido_id|default:''}}" pk="{{df.pk}}">{{ df.tipo_dispositivo.texto_prefixo_html|safe }}{%if df.texto %}{{ df.texto|safe }}{%else%}{%if not df.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
{% if df.ta_publicado_id %} {% if df.ta_publicado_id %}

2
sapl/templates/compilacao/layout/dispositivo_radio.html

@ -23,7 +23,7 @@
<div class="{{ dpt.tipo_dispositivo.class_css }}"> <div class="{{ dpt.tipo_dispositivo.class_css }}">
<div class="dptt {% dispositivo_desativado dpt None None %}" id="dptt{{dpt.pk}}" > <div class="dptt {% dispositivo_desativado dpt None None %}" id="dptt{{dpt.pk}}" >
{{ dpt.tipo_dispositivo.rotulo_prefixo_html|safe }} {{ dpt.tipo_dispositivo.rotulo_prefixo_html|safe }}
{% if dpt.rotulo or dpt.nivel = 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}] - {% endif %} {% if dpt.rotulo or dpt.nivel == 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}] - {% endif %}
{{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }}
<span class="dtxt" <span class="dtxt"
id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}" id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}"

86
sapl/templates/compilacao/text_list__embedded.html

@ -0,0 +1,86 @@
{% load i18n %}
{% load compilacao_filters %}
{% load common_tags %}
{% load staticfiles %}
{% load sass_tags %}
{% block head_content %}
<link rel="stylesheet" href="{% sass_src 'styles/compilacao.scss' %}" type="text/css">
{% endblock %}
<div class="cp">
{% if object_list %}
<div class="clearfix">
<div class="actions btn-group pull-right" role="group">
<a class="btn btn-default" id="btn_font_menos" title="Diminuir tamanho da letra">a</a>
<a class="btn btn-default" id="btn_font_mais" title="Aumentar tamanho da Letra">A</a>
</div>
</div>
{% endif %}
{% for key, values in view.get_vigencias.items %}
{% if forloop.first %}
<ul class="nav nav-pills cp-vigencias">
{% for dispositivo in values %}
<li class="{% if view.inicio_vigencia == dispositivo.inicio_vigencia %}active{%endif%}">
<a href="{% url 'sapl.compilacao:ta_vigencia' dispositivo.ta_id dispositivo|get_sign_vigencia %}" title="{% trans 'Vigência entre'%} {{dispositivo.inicio_vigencia}} {% trans 'e'%} {{dispositivo.fim_vigencia}}">{% trans 'Texto Original'%}</a>
</li>
{% endfor %}
{% elif forloop.last %}
{% for dispositivo in values %}
<li class="{% if not view.inicio_vigencia%}active{% endif %}">
<a href="{% url 'sapl.compilacao:ta_text' dispositivo.ta_id %}" title="{% trans 'Compilação atual'%}.&#013;{% trans 'Vigência a partir de'%} {{dispositivo.inicio_vigencia}}&#013;{% if dispositivo.ta_publicado_id in ta_pub_list %}{{ ta_pub_list|lookup:dispositivo.ta_publicado_id }}{%else%}{%if dispositivo.ta_publicado %}{{dispositivo.ta_publicado}}{%else%}{{dispositivo.ta}}{%endif%}{%endif%}">{% trans 'Texto Atual'%}</a>
</li>
{% if forloop.parentloop.last %}
</ul>
<span class="vigencia-active">
{% if view.inicio_vigencia and view.fim_vigencia %}
{% blocktrans with inicio_vigencia=view.inicio_vigencia fim_vigencia=view.fim_vigencia%}
Vigência entre <b>{{inicio_vigencia}}</b> e <b>{{fim_vigencia}}</b>.
{% endblocktrans%}
{% else%}
{% blocktrans with inicio_vigencia=dispositivo.inicio_vigencia%}
Vigência a partir de <b>{{inicio_vigencia}}</b>.
{% endblocktrans%}
{% endif %}
<br>
{% if view.ta_vigencia %}
<small>{% trans 'Dada por '%}<a href="{% url 'sapl.compilacao:ta_text' view.ta_vigencia %}">{{ta_pub_list|lookup:view.ta_vigencia}}</a></small>
{% elif view.ta_vigencia and view.ta_vigencia != 0%}
<small>{% trans 'Dada por '%}<a href="{% url 'sapl.compilacao:ta_text' dispositivo.ta_publicado.pk %}">{{dispositivo.ta_publicado}}</a></small>
{% endif %}
</span>
{% endif%}
{% endfor %}
{% else %}
<li class="{% if values.inicio_vigencia.year == key %}active{% endif %}"><a data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{{ key }}</a>
<ul class="dropdown-menu">
<div class="arrow top"></div>
{% for dispositivo in values %}
<li>
{% if not forloop.parentloop.first %}
<a href="{% url 'sapl.compilacao:ta_vigencia' dispositivo.ta_id dispositivo|get_sign_vigencia %}" title="{% if dispositivo.ta_publicado_id in ta_pub_list%}{{ ta_pub_list|lookup:dispositivo.ta_publicado_id }}{%else%}{%if dispositivo.ta_publicado %}{{dispositivo.ta_publicado}}{%else%}{{dispositivo.ta}}{%endif%}{%endif%}">{% trans 'Vigência entre'%} {{dispositivo.inicio_vigencia}} {% trans 'e'%} {{dispositivo.fim_vigencia}}</a>
{% endif %}
</li>
{% endfor %}
</ul>
</li>
{% endif %}
{% endfor %}
<ul class="tipo-vigencias">
<li><a class="selected" onclick="textoMultiVigente(this, false);" title="{% trans 'Texto Multivigente Sequencial'%}">{% trans 'TMS'%}</a></li>
<li><a onclick="textoMultiVigente(this, true);" title="{% trans 'Texto Multivigente Integrado com Realce de Alterações'%}">{% trans 'TMI'%}</a></li>
<li><a onclick="textoMultiVigente(this, false); textoVigente(this, true);" title="{% trans 'Texto Vigente COM Links para Textos Alteradores'%}">{% trans 'TVL'%}</a></li>
<li><a onclick="textoVigente(this, false);" title="{% trans 'Texto Vigente'%}">{% trans 'TVT'%}</a></li>
</ul>
{% include 'compilacao/text_list_bloco.html'%}
</div>
<script type="text/javascript" src="{% static 'js/compilacao.js' %}"></script>
<script type="text/javascript" src="{% static 'js/compilacao_view.js' %}"></script>
{% if perms.compilacao.add_nota %}
<script type="text/javascript" src="{% static 'js/compilacao_notas.js' %}"></script>
{% endif %}

2
sapl/templates/compilacao/text_notificacoes.html

@ -24,7 +24,7 @@
<div class="{{ dpt.tipo_dispositivo.class_css }}"> <div class="{{ dpt.tipo_dispositivo.class_css }}">
<div class="dptt {% dispositivo_desativado dpt None None %} {% if dpt.ta != object%}{{dpt.dispositivo_atualizador.tipo_dispositivo.class_css}}{% endif %}" id="dptt{{dpt.pk}}" > <div class="dptt {% dispositivo_desativado dpt None None %} {% if dpt.ta != object%}{{dpt.dispositivo_atualizador.tipo_dispositivo.class_css}}{% endif %}" id="dptt{{dpt.pk}}" >
{{ dpt.tipo_dispositivo.rotulo_prefixo_html|safe }} {{ dpt.tipo_dispositivo.rotulo_prefixo_html|safe }}
<a href="{% url 'sapl.compilacao:ta_text_edit' dpt.ta_id %}#{{dpt.pk}}">{% if dpt.rotulo or dpt.nivel = 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}}{% if dpt.dispositivo_pai_id %} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}{% endif %}] - {% endif %}</a> <a href="{% url 'sapl.compilacao:ta_text_edit' dpt.ta_id %}#{{dpt.pk}}">{% if dpt.rotulo or dpt.nivel == 1 %}{{ dpt.rotulo }}{%else%}[{{ dpt|nomenclatura}}{% if dpt.dispositivo_pai_id %} {% trans "de" %} {{ dpt.dispositivo_pai.rotulo }}{% endif %}] - {% endif %}</a>
{{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }}
<span class="dtxt" <span class="dtxt"
id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}" id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}"

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>

4
sapl/templates/crud/detail_detail.html

@ -63,6 +63,7 @@
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% endblock detail_content %} {% endblock detail_content %}
<div class="actions btn-group pull-right btn-group-lg" role="group"> <div class="actions btn-group pull-right btn-group-lg" role="group">
{% if view.detail_set_create_url %} {% if view.detail_set_create_url %}
<a href="{{ view.detail_set_create_url }}" class="btn btn-default"> <a href="{{ view.detail_set_create_url }}" class="btn btn-default">
@ -71,6 +72,9 @@
{% endif %} {% endif %}
{% block more_buttons %}{% endblock more_buttons %} {% block more_buttons %}{% endblock more_buttons %}
</div> </div>
<div class="clearfix"></div>
<div class="container-table"> <div class="container-table">
{% if not rows %} {% if not rows %}
<p>{{ NO_ENTRIES_MSG }}</p> <p>{{ NO_ENTRIES_MSG }}</p>

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

59
sapl/templates/index.html

@ -1,11 +1,11 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n staticfiles %} {% load i18n staticfiles %}
{% block title%} {% block title %}
<head>
</head> {% endblock title %}
<br /> {% block base_content%}
<div id="conteudo" style="min-height: 60px"> <div class="container-home">
<div id="homeIndex"> <div id="homeIndex">
<div class="homeBlock"> <div class="homeBlock">
<div class="homeFront"> <div class="homeFront">
@ -20,7 +20,11 @@
<h2>Mesa Diretora</h2> <h2>Mesa Diretora</h2>
</div> </div>
<p> <p>
Órgão colegiado, composto de no mínimo três membros efetivos - Presidente e 1° e 2° Secretários - a quem cabe a direção dos trabalhos legislativos. Os parlamentares integrantes da Mesa Diretora são eleitos por seus pares na primeira reunião de instalação do período legislativo, para um mandato de um a dois anos. Órgão colegiado, composto de no mínimo três membros efetivos - Presidente e 1° e 2° Secretários - a
quem cabe a direção dos trabalhos legislativos. Os parlamentares integrantes da Mesa Diretora são
eleitos por seus pares na primeira reunião de instalação do período legislativo, para um mandato de
um
a dois anos.
</p> </p>
</div> </div>
<a href="{% url 'sapl.parlamentares:mesa_diretora' %}"></a> <a href="{% url 'sapl.parlamentares:mesa_diretora' %}"></a>
@ -39,7 +43,11 @@
<h2>Comissões</h2> <h2>Comissões</h2>
</div> </div>
<p> <p>
Órgãos da Casa Legislativa, de natureza técnica especializada e que têm por objetivo prestar melhores esclarecimentos aos parlamentares para a tomada de decisões. Assim, as comissões elaboram estudos, pareceres a respeito de determinados projetos de lei e investigação de irregularidades sobre fato determinado. Órgãos da Casa Legislativa, de natureza técnica especializada e que têm por objetivo prestar
melhores
esclarecimentos aos parlamentares para a tomada de decisões. Assim, as comissões elaboram estudos,
pareceres a respeito de determinados projetos de lei e investigação de irregularidades sobre fato
determinado.
</p> </p>
</div> </div>
<a href="{% url 'sapl.comissoes:comissao_list' %}"></a> <a href="{% url 'sapl.comissoes:comissao_list' %}"></a>
@ -58,7 +66,11 @@
<h2>Parlamentares</h2> <h2>Parlamentares</h2>
</div> </div>
<p> <p>
O Poder Legislativo, exercido pelo sistema de representação, tem nos parlamentares a sua expressão máxima. Devem transformar os anseios de seus representados em ações diretas, na forma de leis ou buscando junto do Executivo obras e atos que beneficiem a sua comunidade. Possuem funções legisladora, administrativa, julgadora e de fiscalização sobre a conduta do Executivo. O Poder Legislativo, exercido pelo sistema de representação, tem nos parlamentares a sua expressão
máxima. Devem transformar os anseios de seus representados em ações diretas, na forma de leis ou
buscando junto do Executivo obras e atos que beneficiem a sua comunidade. Possuem funções
legisladora,
administrativa, julgadora e de fiscalização sobre a conduta do Executivo.
</p> </p>
</div> </div>
<a href="{% url 'sapl.parlamentares:parlamentar_list' %}"></a> <a href="{% url 'sapl.parlamentares:parlamentar_list' %}"></a>
@ -77,7 +89,12 @@
<h2>Pautas das<br>Sessões</h2> <h2>Pautas das<br>Sessões</h2>
</div> </div>
<p> <p>
Utilizadas para se determinar quais matérias serão discutidas e votadas. A responsabilidade pela elaboração das Pautas, que incluem Expediente e Ordem do Dia, é definida no Regimento Interno que, em geral, dá poderes ao Presidente da Casa Legislativa para a sua elaboração. Também, pode ficar a cargo de um colégio de líderes dos partidos políticos. Utilizadas para se determinar quais matérias serão discutidas e votadas. A responsabilidade pela
elaboração das Pautas, que incluem Expediente e Ordem do Dia, é definida no Regimento Interno que,
em
geral, dá poderes ao Presidente da Casa Legislativa para a sua elaboração. Também, pode ficar a
cargo
de um colégio de líderes dos partidos políticos.
</p> </p>
</div> </div>
<a href="{% url 'sapl.sessao:pauta_sessao' %}"></a> <a href="{% url 'sapl.sessao:pauta_sessao' %}"></a>
@ -96,7 +113,13 @@
<h2>Sessão Plenária</h2> <h2>Sessão Plenária</h2>
</div> </div>
<p> <p>
Foro apropriado para a tomada de decisões sobre os projetos de lei e outras matérias legislativas ou administrativas, aprovadas ou rejeitadas em votação pelos parlamentares. É dirigida pela Mesa Diretora de acordo com o Regimento Interno da Casa. As decisões votadas em Plenário são soberanas e prevalecem sobre interesses ou vontades individuais. Foro apropriado para a tomada de decisões sobre os projetos de lei e outras matérias legislativas
ou
administrativas, aprovadas ou rejeitadas em votação pelos parlamentares. É dirigida pela Mesa
Diretora
de acordo com o Regimento Interno da Casa. As decisões votadas em Plenário são soberanas e
prevalecem
sobre interesses ou vontades individuais.
</p> </p>
</div> </div>
<a href="{% url 'sapl.sessao:pesquisar_sessao' %}"></a> <a href="{% url 'sapl.sessao:pesquisar_sessao' %}"></a>
@ -115,7 +138,11 @@
<h2>Matérias<br>Legislativas</h2> <h2>Matérias<br>Legislativas</h2>
</div> </div>
<p> <p>
Têm início com o processo de criação de leis e a apresentação de projetos no Poder Legislativo. Na apreciação de matérias, podem haver eventuais conflitos de interpretação ou de entendimento entre o que estabelece o Regimento Interno da Casa e a Lei Orgânica do Município. Nestes casos, prevalece a Lei Orgânica. Têm início com o processo de criação de leis e a apresentação de projetos no Poder Legislativo. Na
apreciação de matérias, podem haver eventuais conflitos de interpretação ou de entendimento entre o
que
estabelece o Regimento Interno da Casa e a Lei Orgânica do Município. Nestes casos, prevalece a Lei
Orgânica.
</p> </p>
</div> </div>
<a href="{% url 'sapl.materia:pesquisar_materia' %}"></a> <a href="{% url 'sapl.materia:pesquisar_materia' %}"></a>
@ -134,7 +161,9 @@
<h2>Normas Jurídicas</h2> <h2>Normas Jurídicas</h2>
</div> </div>
<p> <p>
Nos Municípios, referem-se às emendas à Lei Orgânica, às leis complementares, às leis ordinárias, aos decretos legislativos e às resoluções. Nos Municípios, referem-se às emendas à Lei Orgânica, às leis complementares, às leis ordinárias,
aos
decretos legislativos e às resoluções.
</p> </p>
</div> </div>
<a href="{% url 'sapl.norma:norma_pesquisa'%}"></a> <a href="{% url 'sapl.norma:norma_pesquisa'%}"></a>
@ -153,12 +182,14 @@
<h2>Relatórios</h2> <h2>Relatórios</h2>
</div> </div>
<p> <p>
Contém informações estatísticas sobre a produção legislativa dos parlamentares e da Casa, dispostas e agrupadas de diferentes formas de acordo com parâmetros fornecidos. Contém informações estatísticas sobre a produção legislativa dos parlamentares e da Casa, dispostas
e
agrupadas de diferentes formas de acordo com parâmetros fornecidos.
</p> </p>
</div> </div>
<a href="/sistema/relatorios/"></a> <a href="/sistema/relatorios/"></a>
</div> </div>
</div> </div>
</div>
</div> </div>
{% endblock %} {% endblock %}

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

28
sapl/templates/norma/normajuridica_detail.html

@ -1,5 +1,5 @@
{% extends "crud/detail.html" %} {% extends "crud/detail.html" %}
{% load i18n common_tags%} {% load i18n common_tags staticfiles%}
{% block detail_content %} {% block detail_content %}
{% for fieldset in view.layout_display %} {% for fieldset in view.layout_display %}
@ -76,4 +76,30 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% if object.texto_articulado.exists and object.texto_articulado.first.has_view_permission %}
<hr />
<div class="row-fluid">
<div class="col-sm-12">
<br>
<p class="control-label">{%trans 'Texto Multivigente' %}</p>
<div id="textomultivigente"></div>
</div>
</div>
{% endif %}
{% endblock detail_content %} {% endblock detail_content %}
{% block extra_js %}
{% if object.texto_articulado.exists and object.texto_articulado.first.has_view_permission %}
<script language="Javascript">
window.onload = function () {
$.get("{% url 'sapl.compilacao:ta_text' object.texto_articulado.first.id %}?embedded",
function(data, status) {
$('#textomultivigente').html(data)
$('cp a[data-toggle="dropdown"]').dropdown()
$('.dne').remove()
});
}
</script>
{% endif %}
{% endblock extra_js %}

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>

3
sapl/templates/sessao/sessaoplenaria_filter.html

@ -34,6 +34,9 @@
<strong>Legislatura:</strong> {{s.legislatura}}</br> <strong>Legislatura:</strong> {{s.legislatura}}</br>
<strong>Sessão Legislativa:</strong> {{s.sessao_legislativa}}</br> <strong>Sessão Legislativa:</strong> {{s.sessao_legislativa}}</br>
<strong>Tipo:</strong> {{s.tipo}}</br> <strong>Tipo:</strong> {{s.tipo}}</br>
{% if s.upload_ata %}
<strong><a href="{{s.upload_ata.url}}">Ata da Sessão</strong></a></br>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

20
sapl/test_general.py

@ -1,10 +1,12 @@
import pytest
from django.apps import apps from django.apps import apps
from django.db.models import CharField, TextField from django.db.models import CharField, TextField
from django.db.models.fields import BooleanField
from model_mommy import mommy from model_mommy import mommy
import pytest
from .settings import SAPL_APPS from .settings import SAPL_APPS
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
sapl_appconfs = [apps.get_app_config(n[5:]) for n in SAPL_APPS] sapl_appconfs = [apps.get_app_config(n[5:]) for n in SAPL_APPS]
@ -34,3 +36,19 @@ def test_str_sanity():
msg = '%s.%s.__str__ is broken.' % ( msg = '%s.%s.__str__ is broken.' % (
model.__module__, model.__name__) model.__module__, model.__name__)
raise AssertionError(msg, exc) raise AssertionError(msg, exc)
def test_booleanfield_configure():
for app in sapl_appconfs:
for model in app.get_models():
for field in model._meta.get_fields():
if not isinstance(field, BooleanField):
continue
assert isinstance(field.default, bool), """
atributo 'default' não definido em:
Campo: %s
Model: %s
app: %s
""" % (field.name,
model._meta.object_name,
app.name)

16
sapl/utils.py

@ -1,14 +1,11 @@
from functools import wraps
import hashlib import hashlib
import logging from operator import itemgetter
import os import os
import re import re
import unicodedata
from functools import wraps
from operator import itemgetter
from unicodedata import normalize as unicodedata_normalize from unicodedata import normalize as unicodedata_normalize
import unicodedata
import django_filters
import magic
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button from crispy_forms.layout import HTML, Button
from django import forms from django import forms
@ -21,17 +18,15 @@ from django.core.exceptions import ValidationError
from django.db.models import Q from django.db.models import Q
from django.utils import six, timezone from django.utils import six, timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django_filters
from django_filters.filterset import STRICTNESS from django_filters.filterset import STRICTNESS
from easy_thumbnails import source_generators from easy_thumbnails import source_generators
from floppyforms import ClearableFileInput from floppyforms import ClearableFileInput
from reversion.admin import VersionAdmin import magic
from reversion_compare.admin import CompareVersionAdmin from reversion_compare.admin import CompareVersionAdmin
from unipath.path import Path from unipath.path import Path
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from sapl.settings import BASE_DIR
sapl_logger = logging.getLogger(BASE_DIR.name)
def pil_image(source, exif_orientation=False, **options): def pil_image(source, exif_orientation=False, **options):
@ -765,5 +760,6 @@ def RemoveTag(texto):
return textoSaida return textoSaida
def remover_acentos(string): def remover_acentos(string):
return unicodedata.normalize('NFKD', string).encode('ASCII', 'ignore').decode() return unicodedata.normalize('NFKD', string).encode('ASCII', 'ignore').decode()

2
setup.py

@ -49,7 +49,7 @@ install_requires = [
] ]
setup( setup(
name='interlegis-sapl', name='interlegis-sapl',
version='3.1.118', version='3.1.126',
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