Browse Source

Merge tag '3.1.159' into migracao

migracao
Marcio Mazza 6 years ago
parent
commit
0fbe113b1e
  1. 19
      docker-compose.yml
  2. 15
      release.sh
  3. 1
      requirements/requirements.txt
  4. 33
      sapl/audiencia/forms.py
  5. 57
      sapl/audiencia/tests/test_audiencia.py
  6. 72
      sapl/base/forms.py
  7. 42
      sapl/base/templatetags/common_tags.py
  8. 24
      sapl/base/tests/test_base.py
  9. 629
      sapl/base/tests/test_view_base.py
  10. 23
      sapl/base/urls.py
  11. 199
      sapl/base/views.py
  12. 46
      sapl/comissoes/forms.py
  13. 18
      sapl/comissoes/tests/test_comissoes.py
  14. 112
      sapl/compilacao/tests/test_compilacao.py
  15. 334
      sapl/materia/forms.py
  16. 21
      sapl/materia/migrations/0051_auto_20190703_1414.py
  17. 2
      sapl/materia/models.py
  18. 22
      sapl/materia/urls.py
  19. 287
      sapl/materia/views.py
  20. 38
      sapl/norma/forms.py
  21. 28
      sapl/norma/migrations/0025_auto_20190704_1403.py
  22. 18
      sapl/norma/models.py
  23. 51
      sapl/norma/views.py
  24. 3
      sapl/painel/views.py
  25. 6
      sapl/parlamentares/urls.py
  26. 11
      sapl/parlamentares/views.py
  27. 27
      sapl/protocoloadm/forms.py
  28. 25
      sapl/protocoloadm/migrations/0022_auto_20190715_1101.py
  29. 9
      sapl/protocoloadm/models.py
  30. 30
      sapl/protocoloadm/tests/test_protocoloadm.py
  31. 7
      sapl/protocoloadm/views.py
  32. 3
      sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py
  33. 4
      sapl/relatorios/urls.py
  34. 79
      sapl/relatorios/views.py
  35. 11
      sapl/rules/apps.py
  36. 45
      sapl/sessao/forms.py
  37. 20
      sapl/sessao/migrations/0041_sessaoplenaria_tema_solene.py
  38. 16
      sapl/sessao/migrations/0042_merge_20190612_0925.py
  39. 2
      sapl/sessao/models.py
  40. 8
      sapl/sessao/urls.py
  41. 260
      sapl/sessao/views.py
  42. 4
      sapl/settings.py
  43. 39
      sapl/static/sapl/css/relatorio.css
  44. 2
      sapl/templates/base.html
  45. 3
      sapl/templates/base/RelatorioAtas_filter.html
  46. 2
      sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html
  47. 67
      sapl/templates/base/RelatorioPresencaSessao_filter.html
  48. 35
      sapl/templates/base/anexadas_ciclicas.html
  49. 36
      sapl/templates/base/anexados_ciclicos.html
  50. 2
      sapl/templates/base/autores_duplicados.html
  51. 4
      sapl/templates/base/bancada_comissao_autor_externo.html
  52. 4
      sapl/templates/base/filiacoes_sem_data_filiacao.html
  53. 2
      sapl/templates/base/legislatura_infindavel.html
  54. 2
      sapl/templates/base/lista_inconsistencias.html
  55. 4
      sapl/templates/base/mandato_sem_data_inicio.html
  56. 4
      sapl/templates/base/materias_protocolo_inexistente.html
  57. 6
      sapl/templates/base/parlamentares_duplicados.html
  58. 8
      sapl/templates/base/parlamentares_filiacoes_intersecao.html
  59. 8
      sapl/templates/base/parlamentares_mandatos_intersecao.html
  60. 2
      sapl/templates/base/protocolos_com_materias.html
  61. 4
      sapl/templates/base/protocolos_duplicados.html
  62. 4
      sapl/templates/base/relatorios_list.html
  63. 2
      sapl/templates/comissoes/reuniao_detail.html
  64. 17
      sapl/templates/crud/detail.html
  65. 4
      sapl/templates/floppyforms/image_thumbnail.html
  66. 7
      sapl/templates/materia/despachoinicial_multicreate_form.html
  67. 10
      sapl/templates/materia/em_lote/acessorio.html
  68. 118
      sapl/templates/materia/em_lote/tramitacao.html
  69. 46
      sapl/templates/materia/materialegislativa_form.html
  70. 2
      sapl/templates/navbar.yaml
  71. 32
      sapl/templates/norma/normajuridica_detail.html
  72. 48
      sapl/templates/norma/normajuridica_form.html
  73. 27
      sapl/templates/painel/index.html
  74. 36
      sapl/templates/painel/voto_nominal.html
  75. 8
      sapl/templates/parlamentares/materias.html
  76. 1
      sapl/templates/protocoloadm/protocolo_list.html
  77. 2
      sapl/templates/protocoloadm/protocolo_mostrar.html
  78. 8
      sapl/templates/relatorios/header_ata.html
  79. 2
      sapl/templates/relatorios/relatorio_ata.html
  80. 5
      sapl/templates/relatorios/relatorio_doc_administrativos.html
  81. 183
      sapl/templates/relatorios/relatorio_sessao_plenaria.html
  82. 6
      sapl/templates/rest_framework_docs/base.html
  83. 8
      sapl/templates/sessao/blocos_ata/identificacao_basica.html
  84. 4
      sapl/templates/sessao/blocos_resumo/identificacao_basica.html
  85. 9
      sapl/templates/sessao/layouts.yaml
  86. 4
      sapl/templates/sessao/pauta_sessao_list.html
  87. 10
      sapl/templates/sessao/resumo.html
  88. 59
      sapl/templates/sessao/sessaoplenaria_form.html
  89. 34
      sapl/templates/sessao/subnav-solene.yaml
  90. 2
      setup.py
  91. 1
      solr/sapl_configset/conf/managed-schema
  92. BIN
      solr/sapl_configset/conf/saplconfigset.zip
  93. 166
      solr/sapl_configset/conf/schema.xml

19
docker-compose.yml

@ -11,8 +11,7 @@ sapldb:
ports:
- "5432:5432"
sapl:
image: interlegis/sapl:3.1.158
# build: .
image: interlegis/sapl:3.1.159
restart: always
environment:
ADMIN_PASSWORD: interlegis
@ -24,27 +23,11 @@ sapl:
EMAIL_HOST_USER: usuariosmtp
EMAIL_SEND_USER: usuariosmtp
EMAIL_HOST_PASSWORD: senhasmtp
# USE_SOLR: 'True'
# SOLR_COLLECTION: sapl
# SOLR_URL: http://saplsolr:8983
TZ: America/Sao_Paulo
volumes:
- sapl_data:/var/interlegis/sapl/data
- sapl_media:/var/interlegis/sapl/media
links:
- sapldb
# - saplsolr
ports:
- "80:80"
#saplsolr:
# image: solr:7.4-alpine
# restart: always
# command: bin/solr start -c -f
# volumes:
# - solr_data:/opt/solr/server/solr
# - solr_configsets:/opt/solr/server/solr/configsets
# ports:
# - "8983:8983"

15
release.sh

@ -1,4 +1,4 @@
#/bin/bash
#!/bin/bash
##
## Versioning info: [major].[minor].[patch][-RC[num]], example: 3.1.159, 3.1.159-RC1
@ -72,19 +72,21 @@ function commit_and_push {
git commit -m "Release: $FINAL_VERSION"
git tag $FINAL_VERSION
echo "sending to github..."
git push origin
git push origin $FINAL_VERSION
echo "Para enviar pro github execute..."
echo "git push origin 3.1.x"
echo "git push origin "$FINAL_VERSION
echo "done."
}
case "$1" in
--latest)
git fetch
echo $LATEST_VERSION
exit 0
;;
--major)
git fetch
set_major_version
echo "generating major release: "$FINAL_VERSION
# git tag $FINAL_VERSION
@ -93,6 +95,7 @@ case "$1" in
exit 0
;;
--rc)
git fetch
set_rc_version
echo "generating release candidate: "$FINAL_VERSION
# git tag $FINAL_VERSION
@ -100,10 +103,6 @@ case "$1" in
commit_and_push
exit 0
;;
--undo)
git tag -d $LATEST_VERSION
exit 0
;;
--top)
git tag | sort --version-sort | tail "-$2"
exit 0

1
requirements/requirements.txt

@ -15,6 +15,7 @@ django-extensions==2.1.4
django-image-cropping==1.2
django-webpack-loader==0.6.0
drf-yasg==1.13.0
ruamel.yaml>=0.15.34,<0.16.0
easy-thumbnails==2.5
python-decouple==3.1
psycopg2-binary==2.7.6.1

33
sapl/audiencia/forms.py

@ -1,6 +1,7 @@
import logging
from django import forms
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
@ -106,8 +107,6 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
else:
cleaned_data['numero'] = 1
if self.cleaned_data['hora_inicio'] and self.cleaned_data['hora_fim']:
if (self.cleaned_data['hora_fim'] <
self.cleaned_data['hora_inicio']):
@ -116,6 +115,22 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
self.logger.error('Hora de fim anterior à hora de início.')
raise ValidationError(msg)
upload_pauta = self.cleaned_data.get('upload_pauta', False)
upload_ata = self.cleaned_data.get('upload_ata', False)
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_pauta and upload_pauta.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Pauta da Audiência Pública deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_pauta.size/1024)/1024))
if upload_ata and upload_ata.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Ata da Audiência Pública deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_ata.size/1024)/1024))
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo da Audiência Pública deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
return cleaned_data
@ -140,3 +155,17 @@ class AnexoAudienciaPublicaForm(forms.ModelForm):
row1, row2))
super(AnexoAudienciaPublicaForm, self).__init__(
*args, **kwargs)
def clean(self):
super(AnexoAudienciaPublicaForm, self).clean()
if not self.is_valid():
return self.cleaned_data
arquivo = self.cleaned_data.get('arquivo', False)
if arquivo and arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (arquivo.size/1024)/1024))
return self.cleaned_data

57
sapl/audiencia/tests/test_audiencia.py

@ -1,11 +1,62 @@
import pytest
from django.utils.translation import ugettext as _
import datetime
from model_mommy import mommy
from django.utils.translation import ugettext as _
from sapl.audiencia import forms
from sapl.audiencia.models import TipoAudienciaPublica
from sapl.audiencia.models import AnexoAudienciaPublica
from sapl.audiencia.models import TipoAudienciaPublica, AudienciaPublica
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
@pytest.mark.django_db(transaction=False)
def test_tipo_audiencia_publica_model():
mommy.make(TipoAudienciaPublica,
nome='Teste_Nome_Tipo_Audiencia_Publica',
tipo='A')
tipo_audiencia_publica = TipoAudienciaPublica.objects.first()
assert tipo_audiencia_publica.nome == 'Teste_Nome_Tipo_Audiencia_Publica'
assert tipo_audiencia_publica.tipo == 'A'
@pytest.mark.django_db(transaction=False)
def test_audiencia_publica_model():
mommy.make(AudienciaPublica,
numero=1,
nome='Teste_Nome_Audiencia_Publica',
tema='Teste_Tema_Audiencia_Publica',
data='2016-03-21',
hora_inicio='16:03')
audiencia_publica = AudienciaPublica.objects.first()
data = '2016-03-21'
teste_data = datetime.datetime.strptime(data, "%Y-%m-%d").date()
assert audiencia_publica.numero == 1
assert audiencia_publica.nome == 'Teste_Nome_Audiencia_Publica'
assert audiencia_publica.tema == 'Teste_Tema_Audiencia_Publica'
assert audiencia_publica.data == teste_data
assert audiencia_publica.hora_inicio == '16:03'
@pytest.mark.django_db(transaction=False)
def test_anexo_audiencia_publica_model():
audiencia = mommy.make(AudienciaPublica,
numero=2,
nome='Nome_Audiencia_Publica',
tema='Tema_Audiencia_Publica',
data='2017-04-22',
hora_inicio='17:04')
mommy.make(AnexoAudienciaPublica,
audiencia=audiencia)
anexo_audiencia_publica = AnexoAudienciaPublica.objects.first()
assert anexo_audiencia_publica.audiencia == audiencia
@pytest.mark.django_db(transaction=False)
def test_valida_campos_obrigatorios_audiencia_form():
form = forms.AudienciaForm(data={})
@ -37,5 +88,3 @@ def test_audiencia_form_hora_invalida():
'hora_fim': '9:00',
})
assert not form.is_valid()

72
sapl/base/forms.py

@ -2,7 +2,6 @@ import logging
import os
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row
from django import forms
from django.conf import settings
@ -26,6 +25,7 @@ from sapl.comissoes.models import Reuniao, Comissao
from sapl.comissoes.models import Reuniao, Comissao
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
to_row)
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.materia.models import (
MateriaLegislativa, UnidadeTramitacao, StatusTramitacao)
from sapl.norma.models import (NormaJuridica, NormaEstatisticas)
@ -39,6 +39,7 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
models_with_gr_for_model, qs_override_django_filter,
choice_anos_com_normas, choice_anos_com_materias,
FilterOverridesMetaMixin, FileFieldCheckMixin)
from .models import AppConfig, CasaLegislativa
@ -65,11 +66,6 @@ def get_roles():
class UsuarioCreateForm(ModelForm):
logger = logging.getLogger(__name__)
username = forms.CharField(
required=True,
label="Nome de usuário",
max_length=30
)
firstname = forms.CharField(
required=True,
label="Nome",
@ -108,8 +104,11 @@ class UsuarioCreateForm(ModelForm):
class Meta:
model = get_user_model()
fields = ['username', 'firstname', 'lastname', 'email',
'password1', 'password2', 'user_active', 'roles']
fields = [
get_user_model().USERNAME_FIELD, 'firstname', 'lastname',
'password1', 'password2', 'user_active', 'roles'
] + (['email']
if get_user_model().USERNAME_FIELD != 'email' else [])
def clean(self):
super().clean()
@ -190,24 +189,30 @@ class UsuarioEditForm(ModelForm):
class Meta:
model = get_user_model()
fields = ['email', 'password1', 'password2', 'user_active', 'roles']
fields = [
get_user_model().USERNAME_FIELD, 'password1',
'password2', 'user_active', 'roles'
] + (['email']
if get_user_model().USERNAME_FIELD != 'email' else [])
def __init__(self, *args, **kwargs):
super(UsuarioEditForm, self).__init__(*args, **kwargs)
row1 = to_row([('email', 6),
row1 = to_row([('username', 12)])
row2 = to_row([('email', 6),
('user_active', 6)])
row2 = to_row(
row3 = to_row(
[('password1', 6),
('password2', 6)])
row3 = to_row([(form_actions(label='Salvar Alterações'), 6)])
row4 = to_row([(form_actions(label='Salvar Alterações'), 6)])
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row1,
row2,
row3,
'roles',
form_actions(label='Salvar Alterações'))
@ -867,22 +872,36 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
class Meta(FilterOverridesMetaMixin):
model = SessaoPlenaria
fields = ['data_inicio']
fields = ['data_inicio',
'sessao_legislativa',
'tipo',
'legislatura']
def __init__(self, *args, **kwargs):
super(RelatorioPresencaSessaoFilterSet, self).__init__(
*args, **kwargs)
self.form.fields['exibir_ordem_dia'] = forms.BooleanField(required=False,
label='Exibir presença das Ordens do Dia')
self.form.initial['exibir_ordem_dia'] = True
self.filters['data_inicio'].label = 'Período (Inicial - Final)'
self.form.fields['data_inicio'].required = True
tipo_sessao_ordinaria = self.filters['tipo'].queryset.filter(nome='Ordinária')
if tipo_sessao_ordinaria:
self.form.initial['tipo'] = tipo_sessao_ordinaria.first()
row1 = to_row([('data_inicio', 12)])
row2 = to_row([('legislatura', 4),
('sessao_legislativa', 4),
('tipo', 4)])
row3 = to_row([('exibir_ordem_dia', 12)])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Presença dos parlamentares nas sessões plenárias'),
row1, form_actions(label='Pesquisar'))
row1, row2, row3, form_actions(label='Pesquisar'))
)
@property
@ -908,8 +927,10 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['tramitacao__status'].label = _('Status')
self.filters['tramitacao__unidade_tramitacao_local'].label = _('Unidade Local (Origem)')
self.filters['tramitacao__unidade_tramitacao_destino'].label = _('Unidade Destino')
self.filters['tramitacao__unidade_tramitacao_local'].label = _(
'Unidade Local (Origem)')
self.filters['tramitacao__unidade_tramitacao_destino'].label = _(
'Unidade Destino')
row1 = to_row([('tramitacao__data_tramitacao', 12)])
row2 = to_row([('tramitacao__unidade_tramitacao_local', 6),
@ -927,7 +948,6 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
)
class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
@property
@ -946,7 +966,8 @@ class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['tramitacao__unidade_tramitacao_local'].label = 'Unidade Local (Origem)'
self.filters[
'tramitacao__unidade_tramitacao_local'].label = 'Unidade Local (Origem)'
self.filters['tramitacao__unidade_tramitacao_destino'].label = 'Unidade Destino'
self.filters['tramitacao__status'].label = 'Status de tramitação'
@ -960,7 +981,7 @@ class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Tramitações por fim de prazo'),
Fieldset(_('Tramitações'),
row1, row2, row3,
form_actions(label='Pesquisar'))
)
@ -1409,7 +1430,7 @@ class PartidoForm(FileFieldCheckMixin, ModelForm):
[('sigla', 2),
('nome', 6),
('data_criacao', 2),
('data_extincao', 2),])
('data_extincao', 2), ])
row2 = to_row([('observacao', 12)])
row3 = to_row([('logo_partido', 12)])
@ -1427,7 +1448,8 @@ class PartidoForm(FileFieldCheckMixin, ModelForm):
if cleaned_data['data_criacao'] and cleaned_data['data_extincao']:
if cleaned_data['data_criacao'] > cleaned_data['data_extincao']:
raise ValidationError("Certifique-se de que a data de criação seja anterior à data de extinção.")
raise ValidationError(
"Certifique-se de que a data de criação seja anterior à data de extinção.")
return cleaned_data
@ -1452,8 +1474,10 @@ class RelatorioHistoricoTramitacaoAdmFilterSet(django_filters.FilterSet):
self.filters['tipo'].label = 'Tipo de Documento'
self.filters['tramitacaoadministrativo__status'].label = _('Status')
self.filters['tramitacaoadministrativo__unidade_tramitacao_local'].label = _('Unidade Local (Origem)')
self.filters['tramitacaoadministrativo__unidade_tramitacao_destino'].label = _('Unidade Destino')
self.filters['tramitacaoadministrativo__unidade_tramitacao_local'].label = _(
'Unidade Local (Origem)')
self.filters['tramitacaoadministrativo__unidade_tramitacao_destino'].label = _(
'Unidade Destino')
row1 = to_row([('tramitacaoadministrativo__data_tramitacao', 12)])
row2 = to_row([('tramitacaoadministrativo__unidade_tramitacao_local', 6),

42
sapl/base/templatetags/common_tags.py

@ -223,8 +223,34 @@ def audio_url(value):
@register.filter
def video_url(value):
return True if url(value) and value.endswith("mp4") else False
def is_video_url(value):
video_extensions = ["mp4", "ogg", "webm", "3gp", "ogv"]
has_ext = any([value.endswith(i) for i in video_extensions])
return url(value) and has_ext
@register.filter
def youtube_url(value):
# Test if YouTube video
# tested on https://pythex.org/
value = value.lower()
youtube_pattern = "^((https?://)?(www\.)?youtube\.com\/watch\?v=)"
r = re.findall(youtube_pattern, value)
return True if r else False
@register.filter
def facebook_url(value):
value = value.lower()
facebook_pattern = "^((https?://)?((www|pt-br)\.)?facebook\.com(\/.+)?\/videos(\/.*)?)"
r = re.findall(facebook_pattern, value)
return True if r else False
@register.filter
def youtube_id(value):
from urllib.parse import urlparse, parse_qs
u_pars = urlparse(value)
quer_v = parse_qs(u_pars.query).get('v')[0]
return quer_v
@register.filter
@ -272,12 +298,20 @@ def urldetail(obj):
@register.filter
def filiacao_data_filter(parlamentar, data_inicio):
return filiacao_data(parlamentar, data_inicio)
try:
filiacao = filiacao_data(parlamentar, data_inicio)
except Exception:
filiacao = ''
return filiacao
@register.filter
def filiacao_intervalo_filter(parlamentar, date_range):
return filiacao_data(parlamentar, date_range[0], date_range[1])
try:
filiacao = filiacao_data(parlamentar, date_range[0], date_range[1])
except Exception:
filiacao = ''
return filiacao
@register.simple_tag

24
sapl/base/tests/test_base.py

@ -0,0 +1,24 @@
import pytest
from model_mommy import mommy
from sapl.base.models import CasaLegislativa
@pytest.mark.django_db(transaction=False)
def test_casa_legislativa_model():
mommy.make(CasaLegislativa,
nome='Teste_Nome_Casa_Legislativa',
sigla='TSCL',
endereco='Teste_Endereço_Casa_Legislativa',
cep='12345678',
municipio='Teste_Municipio_Casa_Legislativa',
uf='DF')
casa_legislativa = CasaLegislativa.objects.first()
assert casa_legislativa.nome == 'Teste_Nome_Casa_Legislativa'
assert casa_legislativa.sigla == 'TSCL'
assert casa_legislativa.endereco == 'Teste_Endereço_Casa_Legislativa'
assert casa_legislativa.cep == '12345678'
assert casa_legislativa.municipio == 'Teste_Municipio_Casa_Legislativa'
assert casa_legislativa.uf == 'DF'

629
sapl/base/tests/test_view_base.py

@ -1,6 +1,635 @@
import pytest
from model_mommy import mommy
import datetime
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from model_mommy import mommy
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Comissao, TipoComissao
from sapl.sessao.models import Bancada
from sapl.protocoloadm.models import (Protocolo, DocumentoAdministrativo,
TipoDocumentoAdministrativo, Anexado)
from sapl.materia.models import (TipoMateriaLegislativa, RegimeTramitacao,
MateriaLegislativa, Anexada)
from sapl.parlamentares.models import (Parlamentar, Partido, Filiacao,
Legislatura, Mandato)
from sapl.base.views import (protocolos_duplicados, protocolos_com_materias,
materias_protocolo_inexistente,
mandato_sem_data_inicio, parlamentares_duplicados,
parlamentares_mandatos_intersecao,
parlamentares_filiacoes_intersecao,
autores_duplicados,
bancada_comissao_autor_externo, anexados_ciclicos)
@pytest.mark.django_db(transaction=False)
def test_lista_protocolos_duplicados():
mommy.make(
Protocolo,
numero=15,
ano=2031
)
mommy.make(
Protocolo,
numero=15,
ano=2031
)
mommy.make(
Protocolo,
numero=33,
ano=2033
)
lista_protocolos_duplicados = protocolos_duplicados()
assert len(lista_protocolos_duplicados) == 1
assert lista_protocolos_duplicados[0][1] == 2
assert lista_protocolos_duplicados[0][0].numero == 15
assert lista_protocolos_duplicados[0][0].ano == 2031
@pytest.mark.django_db(transaction=False)
def test_lista_protocolos_com_materias():
mommy.make(
Protocolo,
numero=15,
ano=2035
)
mommy.make(
Protocolo,
numero=33,
ano=2035
)
tipo_materia = mommy.make(
TipoMateriaLegislativa,
descricao="Tipo_Materia_Teste"
)
regime_tramitacao = mommy.make(
RegimeTramitacao,
descricao="Regime_Tramitacao_Teste"
)
mommy.make(
MateriaLegislativa,
numero=16,
ano=2035,
data_apresentacao='2035-06-02',
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia,
numero_protocolo=15
)
mommy.make(
MateriaLegislativa,
numero=17,
ano=2035,
data_apresentacao='2035-06-05',
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia,
numero_protocolo=15
)
lista_protocolos_com_materias = protocolos_com_materias()
assert len(lista_protocolos_com_materias) == 1
assert lista_protocolos_com_materias[0][1] == 2
assert lista_protocolos_com_materias[0][0].numero_protocolo == 15
assert lista_protocolos_com_materias[0][0].ano == 2035
@pytest.mark.django_db(transaction=False)
def test_lista_materias_protocolo_inexistente():
protocolo_a = mommy.make(
Protocolo,
numero=15,
ano=2031
)
tipo_materia = mommy.make(
TipoMateriaLegislativa,
descricao="Tipo_Materia_Teste"
)
regime_tramitacao = mommy.make(
RegimeTramitacao,
descricao="Regime_Tramitacao_Teste"
)
mommy.make(
MateriaLegislativa,
numero=16,
ano=2031,
data_apresentacao='2031-06-02',
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia,
numero_protocolo=15
)
materia = mommy.make(
MateriaLegislativa,
numero=17,
ano=2031,
data_apresentacao='2031-06-02',
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia,
numero_protocolo=16
)
lista_materias_protocolo_inexistente = materias_protocolo_inexistente()
assert len(lista_materias_protocolo_inexistente) == 1
assert lista_materias_protocolo_inexistente == [(materia, 2031, 16)]
@pytest.mark.django_db(transaction=False)
def test_lista_mandatos_sem_data_inicio():
parlamentar = mommy.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste",
nome_parlamentar="Nome_Parlamentar_Teste",
sexo='M'
)
legislatura = mommy.make(
Legislatura,
numero=1,
data_inicio='2015-05-02',
data_fim='2024-02-04',
data_eleicao='2015-02-05'
)
mandato_a = mommy.make(
Mandato,
parlamentar=parlamentar,
legislatura=legislatura
)
mommy.make(
Mandato,
parlamentar=parlamentar,
legislatura=legislatura,
data_inicio_mandato='2015-05-27'
)
lista_mandatos_sem_data_inicio = mandato_sem_data_inicio()
assert len(lista_mandatos_sem_data_inicio) == 1
assert lista_mandatos_sem_data_inicio[0] == mandato_a
@pytest.mark.django_db(transaction=False)
def test_lista_parlamentares_duplicados():
mommy.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste",
nome_parlamentar="Nome_Parlamentar_Teste",
sexo='M'
)
mommy.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste",
nome_parlamentar="Nome_Parlamentar_Teste",
sexo='M'
)
mommy.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste-1",
nome_parlamentar="Nome_Parlamentar_Teste-1",
sexo='M'
)
lista_dict_values_parlamentares_duplicados = parlamentares_duplicados()
parlamentar_duplicado = list(
lista_dict_values_parlamentares_duplicados[0]
)
parlamentar_duplicado.sort(key=str)
assert len(lista_dict_values_parlamentares_duplicados) == 1
assert parlamentar_duplicado == [2, "Nome_Parlamentar_Teste"]
@pytest.mark.django_db(transaction=False)
def test_lista_parlamentares_mandatos_intersecao():
legislatura = mommy.make(
Legislatura,
numero=1,
data_inicio='2017-07-04',
data_fim='2170-05-01',
data_eleicao='2017-04-07'
)
parlamentar_a = mommy.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste",
nome_parlamentar="Nome_Parlamentar_Teste",
sexo='M'
)
parlamentar_b = mommy.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste-1",
nome_parlamentar="Nome_Parlamentar_Teste-1",
sexo='M'
)
mandato_a = mommy.make(
Mandato,
parlamentar=parlamentar_a,
legislatura=legislatura,
data_inicio_mandato='2017-07-08',
data_fim_mandato='2018-01-07'
)
mandato_b = mommy.make(
Mandato,
parlamentar=parlamentar_a,
legislatura=legislatura,
data_inicio_mandato='2017-07-09'
)
mommy.make(
Mandato,
parlamentar=parlamentar_b,
legislatura=legislatura,
data_inicio_mandato='2017-11-17',
data_fim_mandato='2018-08-02'
)
mommy.make(
Mandato,
parlamentar=parlamentar_b,
legislatura=legislatura,
data_inicio_mandato='2018-08-03'
)
lista_parlamentares = parlamentares_mandatos_intersecao()
assert len(lista_parlamentares) == 1
assert lista_parlamentares == [(parlamentar_a, mandato_a, mandato_b)]
@pytest.mark.django_db(transaction=False)
def test_lista_parlamentares_filiacoes_intersecao():
partido = mommy.make(
Partido,
sigla="ST",
nome="Nome_Partido_Teste"
)
parlamentar_a = mommy.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste",
nome_parlamentar="Nome_Parlamentar_Teste",
sexo='M'
)
parlamentar_b = mommy.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste-1",
nome_parlamentar="Nome_Parlamentar_Teste-1",
sexo='M'
)
filiacao_a = mommy.make(
Filiacao,
parlamentar=parlamentar_a,
partido=partido,
data='2018-02-02',
data_desfiliacao='2019-08-01'
)
filiacao_b = mommy.make(
Filiacao,
parlamentar=parlamentar_a,
partido=partido,
data='2018-02-23',
data_desfiliacao='2020-02-04'
)
mommy.make(
Filiacao,
parlamentar=parlamentar_b,
partido=partido,
data='2018-02-07',
data_desfiliacao='2018-02-27'
)
mommy.make(
Filiacao,
parlamentar=parlamentar_b,
partido=partido,
data='2018-02-28'
)
lista_parlamentares = parlamentares_filiacoes_intersecao()
assert len(lista_parlamentares) == 1
assert lista_parlamentares == [(parlamentar_a, filiacao_b, filiacao_a)]
@pytest.mark.django_db(transaction=False)
def test_lista_autores_duplicados():
tipo_autor = mommy.make(
TipoAutor,
descricao="Tipo_Autor_Teste"
)
mommy.make(
Autor,
tipo=tipo_autor,
nome="Nome_Autor_Teste"
)
mommy.make(
Autor,
tipo=tipo_autor,
nome="Nome_Autor_Teste"
)
mommy.make(
Autor,
tipo=tipo_autor,
nome="Nome_Autor_Teste-1"
)
lista_autores_duplicados = autores_duplicados()
assert len(lista_autores_duplicados) == 1
assert lista_autores_duplicados[0]['count'] == 2
assert lista_autores_duplicados[0]['nome'] == "Nome_Autor_Teste"
@pytest.mark.django_db(transaction=False)
def test_lista_bancada_comissao_autor_externo():
tipo_autor = mommy.make(
TipoAutor,
descricao="Tipo_Autor_Teste"
)
tipo_autor_externo = mommy.make(
TipoAutor,
descricao="Externo"
)
legislatura = mommy.make(
Legislatura,
numero=1,
data_inicio='2012-01-03',
data_fim='2013-01-02',
data_eleicao='2011-10-04'
)
bancada_a = mommy.make(
Bancada,
legislatura=legislatura,
nome="Bancada_Teste",
data_criacao='2012-01-08',
)
bancada_a.autor.create(
nome="Nome_Autor_Teste",
tipo=tipo_autor
)
bancada_b = mommy.make(
Bancada,
legislatura=legislatura,
nome="Bancada_Teste-1",
data_criacao='2012-02-02'
)
autor_bancada_b = bancada_b.autor.create(
nome="Nome_Autor_Externo_Teste",
tipo=tipo_autor_externo
)
tipo_comissao = mommy.make(
TipoComissao,
nome="Tipo_Comissao_Teste",
natureza='T',
sigla="TCT"
)
comissao_a = mommy.make(
Comissao,
nome="Comissao_Teste",
sigla="CT",
data_criacao='2012-03-08',
)
comissao_a.autor.create(
nome="Nome_Autor_Teste",
tipo=tipo_autor
)
comissao_b = mommy.make(
Comissao,
nome="Comissao_Teste-1",
sigla="CT1",
data_criacao='2012-04-01',
)
autor_comissao_b = comissao_b.autor.create(
nome="Nome_Autor_Externo_Teste",
tipo=tipo_autor_externo
)
lista_bancada_comissao = bancada_comissao_autor_externo()
assert len(lista_bancada_comissao) == 2
assert lista_bancada_comissao[0][0:2] == (autor_bancada_b, bancada_b)
assert lista_bancada_comissao[0][2:4] == ('Bancada', 'sistema/bancada')
assert lista_bancada_comissao[1][0:2] == (autor_comissao_b, comissao_b)
assert lista_bancada_comissao[1][2:4] == ('Comissão', 'comissao')
@pytest.mark.django_db(transaction=False)
def test_lista_anexados_ciclicas():
## DocumentoAdministrativo
tipo_documento = mommy.make(
TipoDocumentoAdministrativo,
sigla="TT",
descricao="Tipo_Teste"
)
documento_a = mommy.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=26,
ano=2019,
data='2019-05-15',
)
documento_b = mommy.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=27,
ano=2019,
data='2019-05-16',
)
documento_c = mommy.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=28,
ano=2019,
data='2019-05-17',
)
documento_a1 = mommy.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=29,
ano=2019,
data='2019-05-18',
)
documento_b1 = mommy.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=30,
ano=2019,
data='2019-05-19',
)
documento_c1 = mommy.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=31,
ano=2019,
data='2019-05-20',
)
mommy.make(
Anexado,
documento_principal=documento_a,
documento_anexado=documento_b,
data_anexacao='2019-05-21'
)
mommy.make(
Anexado,
documento_principal=documento_a,
documento_anexado=documento_c,
data_anexacao='2019-05-22'
)
mommy.make(
Anexado,
documento_principal=documento_b,
documento_anexado=documento_c,
data_anexacao='2019-05-23'
)
mommy.make(
Anexado,
documento_principal=documento_a1,
documento_anexado=documento_b1,
data_anexacao='2019-05-24'
)
mommy.make(
Anexado,
documento_principal=documento_a1,
documento_anexado=documento_c1,
data_anexacao='2019-05-25'
)
mommy.make(
Anexado,
documento_principal=documento_b1,
documento_anexado=documento_c1,
data_anexacao='2019-05-26'
)
mommy.make(
Anexado,
documento_principal=documento_c1,
documento_anexado=documento_b1,
data_anexacao='2019-05-27'
)
lista_documento_ciclicos = anexados_ciclicos(False)
## Matéria
tipo_materia = mommy.make(
TipoMateriaLegislativa,
descricao="Tipo_Teste"
)
regime_tramitacao = mommy.make(
RegimeTramitacao,
descricao="Regime_Teste"
)
materia_a = mommy.make(
MateriaLegislativa,
numero=20,
ano=2018,
data_apresentacao="2018-01-04",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
)
materia_b = mommy.make(
MateriaLegislativa,
numero=21,
ano=2019,
data_apresentacao="2019-05-04",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
)
materia_c = mommy.make(
MateriaLegislativa,
numero=22,
ano=2019,
data_apresentacao="2019-05-05",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
)
materia_a1 = mommy.make(
MateriaLegislativa,
numero=23,
ano=2018,
data_apresentacao="2019-05-06",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
)
materia_b1 = mommy.make(
MateriaLegislativa,
numero=24,
ano=2019,
data_apresentacao="2019-05-07",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
)
materia_c1 = mommy.make(
MateriaLegislativa,
numero=25,
ano=2019,
data_apresentacao="2019-05-08",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
)
mommy.make(
Anexada,
materia_principal=materia_a,
materia_anexada=materia_b,
data_anexacao='2019-05-11'
)
mommy.make(
Anexada,
materia_principal=materia_a,
materia_anexada=materia_c,
data_anexacao='2019-05-12'
)
mommy.make(
Anexada,
materia_principal=materia_b,
materia_anexada=materia_c,
data_anexacao='2019-05-13'
)
mommy.make(
Anexada,
materia_principal=materia_a1,
materia_anexada=materia_b1,
data_anexacao='2019-05-11'
)
mommy.make(
Anexada,
materia_principal=materia_a1,
materia_anexada=materia_c1,
data_anexacao='2019-05-12'
)
mommy.make(
Anexada,
materia_principal=materia_b1,
materia_anexada=materia_c1,
data_anexacao='2019-05-13'
)
mommy.make(
Anexada,
materia_principal=materia_c1,
materia_anexada=materia_b1,
data_anexacao='2019-05-14'
)
lista_materias_ciclicas = anexados_ciclicos(True)
assert len(lista_materias_ciclicas) == 2
assert lista_materias_ciclicas[0] == (datetime.date(2019,5,13), materia_b1, materia_c1)
assert lista_materias_ciclicas[1] == (datetime.date(2019,5,14), materia_c1, materia_b1)
assert len(lista_documento_ciclicos) == 2
assert lista_documento_ciclicos[0] == (datetime.date(2019,5,26), documento_b1, documento_c1)
assert lista_documento_ciclicos[1] == (datetime.date(2019,5,27), documento_c1, documento_b1)
@pytest.mark.django_db(transaction=False)

23
sapl/base/urls.py

@ -29,17 +29,13 @@ from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud,
EstatisticasAcessoNormas,
RelatoriosListView,
ListarInconsistenciasView, ListarProtocolosDuplicadosView,
ListarProtocolosComMateriasView,
ListarMatProtocoloInexistenteView,
ListarProtocolosComMateriasView, ListarMatProtocoloInexistenteView,
ListarParlamentaresDuplicadosView,
ListarFiliacoesSemDataFiliacaoView,
ListarMandatoSemDataInicioView,
ListarParlMandatosIntersecaoView,
ListarParlFiliacoesIntersecaoView,
ListarAutoresDuplicadosView,
ListarBancadaComissaoAutorExternoView,
ListarLegislaturaInfindavelView,
pesquisa_textual,
ListarFiliacoesSemDataFiliacaoView, ListarMandatoSemDataInicioView,
ListarParlMandatosIntersecaoView, ListarParlFiliacoesIntersecaoView,
ListarAutoresDuplicadosView, ListarBancadaComissaoAutorExternoView,
ListarLegislaturaInfindavelView, ListarAnexadasCiclicasView,
ListarAnexadosCiclicosView, pesquisa_textual,
RelatorioHistoricoTramitacaoAdmView)
@ -180,6 +176,13 @@ urlpatterns = [
url(r'^sistema/inconsistencias/legislatura_infindavel$',
ListarLegislaturaInfindavelView.as_view(),
name='lista_legislatura_infindavel'),
url(r'sistema/inconsistencias/anexadas_ciclicas$',
ListarAnexadasCiclicasView.as_view(),
name='lista_anexadas_ciclicas'),
url(r'sistema/inconsistencias/anexados_ciclicos$',
ListarAnexadosCiclicosView.as_view(),
name='lista_anexados_ciclicos'),
url(r'^sistema/pesquisa-textual',
pesquisa_textual,
name='pesquisa_textual'),

199
sapl/base/views.py

@ -35,15 +35,15 @@ from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Reuniao, Comissao
from sapl.crud.base import CrudAux, make_pagination
from sapl.materia.models import (Autoria, MateriaLegislativa, Proposicao,
from sapl.materia.models import (Autoria, MateriaLegislativa, Proposicao, Anexada,
TipoMateriaLegislativa, StatusTramitacao, UnidadeTramitacao)
from sapl.norma.models import (NormaJuridica, NormaEstatisticas)
from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato, Filiacao
from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato, Filiacao, SessaoLegislativa
from sapl.protocoloadm.models import (Protocolo, TipoDocumentoAdministrativo,
StatusTramitacaoAdministrativo,
DocumentoAdministrativo)
DocumentoAdministrativo, Anexado)
from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, Bancada)
SessaoPlenariaPresenca, Bancada, TipoSessaoPlenaria)
from sapl.utils import (parlamentares_ativos, gerar_hash_arquivo, SEPARADOR_HASH_PROPOSICAO,
show_results_filter_set, mail_service_configured,
intervalos_tem_intersecao, remover_acentos)
@ -343,12 +343,59 @@ class RelatorioPresencaSessaoView(FilterView):
if not self.filterset.form.is_valid():
return context
# if 'salvar' not in self.request.GET:
cd = self.filterset.form.cleaned_data
if not cd['data_inicio'] and not cd['sessao_legislativa'] \
and not cd['legislatura']:
msg = _("Formulário inválido! Preencha pelo menos algum dos campos Período, Legislatura ou Sessão Legislativa.")
messages.error(self.request, msg)
return context
# Caso a data tenha sido preenchida, verifica se foi preenchida corretamente
if self.request.GET.get('data_inicio_0') and not self.request.GET.get('data_inicio_1'):
msg = _("Formulário inválido! Preencha a data do Período Final.")
messages.error(self.request, msg)
return context
if not self.request.GET.get('data_inicio_0') and self.request.GET.get('data_inicio_1'):
msg = _("Formulário inválido! Preencha a data do Período Inicial.")
messages.error(self.request, msg)
return context
param0 = {}
legislatura_pk = self.request.GET.get('legislatura')
if legislatura_pk:
param0['sessao_plenaria__legislatura_id'] = legislatura_pk
legislatura = Legislatura.objects.get(id=legislatura_pk)
context['legislatura'] = legislatura
sessao_legislativa_pk = self.request.GET.get('sessao_legislativa')
if sessao_legislativa_pk:
param0['sessao_plenaria__sessao_legislativa_id'] = sessao_legislativa_pk
sessao_legislativa = SessaoLegislativa.objects.get(id=sessao_legislativa_pk)
context['sessao_legislativa'] = sessao_legislativa
tipo_sessao_plenaria_pk = self.request.GET.get('tipo')
context['tipo'] = ''
if tipo_sessao_plenaria_pk:
param0['sessao_plenaria__tipo_id'] = tipo_sessao_plenaria_pk
context['tipo'] = TipoSessaoPlenaria.objects.get(id=tipo_sessao_plenaria_pk)
_range = []
if ('data_inicio_0' in self.request.GET) and self.request.GET['data_inicio_0'] and \
('data_inicio_1' in self.request.GET) and self.request.GET['data_inicio_1']:
where = context['object_list'].query.where
_range = where.children[0].rhs
sufixo = 'sessao_plenaria__data_inicio__range'
param0 = {'%s' % sufixo: _range}
elif legislatura_pk and not sessao_legislativa_pk:
_range = [legislatura.data_inicio, legislatura.data_fim]
elif sessao_legislativa_pk:
_range = [sessao_legislativa.data_inicio, sessao_legislativa.data_fim]
param0.update({'sessao_plenaria__data_inicio__range': _range})
# Parlamentares com Mandato no intervalo de tempo (Ativos)
parlamentares_qs = parlamentares_ativos(
@ -357,16 +404,12 @@ class RelatorioPresencaSessaoView(FilterView):
'id', flat=True)
# Presenças de cada Parlamentar em Sessões
presenca_sessao = SessaoPlenariaPresenca.objects.filter(
parlamentar_id__in=parlamentares_id,
sessao_plenaria__data_inicio__range=_range).values_list(
presenca_sessao = SessaoPlenariaPresenca.objects.filter(**param0).values_list(
'parlamentar_id').annotate(
sessao_count=Count('id'))
# Presenças de cada Ordem do Dia
presenca_ordem = PresencaOrdemDia.objects.filter(
parlamentar_id__in=parlamentares_id,
sessao_plenaria__data_inicio__range=_range).values_list(
presenca_ordem = PresencaOrdemDia.objects.filter(**param0).values_list(
'parlamentar_id').annotate(
sessao_count=Count('id'))
@ -433,6 +476,14 @@ class RelatorioPresencaSessaoView(FilterView):
context['periodo'] = (
self.request.GET['data_inicio_0'] +
' - ' + self.request.GET['data_inicio_1'])
context['sessao_legislativa'] = ''
context['legislatura'] = ''
context['exibir_ordem'] = self.request.GET.get('exibir_ordem_dia') == 'on'
if sessao_legislativa_pk:
context['sessao_legislativa'] = SessaoLegislativa.objects.get(id=sessao_legislativa_pk)
if legislatura_pk:
context['legislatura'] = Legislatura.objects.get(id=legislatura_pk)
# =====================================================================
qr = self.request.GET.copy()
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
@ -498,7 +549,7 @@ class RelatorioDataFimPrazoTramitacaoView(FilterView):
def get_context_data(self, **kwargs):
context = super(RelatorioDataFimPrazoTramitacaoView,
self).get_context_data(**kwargs)
context['title'] = _('Fim de Prazo de Tramitações')
context['title'] = _('Relatório de Tramitações')
if not self.filterset.form.is_valid():
return context
qr = self.request.GET.copy()
@ -1016,10 +1067,128 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
len(legislatura_infindavel())
)
)
tabela.append(
('anexadas_ciclicas',
'Matérias Anexadas cíclicas',
len(anexados_ciclicos(True))
)
)
tabela.append(
('anexados_ciclicos',
'Documentos Anexados cíclicos',
len(anexados_ciclicos(False))
)
)
return tabela
def anexados_ciclicos(ofMateriaLegislativa):
ciclicos = []
if ofMateriaLegislativa:
principais = Anexada.objects.values(
'materia_principal'
).annotate(
count=Count('materia_principal')
).filter(count__gt=0).order_by('-data_anexacao')
else:
principais = Anexado.objects.values(
'documento_principal'
).annotate(
count=Count('documento_principal')
).filter(count__gt=0).order_by('-data_anexacao')
for principal in principais:
anexados_total = []
if ofMateriaLegislativa:
anexados = Anexada.objects.filter(
materia_principal=principal['materia_principal']
).order_by('-data_anexacao')
else:
anexados = Anexado.objects.filter(
documento_principal=principal['documento_principal']
).order_by('-data_anexacao')
anexados_temp = list(anexados)
while anexados_temp:
anexado = anexados_temp.pop()
if ofMateriaLegislativa:
if anexado.materia_anexada not in anexados_total:
if not principal['materia_principal'] == anexado.materia_anexada.pk:
anexados_total.append(anexado.materia_anexada)
anexados_anexado = Anexada.objects.filter(
materia_principal=anexado.materia_anexada
)
anexados_temp.extend(anexados_anexado)
else:
ciclicos.append((anexado.data_anexacao, anexado.materia_principal, anexado.materia_anexada))
else:
if anexado.documento_anexado not in anexados_total:
if not principal['documento_principal'] == anexado.documento_anexado.pk:
anexados_total.append(anexado.documento_anexado)
anexados_anexado = Anexado.objects.filter(
documento_principal=anexado.documento_anexado
)
anexados_temp.extend(anexados_anexado)
else:
ciclicos.append((anexado.data_anexacao, anexado.documento_principal, anexado.documento_anexado))
return ciclicos
class ListarAnexadosCiclicosView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/anexados_ciclicos.html'
context_object_name = 'anexados_ciclicos'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return anexados_ciclicos(False)
def get_context_data(self, **kwargs):
context = super(
ListarAnexadosCiclicosView, self
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages
)
context['NO_ENTRIES_MSG'] = 'Nenhum encontrado.'
return context
class ListarAnexadasCiclicasView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/anexadas_ciclicas.html'
context_object_name = 'anexadas_ciclicas'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return anexados_ciclicos(True)
def get_context_data(self, **kwargs):
context = super(
ListarAnexadasCiclicasView, self
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages
)
context['NO_ENTRIES_MSG'] = 'Nenhuma encontrada.'
return context
def legislatura_infindavel():
return Legislatura.objects.filter(data_fim__isnull=True).order_by('-numero')

46
sapl/comissoes/forms.py

@ -1,6 +1,7 @@
import logging
from django import forms
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import transaction
@ -381,6 +382,23 @@ class ReuniaoForm(ModelForm):
self.logger.error("A hora de término da reunião ({}) não pode ser menor que a de início ({})."
.format(self.cleaned_data['hora_fim'], self.cleaned_data['hora_inicio']))
raise ValidationError(msg)
upload_pauta = self.cleaned_data.get('upload_pauta', False)
upload_ata = self.cleaned_data.get('upload_ata', False)
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_pauta and upload_pauta.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Pauta da Reunião deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_pauta.size/1024)/1024))
if upload_ata and upload_ata.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Ata da Reunião deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_ata.size/1024)/1024))
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo da Reunião deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
return self.cleaned_data
@ -412,6 +430,20 @@ class DocumentoAcessorioCreateForm(FileFieldCheckMixin, forms.ModelForm):
def create_documentoacessorio(self):
reuniao = Reuniao.objects.get(id=self.initial['parent_pk'])
def clean(self):
super(DocumentoAcessorioCreateForm, self).clean()
if not self.is_valid():
return self.cleaned_data
arquivo = self.cleaned_data.get('arquivo', False)
if arquivo and arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Integral deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (arquivo.size/1024)/1024))
return self.cleaned_data
class DocumentoAcessorioEditForm(FileFieldCheckMixin, forms.ModelForm):
@ -424,3 +456,17 @@ class DocumentoAcessorioEditForm(FileFieldCheckMixin, forms.ModelForm):
def __init__(self, user=None, **kwargs):
super(DocumentoAcessorioEditForm, self).__init__(**kwargs)
def clean(self):
super(DocumentoAcessorioEditForm, self).clean()
if not self.is_valid():
return self.cleaned_data
arquivo = self.cleaned_data.get('arquivo', False)
if arquivo and arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Integral deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (arquivo.size/1024)/1024))
return self.cleaned_data

18
sapl/comissoes/tests/test_comissoes.py

@ -3,7 +3,8 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from model_mommy import mommy
from sapl.comissoes.models import Comissao, Composicao, Periodo, TipoComissao, Reuniao
from sapl.comissoes.models import Comissao, Composicao, Periodo
from sapl.comissoes.models import TipoComissao, Reuniao
from sapl.parlamentares.models import Filiacao, Parlamentar, Partido
from sapl.comissoes import forms
@ -45,6 +46,20 @@ def make_filiacao():
return Filiacao.objects.first()
@pytest.mark.django_db(transaction=False)
def test_tipo_comissao_model():
mommy.make(TipoComissao,
nome='Teste_Nome_Tipo_Comissao',
natureza='T',
sigla='TSTC')
tipo_comissao = TipoComissao.objects.first()
assert tipo_comissao.nome == 'Teste_Nome_Tipo_Comissao'
assert tipo_comissao.natureza == 'T'
assert tipo_comissao.sigla == 'TSTC'
@pytest.mark.django_db(transaction=False)
def test_incluir_parlamentar_errors(admin_client):
comissao = make_comissao()
@ -141,4 +156,3 @@ def test_valida_campos_obrigatorios_reuniao_form():
assert errors['hora_inicio'] == [_('Este campo é obrigatório.')]
assert len(errors) == 6

112
sapl/compilacao/tests/test_compilacao.py

@ -0,0 +1,112 @@
import pytest
from model_mommy import mommy
from sapl.compilacao.models import PerfilEstruturalTextoArticulado
from sapl.compilacao.models import TipoTextoArticulado
from sapl.compilacao.models import TextoArticulado, TipoNota
from sapl.compilacao.models import TipoVide, TipoDispositivo
from sapl.compilacao.models import TipoDispositivoRelationship
@pytest.mark.django_db(transaction=False)
def test_perfil_estrutural_texto_articulado_model():
perfil_estrutural_texto_articulado = mommy.make(
PerfilEstruturalTextoArticulado,
nome='Teste_Nome_Perfil',
sigla='TSPETA')
assert perfil_estrutural_texto_articulado.nome == 'Teste_Nome_Perfil'
assert perfil_estrutural_texto_articulado.sigla == 'TSPETA'
@pytest.mark.django_db(transaction=False)
def test_tipo_texto_articulado_model():
tipo_texto_articulado = mommy.make(
TipoTextoArticulado,
sigla='TTP',
descricao='T_Desc_Tipo_Texto_Articulado'
)
assert tipo_texto_articulado.sigla == 'TTP'
assert tipo_texto_articulado.descricao == 'T_Desc_Tipo_Texto_Articulado'
@pytest.mark.django_db(transaction=False)
def test_texto_articulado_model():
texto_articulado = mommy.make(
TextoArticulado,
ementa='Teste_Ementa_Texto_Articulado',
numero='12345678',
ano=2016,
)
assert texto_articulado.ementa == 'Teste_Ementa_Texto_Articulado'
assert texto_articulado.numero == '12345678'
assert texto_articulado.ano == 2016
@pytest.mark.django_db(transaction=False)
def test_tipo_nota_model():
tipo_nota = mommy.make(
TipoNota,
sigla='TTN',
nome='Teste_Nome_Tipo_Nota'
)
assert tipo_nota.sigla == 'TTN'
assert tipo_nota.nome == 'Teste_Nome_Tipo_Nota'
@pytest.mark.django_db(transaction=False)
def test_tipo_vide_model():
tipo_vide = mommy.make(
TipoVide,
sigla='TTV',
nome='Teste_Nome_Tipo_Vide'
)
assert tipo_vide.sigla == 'TTV'
assert tipo_vide.nome == 'Teste_Nome_Tipo_Vide'
@pytest.mark.django_db(transaction=False)
def test_tipo_dispositivo_model():
tipo_dispositivo = mommy.make(
TipoDispositivo,
nome='Teste_Nome_Tipo_Dispositivo',
rotulo_ordinal=0
)
assert tipo_dispositivo.nome == 'Teste_Nome_Tipo_Dispositivo'
assert tipo_dispositivo.rotulo_ordinal == 0
@pytest.mark.django_db(transaction=False)
def test_tipo_dispositivo_relationship_model():
tipo_dispositivo_pai = mommy.make(
TipoDispositivo,
nome='Tipo_Dispositivo_Pai',
rotulo_ordinal=0
)
t_dispositivo_filho = mommy.make(
TipoDispositivo,
nome='Tipo_Dispositivo_Filho',
rotulo_ordinal=0
)
p_e_texto_articulado = mommy.make(
PerfilEstruturalTextoArticulado,
nome='Teste_Nome_Perfil',
sigla='TSPETA')
tipo_dispositivo_relationship = mommy.make(
TipoDispositivoRelationship,
pai=tipo_dispositivo_pai,
filho_permitido=t_dispositivo_filho,
perfil=p_e_texto_articulado,
)
assert tipo_dispositivo_relationship.pai == tipo_dispositivo_pai
assert tipo_dispositivo_relationship.perfil == p_e_texto_articulado
assert tipo_dispositivo_relationship.filho_permitido == t_dispositivo_filho

334
sapl/materia/forms.py

@ -3,7 +3,7 @@ import logging
import os
from crispy_forms.bootstrap import Alert, InlineRadios
from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset,
from crispy_forms.layout import (HTML, Button, Field, Fieldset,
Layout, Row)
from django import forms
from django.contrib.contenttypes.models import ContentType
@ -156,25 +156,6 @@ class MateriaSimplificadaForm(FileFieldCheckMixin, ModelForm):
)
super(MateriaSimplificadaForm, self).__init__(*args, **kwargs)
def clean(self):
super(MateriaSimplificadaForm, self).clean()
if not self.is_valid():
return self.cleaned_data
cleaned_data = self.cleaned_data
data_apresentacao = cleaned_data['data_apresentacao']
ano = cleaned_data['ano']
if data_apresentacao.year != ano:
self.logger.error("O ano da matéria ({}) é diferente"
" do ano na data de apresentação ({}).".format(ano, data_apresentacao.year))
raise ValidationError("O ano da matéria não pode ser "
"diferente do ano na data de apresentação")
return cleaned_data
class MateriaLegislativaForm(FileFieldCheckMixin, ModelForm):
@ -256,13 +237,6 @@ class MateriaLegislativaForm(FileFieldCheckMixin, ModelForm):
raise ValidationError(
_('Tipo do Protocolo deve ser o mesmo do Tipo Matéria'))
if data_apresentacao.year != ano:
self.logger.error("O ano da matéria ({}) é diferente "
"do ano na data de apresentação ({})."
.format(ano, data_apresentacao.year))
raise ValidationError(_("O ano da matéria não pode ser "
"diferente do ano na data de apresentação"))
ano_origem_externa = cleaned_data['ano_origem_externa']
data_origem_externa = cleaned_data['data_origem_externa']
@ -275,6 +249,12 @@ class MateriaLegislativaForm(FileFieldCheckMixin, ModelForm):
"pode ser diferente do ano na data de "
"origem externa"))
texto_original = self.cleaned_data.get('texto_original', False)
if texto_original and texto_original.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Original deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (texto_original.size/1024)/1024))
return cleaned_data
def save(self, commit=False):
@ -365,6 +345,20 @@ class DocumentoAcessorioForm(FileFieldCheckMixin, ModelForm):
model = DocumentoAcessorio
fields = ['tipo', 'nome', 'data', 'autor', 'ementa', 'arquivo']
def clean(self):
super(DocumentoAcessorioForm, self).clean()
if not self.is_valid():
return self.cleaned_data
arquivo = self.cleaned_data.get('arquivo', False)
if arquivo and arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Integral deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (arquivo.size/1024)/1024))
return self.cleaned_data
class RelatoriaForm(ModelForm):
logger = logging.getLogger(__name__)
@ -561,7 +555,8 @@ class TramitacaoForm(ModelForm):
materia.em_tramitacao = False if tramitacao.status.indicador == "F" else True
materia.save()
tramitar_anexadas = sapl.base.models.AppConfig.attr('tramitacao_materia')
tramitar_anexadas = sapl.base.models.AppConfig.attr(
'tramitacao_materia')
if tramitar_anexadas:
lista_tramitacao = []
anexadas_list = lista_anexados(materia)
@ -596,10 +591,13 @@ def compara_tramitacoes_mat(tramitacao1, tramitacao2):
return False
lst_items = ['id', 'materia_id', 'timestamp']
values = [(k,v) for k,v in tramitacao1.__dict__.items() if ((k not in lst_items) and (k[0] != '_'))]
other_values = [(k,v) for k,v in tramitacao2.__dict__.items() if (k not in lst_items and k[0] != '_')]
values = [(k, v) for k, v in tramitacao1.__dict__.items()
if ((k not in lst_items) and (k[0] != '_'))]
other_values = [(k, v) for k, v in tramitacao2.__dict__.items()
if (k not in lst_items and k[0] != '_')]
return values == other_values
class TramitacaoUpdateForm(TramitacaoForm):
unidade_tramitacao_local = forms.ModelChoiceField(
queryset=UnidadeTramitacao.objects.all(),
@ -673,7 +671,8 @@ class TramitacaoUpdateForm(TramitacaoForm):
materia.em_tramitacao = False if nova_tram_principal.status.indicador == "F" else True
materia.save()
tramitar_anexadas = sapl.base.models.AppConfig.attr('tramitacao_materia')
tramitar_anexadas = sapl.base.models.AppConfig.attr(
'tramitacao_materia')
if tramitar_anexadas:
anexadas_list = lista_anexados(materia)
for ma in anexadas_list:
@ -696,6 +695,7 @@ class TramitacaoUpdateForm(TramitacaoForm):
ma.save()
return nova_tram_principal
class LegislacaoCitadaForm(ModelForm):
tipo = forms.ModelChoiceField(
@ -869,8 +869,10 @@ class AnexadaForm(ModelForm):
data_desanexacao = cleaned_data['data_desanexacao'] if cleaned_data['data_desanexacao'] else data_anexacao
if data_anexacao > data_desanexacao:
self.logger.error("Data de anexação posterior à data de desanexação.")
raise ValidationError(_("Data de anexação posterior à data de desanexação."))
self.logger.error(
"Data de anexação posterior à data de desanexação.")
raise ValidationError(
_("Data de anexação posterior à data de desanexação."))
try:
self.logger.info("Tentando obter objeto MateriaLegislativa (numero={}, ano={}, tipo={})."
@ -901,7 +903,8 @@ class AnexadaForm(ModelForm):
raise ValidationError(_('Matéria já se encontra anexada'))
ciclico = False
anexadas_anexada = Anexada.objects.filter(materia_principal=materia_anexada)
anexadas_anexada = Anexada.objects.filter(
materia_principal=materia_anexada)
while anexadas_anexada and not ciclico:
anexadas = []
@ -917,8 +920,10 @@ class AnexadaForm(ModelForm):
anexadas_anexada = anexadas
if ciclico:
self.logger.error("A matéria não pode ser anexada por uma de suas anexadas.")
raise ValidationError(_("A matéria não pode ser anexada por uma de suas anexadas."))
self.logger.error(
"A matéria não pode ser anexada por uma de suas anexadas.")
raise ValidationError(
_("A matéria não pode ser anexada por uma de suas anexadas."))
cleaned_data['materia_anexada'] = materia_anexada
@ -1079,9 +1084,6 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
HTML(autor_modal),
row4, row6, row7, row9,
form_actions(label=_('Pesquisar')))
)
@property
@ -1140,9 +1142,50 @@ def filtra_tramitacao_destino_and_status(status, destino):
'materia_id', flat=True)
class DespachoInicialCreateForm(forms.Form):
comissao = forms.ModelMultipleChoiceField(
queryset=Comissao.objects.filter(ativa=True),
widget=forms.CheckboxSelectMultiple(),
label=Comissao._meta.verbose_name_plural)
def __init__(self, *args, **kwargs):
row1 = to_row(
[('comissao', 12), ])
self.helper = SaplFormHelper()
self.helper.form_method = 'POST'
self.helper.layout = SaplFormLayout(row1)
super().__init__(*args, **kwargs)
def clean(self):
super().clean()
comissoes = self.cleaned_data.get('comissao')
if not comissoes:
msg = _('Você deve escolher pelo menos uma comissão.')
raise ValidationError(msg)
if not self.is_valid():
return self.cleaned_data
errors = []
for comissao in comissoes:
if DespachoInicial.objects.filter(
materia=self.initial['materia'],
comissao=comissao,
).exists():
msg = _('Já existe um Despacho cadastrado para %s' %
comissao)
errors.append(msg)
if errors:
raise ValidationError(errors)
return self.cleaned_data
class DespachoInicialForm(ModelForm):
comissao = forms.ModelChoiceField(
queryset=Comissao.objects.filter(ativa=True))
queryset=Comissao.objects.filter(ativa=True), label=_('Comissão'))
class Meta:
model = DespachoInicial
@ -1184,7 +1227,8 @@ class AutoriaForm(ModelForm):
if 'initial' in kwargs and 'materia' in kwargs['initial']:
materia = kwargs['initial']['materia']
self.fields['primeiro_autor'].initial = Autoria.objects.filter(materia=materia).count() == 0
self.fields['primeiro_autor'].initial = Autoria.objects.filter(
materia=materia).count() == 0
row1 = to_row([('tipo_autor', 4),
('autor', 4),
@ -1254,7 +1298,8 @@ class AutoriaMultiCreateForm(Form):
super().__init__(*args, **kwargs)
if 'initial' in kwargs and 'autores' in kwargs['initial']:
self.fields['primeiro_autor'].initial = kwargs['initial']['autores'].count() == 0
self.fields['primeiro_autor'].initial = kwargs['initial']['autores'].count(
) == 0
row1 = to_row([('tipo_autor', 10), ('primeiro_autor', 2)])
@ -1456,7 +1501,8 @@ class TipoProposicaoForm(ModelForm):
self.fields['content_type'].choices = [
(ct.pk, ct) for k, ct in content_types.items()]
self.fields['content_type'].choices.sort(key=lambda x: str(x[1]))
# Ordena por id
self.fields['content_type'].choices.sort(key=lambda x: x[0])
if self.instance.pk:
self.fields[
@ -1536,6 +1582,197 @@ class TipoProposicaoSelect(Select):
return option
class TramitacaoEmLoteForm(ModelForm):
logger = logging.getLogger(__name__)
class Meta:
model = Tramitacao
fields = ['data_tramitacao',
'unidade_tramitacao_local',
'status',
'urgente',
'turno',
'unidade_tramitacao_destino',
'data_encaminhamento',
'data_fim_prazo',
'texto',
'user',
'ip']
widgets = {'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
super(TramitacaoEmLoteForm, self).__init__(*args, **kwargs)
self.fields['data_tramitacao'].initial = timezone.now().date()
ust = UnidadeTramitacao.objects.select_related().all()
unidade_tramitacao_destino = [('', '---------')] + [(ut.pk, ut)
for ut in ust if ut.comissao and ut.comissao.ativa]
unidade_tramitacao_destino.extend(
[(ut.pk, ut) for ut in ust if ut.orgao])
unidade_tramitacao_destino.extend(
[(ut.pk, ut) for ut in ust if ut.parlamentar])
self.fields['unidade_tramitacao_destino'].choices = unidade_tramitacao_destino
self.fields['urgente'].label = "Urgente? *"
row1 = to_row([
('data_tramitacao', 4),
('data_encaminhamento', 4),
('data_fim_prazo', 4)
])
row2 = to_row([
('unidade_tramitacao_local', 6),
('unidade_tramitacao_destino', 6),
])
row3 = to_row([
('status', 4),
('urgente', 4),
('turno', 4)
])
row4 = to_row([
('texto', 12)
])
documentos_checkbox_HTML = '''
<br\><br\><br\>
<fieldset>
<legend style="font-size: 24px;">Selecione as matérias para tramitação:</legend>
<table class="table table-striped table-hover">
<div class="controls">
<div class="checkbox">
<label for="id_check_all">
<input type="checkbox" id="id_check_all" onchange="checkAll(this)" /> Marcar/Desmarcar Todos
</label>
</div>
</div>
<thead>
<tr><th>Matéria</th></tr>
</thead>
<tbody>
{% for materia in object_list %}
<tr>
<td>
<input type="checkbox" name="materias" value="{{materia.id}}" {% if check %} checked {% endif %}/>
<a href="{% url 'sapl.materia:materialegislativa_detail' materia.id %}">
{{materia.tipo.sigla}} {{materia.tipo.descricao}} {{materia.numero}}/{{materia.ano}}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</fieldset>
'''
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
'Detalhes da tramitação:',
row1, row2, row3, row4,
HTML(documentos_checkbox_HTML),
form_actions(label='Salvar')
)
)
def clean(self):
cleaned_data = super(TramitacaoEmLoteForm, self).clean()
if not self.is_valid():
return self.cleaned_data
if 'data_encaminhamento' in cleaned_data:
data_enc_form = cleaned_data['data_encaminhamento']
if 'data_fim_prazo' in cleaned_data:
data_prazo_form = cleaned_data['data_fim_prazo']
if 'data_tramitacao' in cleaned_data:
data_tram_form = cleaned_data['data_tramitacao']
if not self.instance.data_tramitacao:
if cleaned_data['data_tramitacao'] > timezone.now().date():
self.logger.error('A data de tramitação ({}) deve ser '
'menor ou igual a data de hoje ({})!'
.format(cleaned_data['data_tramitacao'], timezone.now().date()))
msg = _(
'A data de tramitação deve ser ' +
'menor ou igual a data de hoje!')
raise ValidationError(msg)
if data_enc_form:
if data_enc_form < data_tram_form:
self.logger.error('A data de encaminhamento ({}) deve ser '
'maior que a data de tramitação ({})!'
.format(data_enc_form, data_tram_form))
msg = _('A data de encaminhamento deve ser ' +
'maior que a data de tramitação!')
raise ValidationError(msg)
if data_prazo_form:
if data_prazo_form < data_tram_form:
self.logger.error('A data fim de prazo ({}) deve ser '
'maior que a data de tramitação ({})!'
.format(data_prazo_form, data_tram_form))
msg = _('A data fim de prazo deve ser ' +
'maior que a data de tramitação!')
raise ValidationError(msg)
return cleaned_data
@transaction.atomic
def save(self, commit=True):
cd = self.cleaned_data
materias = self.initial['materias']
user = self.initial['user'] if 'user' in self.initial else None
ip = self.initial['ip'] if 'ip' in self.initial else ''
tramitar_anexadas = AppConfig.attr('tramitacao_materia')
for mat_id in materias:
mat = MateriaLegislativa.objects.get(id=mat_id)
tramitacao = Tramitacao.objects.create(
status=cd['status'],
materia=mat,
data_tramitacao=cd['data_tramitacao'],
unidade_tramitacao_local=cd['unidade_tramitacao_local'],
unidade_tramitacao_destino=cd['unidade_tramitacao_destino'],
data_encaminhamento=cd['data_encaminhamento'],
urgente=cd['urgente'],
turno=cd['turno'],
texto=cd['texto'],
data_fim_prazo=cd['data_fim_prazo'],
user=user,
ip=ip
)
mat.em_tramitacao = False if tramitacao.status.indicador == "F" else True
mat.save()
if tramitar_anexadas:
lista_tramitacao = []
anexadas = lista_anexados(mat)
for ml in anexadas:
if not ml.tramitacao_set.all() \
or ml.tramitacao_set.last() \
.unidade_tramitacao_destino == tramitacao.unidade_tramitacao_local:
ml.em_tramitacao = False if tramitacao.status.indicador == "F" else True
ml.save()
lista_tramitacao.append(Tramitacao(
status=tramitacao.status,
materia=ml,
data_tramitacao=tramitacao.data_tramitacao,
unidade_tramitacao_local=tramitacao.unidade_tramitacao_local,
data_encaminhamento=tramitacao.data_encaminhamento,
unidade_tramitacao_destino=tramitacao.unidade_tramitacao_destino,
urgente=tramitacao.urgente,
turno=tramitacao.turno,
texto=tramitacao.texto,
data_fim_prazo=tramitacao.data_fim_prazo,
user=tramitacao.user,
ip=tramitacao.ip
))
Tramitacao.objects.bulk_create(lista_tramitacao)
return tramitacao
class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
logger = logging.getLogger(__name__)
@ -1584,7 +1821,6 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
'observacao',
'texto_original',
'materia_de_vinculo',
'tipo_materia',
'numero_materia',
'ano_materia',
@ -1672,12 +1908,11 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
def clean_texto_original(self):
texto_original = self.cleaned_data.get('texto_original', False)
if texto_original and texto_original.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
self.logger.error(
"- Arquivo muito grande. ( > {0}MB )".format(max_size))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))
raise ValidationError("O arquivo Texto Original deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (texto_original.size/1024)/1024))
return texto_original
def gerar_hash(self, inst, receber_recibo):
@ -1731,6 +1966,7 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
self.logger.info("MateriaLegislativa vinculada (tipo_id={}, ano={}, numero={}) com sucesso."
.format(tm, am, nm))
cd['materia_de_vinculo'] = materia_de_vinculo
return cd
def save(self, commit=True):

21
sapl/materia/migrations/0051_auto_20190703_1414.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-03 17:14
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('materia', '0050_auto_20190521_1148'),
]
operations = [
migrations.AlterField(
model_name='despachoinicial',
name='comissao',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='comissoes.Comissao', verbose_name='Comissão'),
),
]

2
sapl/materia/models.py

@ -485,7 +485,7 @@ class AssuntoMateria(models.Model):
@reversion.register()
class DespachoInicial(models.Model):
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE)
comissao = models.ForeignKey(Comissao, on_delete=models.CASCADE)
comissao = models.ForeignKey(Comissao, on_delete=models.CASCADE, verbose_name="Comissão")
class Meta:
verbose_name = _('Despacho Inicial')

22
sapl/materia/urls.py

@ -26,9 +26,11 @@ from sapl.materia.views import (AcompanhamentoConfirmarView,
TramitacaoEmLoteView, UnidadeTramitacaoCrud,
proposicao_texto, recuperar_materia,
ExcluirTramitacaoEmLoteView, RetornarProposicao,
MateriaPesquisaSimplesView)
MateriaPesquisaSimplesView,
DespachoInicialMultiCreateView)
from sapl.norma.views import NormaPesquisaSimplesView
from sapl.protocoloadm.views import (FichaPesquisaAdmView, FichaSelecionaAdmView)
from sapl.protocoloadm.views import (
FichaPesquisaAdmView, FichaSelecionaAdmView)
from .apps import AppConfig
@ -55,13 +57,20 @@ urlpatterns_impressos = [
name='impressos_materia_pesquisa'),
url(r'^materia/impressos/ficha-pesquisa-adm/$',
FichaPesquisaAdmView.as_view(),
name= 'impressos_ficha_pesquisa_adm'),
name='impressos_ficha_pesquisa_adm'),
url(r'^materia/impressos/ficha-seleciona-adm/$',
FichaSelecionaAdmView.as_view(),
name= 'impressos_ficha_seleciona_adm'),
name='impressos_ficha_seleciona_adm'),
]
urlpatterns_materia = [
# Esta customização substitui a url do crud desque que ela permaneça antes
# da inclusão das urls de DespachoInicialCrud
url(r'^materia/(?P<pk>\d+)/despachoinicial/create',
DespachoInicialMultiCreateView.as_view(),
name='despacho-inicial-multi'),
url(r'^materia/', include(MateriaLegislativaCrud.get_urls() +
AnexadaCrud.get_urls() +
AutoriaCrud.get_urls() +
@ -76,7 +85,8 @@ urlpatterns_materia = [
url(r'^materia/(?P<pk>[0-9]+)/create_simplificado$',
CriarProtocoloMateriaView.as_view(),
name='materia_create_simplificado'),
url(r'^materia/recuperar-materia', recuperar_materia, name='recuperar_materia'),
url(r'^materia/recuperar-materia',
recuperar_materia, name='recuperar_materia'),
url(r'^materia/(?P<pk>[0-9]+)/ta$',
MateriaTaView.as_view(), name='materia_ta'),
@ -96,6 +106,7 @@ urlpatterns_materia = [
AutoriaMultiCreateView.as_view(),
name='autoria_multicreate'),
url(r'^materia/acessorio-em-lote', DocumentoAcessorioEmLoteView.as_view(),
name='acessorio_em_lote'),
url(r'^materia/(?P<pk>\d+)/anexada-em-lote', MateriaAnexadaEmLoteView.as_view(),
@ -136,6 +147,7 @@ urlpatterns_proposicao = [
name='proposicao_texto'),
url(r'^proposicao/(?P<pk>\d+)/retornar', RetornarProposicao.as_view(),
name='retornar-proposicao'),
]
urlpatterns_sistema = [

287
sapl/materia/views.py

@ -1,13 +1,11 @@
from datetime import datetime
import itertools
import logging
import os
import shutil
import tempfile
import weasyprint
import itertools
from datetime import datetime
from random import choice
import shutil
from string import ascii_letters, digits
import tempfile
from crispy_forms.layout import HTML
from django.conf import settings
@ -28,6 +26,7 @@ from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView
from django_filters.views import FilterView
import weasyprint
import weasyprint
import sapl
from sapl.base.email_utils import do_envia_email_confirmacao
@ -47,11 +46,12 @@ from sapl.materia.forms import (AnexadaForm, AutoriaForm,
ConfirmarProposicaoForm,
DevolverProposicaoForm, LegislacaoCitadaForm,
OrgaoForm, ProposicaoForm, TipoProposicaoForm,
TramitacaoForm, TramitacaoUpdateForm, MateriaPesquisaSimplesForm)
TramitacaoForm, TramitacaoUpdateForm, MateriaPesquisaSimplesForm,
DespachoInicialCreateForm)
from sapl.norma.models import LegislacaoCitada
from sapl.parlamentares.models import Legislatura
from sapl.protocoloadm.models import Protocolo
from sapl.settings import MEDIA_ROOT
from sapl.settings import MEDIA_ROOT, MAX_DOC_UPLOAD_SIZE
from sapl.utils import (YES_NO_CHOICES, autor_label, autor_modal, SEPARADOR_HASH_PROPOSICAO,
gerar_hash_arquivo, get_base_url, get_client_ip,
get_mime_type_from_file_extension, montar_row_autor,
@ -69,7 +69,8 @@ from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
filtra_tramitacao_destino,
filtra_tramitacao_destino_and_status,
filtra_tramitacao_status,
ExcluirTramitacaoEmLote, compara_tramitacoes_mat)
ExcluirTramitacaoEmLote, compara_tramitacoes_mat,
TramitacaoEmLoteForm)
from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria,
DespachoInicial, DocumentoAcessorio, MateriaAssunto,
MateriaLegislativa, Numeracao, Orgao, Origem, Proposicao,
@ -1203,7 +1204,8 @@ class TramitacaoCrud(MasterDetailCrud):
'-timestamp',
'-id').first()
#TODO: Esta checagem foi inserida na issue #2027, mas é mesmo necessária?
# TODO: Esta checagem foi inserida na issue #2027, mas é mesmo
# necessária?
if ultima_tramitacao:
if ultima_tramitacao.unidade_tramitacao_destino:
context['form'].fields[
@ -1257,7 +1259,8 @@ class TramitacaoCrud(MasterDetailCrud):
layout_key = 'TramitacaoUpdate'
def form_valid(self, form):
dict_objeto_antigo = Tramitacao.objects.get(pk=self.kwargs['pk']).__dict__
dict_objeto_antigo = Tramitacao.objects.get(
pk=self.kwargs['pk']).__dict__
self.object = form.save()
dict_objeto_novo = self.object.__dict__
@ -1269,7 +1272,8 @@ class TramitacaoCrud(MasterDetailCrud):
'data_encaminhamento', 'data_fim_prazo', 'urgente', 'turno'
]
# Se não houve qualquer alteração em um dos dados, mantém o usuário e ip
# Se não houve qualquer alteração em um dos dados, mantém o usuário
# e ip
for atributo in atributos:
if dict_objeto_antigo[atributo] != dict_objeto_novo[atributo]:
self.object.user = user
@ -1331,7 +1335,8 @@ class TramitacaoCrud(MasterDetailCrud):
if materia.tramitacao_set.count() == 0:
materia.em_tramitacao = False
materia.save()
tramitar_anexadas = sapl.base.models.AppConfig.attr('tramitacao_materia')
tramitar_anexadas = sapl.base.models.AppConfig.attr(
'tramitacao_materia')
if tramitar_anexadas:
mat_anexadas = lista_anexados(materia)
for ma in mat_anexadas:
@ -1484,20 +1489,58 @@ class AutoriaMultiCreateView(PermissionRequiredForAppCrudMixin, FormView):
autores_selecionados = form.cleaned_data['autor']
primeiro_autor = form.cleaned_data['primeiro_autor']
for autor in autores_selecionados:
Autoria.objects.create(materia=self.materia, autor=autor, primeiro_autor=primeiro_autor)
Autoria.objects.create(materia=self.materia,
autor=autor, primeiro_autor=primeiro_autor)
return FormView.form_valid(self, form)
class DespachoInicialMultiCreateView(PermissionRequiredForAppCrudMixin, FormView):
app_label = sapl.materia.apps.AppConfig.label
form_class = DespachoInicialCreateForm
template_name = 'materia/despachoinicial_multicreate_form.html'
def get_initial(self):
initial = super().get_initial()
self.materia = MateriaLegislativa.objects.get(id=self.kwargs['pk'])
initial['materia'] = self.materia
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '%s <small>(%s)</small>' % (
_('Adicionar Vários Despachos'), self.materia)
context['root_pk'] = self.kwargs['pk']
context['subnav_template_name'] = 'materia/subnav.yaml'
return context
def get_success_url(self):
messages.add_message(
self.request, messages.SUCCESS,
_('Despachos adicionados com sucesso.'))
return reverse(
'sapl.materia:despachoinicial_list', kwargs={'pk': self.materia.pk})
def form_valid(self, form):
comissoes_selecionadas = form.cleaned_data['comissao']
for comissao in comissoes_selecionadas:
DespachoInicial.objects.create(
materia=self.materia, comissao=comissao)
return FormView.form_valid(self, form)
@property
def cancel_url(self):
return reverse(
'sapl.materia:despachoinicial_list', kwargs={'pk': self.materia.pk})
class DespachoInicialCrud(MasterDetailCrud):
model = DespachoInicial
parent_field = 'materia'
help_topic = 'despacho_autoria'
public = [RP_LIST, RP_DETAIL]
class CreateView(MasterDetailCrud.CreateView):
form_class = DespachoInicialForm
class UpdateView(MasterDetailCrud.UpdateView):
form_class = DespachoInicialForm
@ -1620,7 +1663,7 @@ class MateriaLegislativaCrud(Crud):
form_class = MateriaLegislativaForm
def get_initial(self):
initial = super(CreateView, self).get_initial()
initial = super().get_initial()
initial['user'] = self.request.user
initial['ip'] = get_client_ip(self.request)
@ -1686,7 +1729,8 @@ class MateriaLegislativaCrud(Crud):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user'] = self.request.user
context['materia'] = MateriaLegislativa.objects.get(pk=self.kwargs['pk'])
context['materia'] = MateriaLegislativa.objects.get(
pk=self.kwargs['pk'])
return context
class ListView(Crud.ListView, RedirectView):
@ -2074,6 +2118,13 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
if request.FILES['arquivo'].size > MAX_DOC_UPLOAD_SIZE:
msg = _("O arquivo Anexo de Texto Integral deve ser menor que {0:.1f} MB, \
o tamanho atual desse arquivo é {1:.1f} MB" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (request.FILES['arquivo'].size/1024)/1024))
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
tmp_name = os.path.join(MEDIA_ROOT, request.FILES['arquivo'].name)
with open(tmp_name, 'wb') as destination:
for chunk in request.FILES['arquivo'].chunks():
@ -2082,9 +2133,11 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
doc_data = tz.localize(datetime.strptime(
request.POST['data'], "%d/%m/%Y"))
except Exception as e:
msg = _('Formato da data incorreto. O formato deve ser da forma dd/mm/aaaa.')
msg = _(
'Formato da data incorreto. O formato deve ser da forma dd/mm/aaaa.')
messages.add_message(request, messages.ERROR, msg)
self.logger.error("User={}. {}. Data inserida: {}".format(username, str(msg), request.POST['data']))
self.logger.error("User={}. {}. Data inserida: {}".format(
username, str(msg), request.POST['data']))
os.remove(tmp_name)
return self.get(request, self.kwargs)
@ -2100,12 +2153,13 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
try:
doc.clean_fields()
except ValidationError as e:
for m in [ '%s: %s' % (DocumentoAcessorio()._meta.get_field(k).verbose_name, '</br>'.join(v))
for k,v in e.message_dict.items() ]:
for m in ['%s: %s' % (DocumentoAcessorio()._meta.get_field(k).verbose_name, '</br>'.join(v))
for k, v in e.message_dict.items()]:
# Insere as mensagens de erro no formato:
# 'verbose_name do nome do campo': 'mensagem de erro'
messages.add_message(request, messages.ERROR, m)
self.logger.error("User={}. {}. Nome do arquivo: {}.".format(username, str(msg), request.FILES['arquivo'].name))
self.logger.error("User={}. {}. Nome do arquivo: {}.".format(
username, str(msg), request.FILES['arquivo'].name))
os.remove(tmp_name)
return self.get(request, self.kwargs)
doc.save()
@ -2118,7 +2172,8 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
file_path = os.path.join(diretorio,
request.FILES['arquivo'].name)
shutil.copy2(tmp_name, file_path)
doc.arquivo.name = file_path.split(MEDIA_ROOT + "/")[1] # Retira MEDIA_ROOT do nome
doc.arquivo.name = file_path.split(
MEDIA_ROOT + "/")[1] # Retira MEDIA_ROOT do nome
doc.save()
os.remove(tmp_name)
@ -2144,17 +2199,17 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
# Verifica se os campos foram preenchidos
if not self.request.GET.get('tipo', " "):
msg =_('Por favor, selecione um tipo de matéria.')
msg = _('Por favor, selecione um tipo de matéria.')
messages.add_message(self.request, messages.ERROR, msg)
if not self.request.GET.get('data_apresentacao_0', " ") or not self.request.GET.get('data_apresentacao_1', " "):
msg =_('Por favor, preencha as datas.')
msg = _('Por favor, preencha as datas.')
messages.add_message(self.request, messages.ERROR, msg)
return context
if not self.request.GET.get('data_apresentacao_0', " ") or not self.request.GET.get('data_apresentacao_1', " "):
msg =_('Por favor, preencha as datas.')
msg = _('Por favor, preencha as datas.')
messages.add_message(self.request, messages.ERROR, msg)
return context
@ -2163,8 +2218,10 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
'numero', '-ano')
principal = MateriaLegislativa.objects.get(pk=self.kwargs['pk'])
not_list = [self.kwargs['pk']] + \
[m for m in principal.materia_principal_set.all().values_list('materia_anexada_id', flat=True)]
context['object_list'] = context['object_list'].exclude(pk__in=not_list)
[m for m in principal.materia_principal_set.all(
).values_list('materia_anexada_id', flat=True)]
context['object_list'] = context['object_list'].exclude(
pk__in=not_list)
context['temp_object_list'] = context['object_list']
context['object_list'] = []
@ -2172,7 +2229,7 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
materia_anexada = obj
ciclico = False
anexadas_anexada = Anexada.objects.filter(
materia_principal = materia_anexada
materia_principal=materia_anexada
)
while anexadas_anexada and not ciclico:
@ -2241,7 +2298,8 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
msg = _('Matéria(s) anexada(s).')
messages.add_message(request, messages.SUCCESS, msg)
success_url = reverse('sapl.materia:anexada_list', kwargs={'pk': kwargs['pk']})
success_url = reverse('sapl.materia:anexada_list',
kwargs={'pk': kwargs['pk']})
return HttpResponseRedirect(success_url)
@ -2254,39 +2312,34 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
logger = logging.getLogger(__name__)
def get_context_data(self, **kwargs):
context = super(PrimeiraTramitacaoEmLoteView,
self).get_context_data(**kwargs)
context['subnav_template_name'] = 'materia/em_lote/subnav_em_lote.yaml'
context['primeira_tramitacao'] = self.primeira_tramitacao
# Verifica se os campos foram preenchidos
if not self.filterset.form.is_valid():
return context
context['title'] = _('Primeira Tramitação em Lote')
context['object_list'] = context['object_list'].order_by(
'ano', 'numero')
qr = self.request.GET.copy()
context['unidade_destino'] = UnidadeTramitacao.objects.all()
context['status_tramitacao'] = StatusTramitacao.objects.all()
context['turnos_tramitacao'] = Tramitacao.TURNO_CHOICES
context['urgente_tramitacao'] = YES_NO_CHOICES
context['unidade_local'] = UnidadeTramitacao.objects.all()
context['primeira_tramitacao'] = True
form = TramitacaoEmLoteForm()
context['form'] = form
# Pega somente matéria que não possuem tramitação
if (type(self.__dict__['filterset']).__name__ ==
'PrimeiraTramitacaoEmLoteFilterSet'):
context['object_list'] = context['object_list'].filter(
tramitacao__isnull=True)
if self.primeira_tramitacao:
context['title'] = _('Primeira Tramitação em Lote')
# Pega somente documentos que não possuem tramitação
context['object_list'] = [obj for obj in context['object_list']
if obj.tramitacao_set.all().count() == 0]
else:
context['title'] = _('Tramitação em Lote')
context['unidade_local'] = [UnidadeTramitacao.objects.get(
id=qr['tramitacao__unidade_tramitacao_destino'])]
context['object_list'] = context['object_list'].order_by(
'ano', 'numero')
context['form'].fields['unidade_tramitacao_local'].initial = UnidadeTramitacao.objects.get(
id=qr['tramitacao__unidade_tramitacao_destino'])
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
@ -2295,127 +2348,43 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
return context
def post(self, request, *args, **kwargs):
user = request.user
ip = get_client_ip(request)
marcadas = request.POST.getlist('materia_id')
tz = timezone.get_current_timezone()
username = request.user.username
if len(marcadas) == 0:
msg = _('Nenhuma máteria foi selecionada.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
obrigatorios = {'data_tramitacao': 'Data da Tramitação',
'unidade_tramitacao_local': 'Unidade Local',
'unidade_tramitacao_destino': 'Unidade Destino',
'status': 'Status',
'urgente': 'Urgente',
'texto': 'Texto da Ação'}
for field, nome in obrigatorios.items():
if not request.POST[field]:
msg = _('Campo {} deve ser preenchido.'.format(nome))
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
if not request.POST['data_encaminhamento']:
data_encaminhamento = None
else:
try:
data_encaminhamento = tz.localize(datetime.strptime(
request.POST['data_encaminhamento'], "%d/%m/%Y"))
except ValueError:
msg = _('Formato da data de encaminhamento incorreto.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
if request.POST['data_fim_prazo'] == '':
data_fim_prazo = None
else:
try:
data_fim_prazo = tz.localize(datetime.strptime(
request.POST['data_fim_prazo'], "%d/%m/%Y"))
except ValueError:
msg = _('Formato da data fim do prazo incorreto.')
materias_ids = request.POST.getlist('materias')
if not materias_ids:
msg = _("Escolha alguma matéria para ser tramitada.")
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
# issue https://github.com/interlegis/sapl/issues/1123
# TODO: usar Form
urgente = request.POST['urgente'] == 'True'
flag_error = False
form = TramitacaoEmLoteForm(request.POST,
initial= {'materias': materias_ids,
'user': user, 'ip':ip})
materias_principais = [m for m in MateriaLegislativa.objects.filter(id__in=marcadas)]
tramitar_anexadas = sapl.base.models.AppConfig.attr('tramitacao_materia')
materias_anexadas = []
if tramitar_anexadas:
for materia in materias_principais:
materias_anexadas = materias_anexadas + lista_anexados(materia)
if form.is_valid():
form.save()
materias = set(materias_principais + materias_anexadas)
msg = _('Tramitação completa.')
self.logger.info('user=' + user.username + '. Tramitação completa.')
messages.add_message(request, messages.SUCCESS, msg)
return self.get_success_url()
for materia in materias:
try:
data_tramitacao = tz.localize(datetime.strptime(
request.POST['data_tramitacao'], "%d/%m/%Y"))
except ValueError:
msg = _('Formato da data da tramitação incorreto.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
return self.form_invalid(form)
user = request.user
ip = get_client_ip(request)
t = Tramitacao(
materia=materia,
data_tramitacao=data_tramitacao,
data_encaminhamento=data_encaminhamento,
data_fim_prazo=data_fim_prazo,
unidade_tramitacao_local_id=request.POST[
'unidade_tramitacao_local'],
unidade_tramitacao_destino_id=request.POST[
'unidade_tramitacao_destino'],
urgente=urgente,
status_id=request.POST['status'],
turno=request.POST['turno'],
texto=request.POST['texto'],
user=user,
ip=ip
)
t.save()
try:
self.logger.debug("user=" + username +
". Tentando enviar tramitação.")
tramitacao_signal.send(sender=Tramitacao,
post=t,
request=self.request)
except Exception as e:
self.logger.error('user=' + username + '. Tramitação criada , mas e-mail de acompanhamento '
'de matéria não enviado. Há problemas na configuração '
'do e-mail. ' + str(e))
flag_error = True
if flag_error:
msg = _('Tramitação criada, mas e-mail de acompanhamento '
'de matéria 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)
def get_success_url(self):
return HttpResponseRedirect(reverse('sapl.materia:primeira_tramitacao_em_lote'))
status = StatusTramitacao.objects.get(id=request.POST['status'])
for materia in materias:
if status.indicador == 'F':
materia.em_tramitacao = False
elif self.primeira_tramitacao:
materia.em_tramitacao = True
materia.save()
def form_invalid(self, form, *args, **kwargs):
for key, erros in form.errors.items():
if not key=='__all__':
[messages.add_message(self.request, messages.ERROR, form.fields[key].label + ": " + e) for e in erros]
else:
[messages.add_message(self.request, messages.ERROR, e) for e in erros]
return self.get(self.request, kwargs, {'form':form})
msg = _('Tramitação completa. ' + "Foram tramitadas " + str(len(materias)) + " matéria(s).")
self.logger.info('user=' + username + '. Tramitação completa.')
messages.add_message(request, messages.SUCCESS, msg)
if self.primeira_tramitacao:
return HttpResponseRedirect(reverse('sapl.materia:primeira_tramitacao_em_lote'))
return HttpResponseRedirect(reverse('sapl.materia:tramitacao_em_lote'))
class TramitacaoEmLoteView(PrimeiraTramitacaoEmLoteView):
filterset_class = TramitacaoEmLoteFilterSet
@ -2428,7 +2397,7 @@ class TramitacaoEmLoteView(PrimeiraTramitacaoEmLoteView):
qr = self.request.GET.copy()
context['primeira_tramitacao'] = False
context['primeira_tramitacao'] = self.primeira_tramitacao
if ('tramitacao__status' in qr and
'tramitacao__unidade_tramitacao_destino' in qr and

38
sapl/norma/forms.py

@ -132,8 +132,12 @@ class NormaJuridicaForm(FileFieldCheckMixin, ModelForm):
'indexacao',
'observacao',
'texto_integral',
'assuntos']
widgets = {'assuntos': widgets.CheckboxSelectMultiple}
'assuntos',
'user',
'ip']
widgets = {'assuntos': widgets.CheckboxSelectMultiple,
'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
def clean(self):
@ -189,27 +193,17 @@ class NormaJuridicaForm(FileFieldCheckMixin, ModelForm):
else:
cleaned_data['materia'] = None
ano = cleaned_data['ano']
data = cleaned_data['data']
if data.year != ano:
self.logger.error("O ano da norma ({}) é diferente "
"do ano no campo data ({}).".format(ano, data.year))
raise ValidationError("O ano da norma não pode ser "
"diferente do ano no campo data")
return cleaned_data
def clean_texto_integral(self):
super(NormaJuridicaForm, self).clean()
texto_integral = self.cleaned_data.get('texto_integral', False)
if texto_integral and texto_integral.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
tam_fornecido = str(texto_integral.size / (1024 * 1024))
self.logger.error("Arquivo muito grande ({}MB). ( Tamanho máximo permitido: {}MB )".format(
tam_fornecido, max_size))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))
raise ValidationError("O arquivo Texto Integral deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (texto_integral.size/1024)/1024))
return texto_integral
def save(self, commit=False):
@ -283,16 +277,16 @@ class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm):
def clean(self):
cleaned_data = super(AnexoNormaJuridicaForm, self).clean()
if not self.is_valid():
return cleaned_data
anexo_arquivo = self.cleaned_data.get('anexo_arquivo', False)
if anexo_arquivo and anexo_arquivo.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
tam_fornecido = str(anexo_arquivo.size / (1024 * 1024))
self.logger.error("Arquivo muito grande ({}MB). ( Tamanho máximo permitido: {}MB )".format(
tam_fornecido, max_size))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))
raise ValidationError("O Arquivo Anexo deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (anexo_arquivo.size/1024)/1024))
return cleaned_data
def save(self, commit=False):

28
sapl/norma/migrations/0025_auto_20190704_1403.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-04 17:03
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('norma', '0024_auto_20190425_0917'),
]
operations = [
migrations.AddField(
model_name='normajuridica',
name='ip',
field=models.CharField(blank=True, default='', max_length=30, verbose_name='IP'),
),
migrations.AddField(
model_name='normajuridica',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Usuário'),
),
]

18
sapl/norma/models.py

@ -10,7 +10,9 @@ from sapl.base.models import Autor
from sapl.compilacao.models import TextoArticulado
from sapl.materia.models import MateriaLegislativa
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
restringe_tipos_de_arquivo_txt, texto_upload_path)
restringe_tipos_de_arquivo_txt,
texto_upload_path,
get_settings_auth_user_model)
@reversion.register()
@ -138,6 +140,20 @@ class NormaJuridica(models.Model):
through_fields=('norma', 'autor'),
symmetrical=False)
user = models.ForeignKey(
get_settings_auth_user_model(),
verbose_name=_('Usuário'),
on_delete=models.PROTECT,
null=True,
blank=True
)
ip = models.CharField(
verbose_name=_('IP'),
max_length=30,
blank=True,
default=''
)
class Meta:
verbose_name = _('Norma Jurídica')
verbose_name_plural = _('Normas Jurídicas')

51
sapl/norma/views.py

@ -20,7 +20,7 @@ from sapl.base.models import AppConfig
from sapl.compilacao.views import IntegracaoTaView
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud, make_pagination)
from sapl.utils import show_results_filter_set
from sapl.utils import show_results_filter_set, get_client_ip
from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm,
NormaPesquisaSimplesForm, NormaRelacionadaForm, AutoriaNormaForm)
@ -217,20 +217,24 @@ class NormaCrud(Crud):
return self.search_url
def get_initial(self):
username = self.request.user.username
initial = super().get_initial()
initial['user'] = self.request.user
initial['ip'] = get_client_ip(self.request)
username = self.request.user.username
try:
self.logger.debug(
'user=' + username + '. Tentando obter objeto de modelo da esfera da federação.')
esfera = sapl.base.models.AppConfig.objects.last(
).esfera_federacao
self.initial['esfera_federacao'] = esfera
initial['esfera_federacao'] = esfera
except:
self.logger.error(
'user=' + username + '. Erro ao obter objeto de modelo da esfera da federação.')
pass
self.initial['complemento'] = False
return self.initial
initial['complemento'] = False
return initial
layout_key = 'NormaJuridicaCreate'
@ -249,7 +253,7 @@ class NormaCrud(Crud):
layout_key = 'NormaJuridicaCreate'
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial = super().get_initial()
norma = NormaJuridica.objects.get(id=self.kwargs['pk'])
if norma.materia:
initial['tipo_materia'] = norma.materia.tipo
@ -258,6 +262,41 @@ class NormaCrud(Crud):
initial['esfera_federacao'] = norma.esfera_federacao
return initial
def form_valid(self, form):
norma_antiga = NormaJuridica.objects.get(
pk=self.kwargs['pk']
)
# Feito desta forma para que sejam materializados os assuntos antigos
assuntos_antigos = set(norma_antiga.assuntos.all())
dict_objeto_antigo = norma_antiga.__dict__
self.object = form.save()
dict_objeto_novo = self.object.__dict__
atributos = ['tipo_id', 'numero', 'ano', 'data', 'esfera_federacao',
'complemento', 'materia_id', 'numero',
'data_publicacao', 'data_vigencia',
'veiculo_publicacao', 'pagina_inicio_publicacao',
'pagina_fim_publicacao', 'ementa', 'indexacao',
'observacao', 'texto_integral']
for atributo in atributos:
if dict_objeto_antigo[atributo] != dict_objeto_novo[atributo]:
self.object.user = self.request.user
self.object.ip = get_client_ip(self.request)
self.object.save()
break
# Campo Assuntos não veio no __dict__, então é comparado separadamente
assuntos_novos = set(self.object.assuntos.all())
if assuntos_antigos != assuntos_novos:
self.object.user = self.request.user
self.object.ip = get_client_ip(self.request)
self.object.save()
return super().form_valid(form)
def recuperar_norma(request):
logger = logging.getLogger(__name__)

3
sapl/painel/views.py

@ -528,6 +528,9 @@ def get_dados_painel(request, pk):
'sessao_plenaria': str(sessao),
'sessao_plenaria_data': sessao.data_inicio.strftime('%d/%m/%Y'),
'sessao_plenaria_hora_inicio': sessao.hora_inicio,
'sessao_solene': sessao.tipo.nome == "Solene",
'sessao_finalizada': sessao.finalizada,
'tema_solene': sessao.tema_solene,
'cronometro_aparte': get_cronometro_status(request, 'aparte'),
'cronometro_discurso': get_cronometro_status(request, 'discurso'),
'cronometro_ordem': get_cronometro_status(request, 'ordem'),

6
sapl/parlamentares/urls.py

@ -19,7 +19,8 @@ from sapl.parlamentares.views import (CargoMesaCrud, ColigacaoCrud,
parlamentares_frente_selected,
remove_parlamentar_composicao,
parlamentares_filiados, BlocoCrud,
PesquisarParlamentarView, VincularParlamentarView)
PesquisarParlamentarView, VincularParlamentarView,
get_sessoes_legislatura)
from .apps import AppConfig
@ -91,4 +92,7 @@ urlpatterns = [
url(r'^mesa-diretora/remove-parlamentar-composicao/$',
remove_parlamentar_composicao, name='remove_parlamentar_composicao'),
url(r'^parlamentar/get-sessoes-legislatura/$',
get_sessoes_legislatura, name='get_sessoes_legislatura'),
]

11
sapl/parlamentares/views.py

@ -1177,3 +1177,14 @@ class BlocoCrud(CrudAux):
def get_success_url(self):
return reverse('sapl.parlamentares:bloco_list')
def get_sessoes_legislatura(request):
legislatura_id = request.GET['legislatura']
json_response = {'sessoes_legislativas': []}
for s in SessaoLegislativa.objects.filter(legislatura_id=legislatura_id):
json_response['sessoes_legislativas'].append( (s.id, str(s)) )
return JsonResponse(json_response)

27
sapl/protocoloadm/forms.py

@ -5,6 +5,7 @@ from crispy_forms.bootstrap import InlineRadios, Alert, FormActions
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Div, Submit
from django import forms
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from django.core.exceptions import (MultipleObjectsReturned,
ObjectDoesNotExist, ValidationError)
from django.db import models, transaction
@ -657,6 +658,20 @@ class DocumentoAcessorioAdministrativoForm(FileFieldCheckMixin, ModelForm):
'data': forms.DateInput(format='%d/%m/%Y')
}
def clean(self):
super(DocumentoAcessorioAdministrativoForm, self).clean()
if not self.is_valid():
return self.cleaned_data
arquivo = self.cleaned_data.get('arquivo', False)
if arquivo and arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (arquivo.size/1024)/1024))
return self.cleaned_data
class TramitacaoAdmForm(ModelForm):
@ -1142,6 +1157,12 @@ class DocumentoAdministrativoForm(FileFieldCheckMixin, ModelForm):
' documento vinculado'
% (numero_protocolo, ano_protocolo)))
texto_integral = self.cleaned_data.get('texto_integral', False)
if texto_integral and texto_integral.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Integral deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (texto_integral.size/1024)/1024))
return self.cleaned_data
def save(self, commit=True):
@ -1587,12 +1608,6 @@ class TramitacaoEmLoteAdmForm(ModelForm):
'maior que a data de tramitação!')
raise ValidationError(msg)
if cleaned_data['unidade_tramitacao_local'] == cleaned_data['unidade_tramitacao_destino']:
msg = _('Unidade tramitação local deve ser diferente da unidade tramitação destino.')
self.logger.error('Unidade tramitação local ({}) deve ser diferente da unidade tramitação destino'
.format(cleaned_data['unidade_tramitacao_local']))
raise ValidationError(msg)
return cleaned_data
@transaction.atomic

25
sapl/protocoloadm/migrations/0022_auto_20190715_1101.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-15 14:01
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0021_merge_20190429_1531'),
]
operations = [
migrations.AlterField(
model_name='protocolo',
name='ip_data_hora_manual',
field=models.CharField(blank=True, help_text='Endereço IP da estação de trabalho do usuário que está realizando Protocolo e informando data e hora manualmente.', max_length=256, verbose_name='IP'),
),
migrations.AlterField(
model_name='protocolo',
name='user_data_hora_manual',
field=models.CharField(blank=True, help_text='Usuário que está realizando Protocolo e informando data e hora manualmente.', max_length=256, verbose_name='IP'),
),
]

9
sapl/protocoloadm/models.py

@ -57,7 +57,6 @@ class Protocolo(models.Model):
null=False,
choices=RANGE_ANOS,
verbose_name=_('Ano do Protocolo'))
data = models.DateField(null=True, blank=True,
verbose_name=_('Data do Protocolo'),
help_text=_('Informado manualmente'))
@ -66,12 +65,12 @@ class Protocolo(models.Model):
help_text=_('Informado manualmente'))
timestamp_data_hora_manual = models.DateTimeField(default=timezone.now)
user_data_hora_manual = models.CharField(
max_length=20, blank=True,
max_length=256, blank=True,
verbose_name=_('IP'),
help_text=_('Usuário que está realizando Protocolo e informando '
'data e hora manualmente.'))
ip_data_hora_manual = models.CharField(
max_length=15, blank=True,
max_length=256, blank=True,
verbose_name=_('IP'),
help_text=_('Endereço IP da estação de trabalho '
'do usuário que está realizando Protocolo e informando '
@ -346,12 +345,12 @@ class TramitacaoAdministrativo(models.Model):
class Anexado(models.Model):
documento_principal = models.ForeignKey(
DocumentoAdministrativo, related_name='documento_principal_set',
on_delete = models.CASCADE,
on_delete=models.CASCADE,
verbose_name=_('Documento Principal')
)
documento_anexado = models.ForeignKey(
DocumentoAdministrativo, related_name='documento_anexado_set',
on_delete = models.CASCADE,
on_delete=models.CASCADE,
verbose_name=_('Documento Anexado')
)
data_anexacao = models.DateField(verbose_name=_('Data Anexação'))

30
sapl/protocoloadm/tests/test_protocoloadm.py

@ -881,20 +881,6 @@ def test_tramitacao_lote_documentos_form(admin_client):
["A data fim de prazo deve ser maior que a data de tramitação!"]
assert not form.is_valid()
form = TramitacaoEmLoteAdmForm(initial={'documentos': documentos}, data={})
form.data = {'data_tramitacao': '2019-05-14',
'data_encaminhamento' : '2019-05-15',
'data_fim_prazo': '2019-05-18',
'unidade_tramitacao_local': unidade_tramitacao_local_1.id,
'unidade_tramitacao_destino': unidade_tramitacao_local_1.id,
'status': status.id,
'urgente': False,
'texto': 'aaaa'}
assert form.errors['__all__'] == \
["Unidade tramitação local deve ser diferente da unidade tramitação destino."]
assert not form.is_valid()
form = TramitacaoEmLoteAdmForm(initial={'documentos': documentos}, data={})
form.data = {'data_tramitacao': '2019-05-14',
'data_encaminhamento' : '2019-05-15',
@ -1038,22 +1024,6 @@ def test_tramitacao_lote_documentos_views(admin_client):
assert 'Unidade Destino: Este campo é obrigatório.' in msgs
assert 'Texto da Ação: Este campo é obrigatório.' in msgs
response = admin_client.post(url_lote,
{'documentos': documentos,
'data_tramitacao': date(2019, 5, 15),
'unidade_tramitacao_local': unidade_tramitacao_destino_1.id,
'unidade_tramitacao_destino': unidade_tramitacao_destino_1.id,
'status': status.id,
'urgente': False,
'texto': 'aaaa',
'salvar':'salvar'},
follow=True)
assert response.status_code == 200
msgs = [m.message for m in response.context['messages']]
assert 'Unidade tramitação local deve ser diferente da unidade tramitação destino.' in msgs
response = admin_client.post(url_lote,
{'documentos': documentos,
'data_tramitacao': date(2019, 5, 15),

7
sapl/protocoloadm/views.py

@ -11,6 +11,7 @@ from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import Max, Q
from django.http import Http404, HttpResponse, JsonResponse
from django.http.response import HttpResponseRedirect
@ -539,6 +540,7 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
initial['hora'] = timezone.localtime(timezone.now())
return initial
@transaction.atomic
def form_valid(self, form):
protocolo = form.save(commit=False)
username = self.request.user.username
@ -726,6 +728,7 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
initial['hora'] = timezone.localtime(timezone.now())
return initial
@transaction.atomic
def form_valid(self, form):
protocolo = form.save(commit=False)
username = self.request.user.username
@ -983,7 +986,7 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
def get_context_data(self, **kwargs):
context = super(
DocumentoAnexadoEmLoteView,self
DocumentoAnexadoEmLoteView, self
).get_context_data(**kwargs)
context['root_pk'] = self.kwargs['pk']
@ -1545,7 +1548,7 @@ class TramitacaoEmLoteAdmView(PrimeiraTramitacaoEmLoteAdmView):
qr = self.request.GET.copy()
context['primeira_tramitacao'] = False
context['primeira_tramitacao'] = self.primeira_tramitacao
if ('tramitacao__status' in qr and
'tramitacao__unidade_tramitacao_destino' in qr and

3
sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py

@ -128,6 +128,9 @@ def inf_basicas(inf_basicas_dic):
if dat_fim_sessao or hr_fim_sessao:
tmp += '\t\t<para style="P2" spaceAfter="5"><b>Encerramento: </b> ' + \
dat_fim_sessao + ' <b>- </b> ' + hr_fim_sessao + '</para>\n'
if inf_basicas_dic.get('tema_solene'):
tmp += '\t\t<para style="P2" spaceAfter="5"><b>Tema da Sessão Solene: </b> ' + \
inf_basicas_dic['tema_solene'] + '</para>\n'
return tmp

4
sapl/relatorios/urls.py

@ -6,7 +6,7 @@ from .views import (relatorio_capa_processo,
relatorio_etiqueta_protocolo, relatorio_materia,
relatorio_ordem_dia, relatorio_pauta_sessao,
relatorio_protocolo, relatorio_sessao_plenaria,
resumo_ata_pdf)
resumo_ata_pdf, relatorio_sessao_plenaria_pdf)
app_name = AppConfig.name
@ -31,4 +31,6 @@ urlpatterns = [
relatorio_pauta_sessao, name='relatorio_pauta_sessao'),
url(r'^relatorios/(?P<pk>\d+)/resumo_ata$',
resumo_ata_pdf, name='resumo_ata_pdf'),
url(r'^relatorios/(?P<pk>\d+)/sessao-plenaria-pdf$',
relatorio_sessao_plenaria_pdf, name='relatorio_sessao_plenaria_pdf'),
]

79
sapl/relatorios/views.py

@ -520,6 +520,9 @@ def get_sessao_plenaria(sessao, casa):
inf_basicas_dic["hr_fim_sessao"] = sessao.hora_fim
inf_basicas_dic["nom_camara"] = casa.nome
if sessao.tipo.nome == 'Solene':
inf_basicas_dic["tema_solene"] = sessao.tema_solene
# Conteudo multimidia
cont_mult_dic = {}
if sessao.url_audio:
@ -1350,3 +1353,79 @@ def relatorio_doc_administrativos(request, context):
return response
def relatorio_sessao_plenaria_pdf(request, pk):
base_url=request.build_absolute_uri()
logger = logging.getLogger(__name__)
username = request.user.username
casa = CasaLegislativa.objects.first()
if not casa:
raise Http404
rodape = get_rodape(casa)
rodape = ' '.join(rodape)
try:
logger.debug("user=" + username +
". Tentando obter SessaoPlenaria com id={}.".format(pk))
sessao = SessaoPlenaria.objects.get(id=pk)
except ObjectDoesNotExist as e:
logger.error("user=" + username +
". Essa SessaoPlenaria não existe (pk={}). ".format(pk) + str(e))
raise Http404('Essa página não existe')
(inf_basicas_dic,
cont_mult_dic,
lst_mesa,
lst_presenca_sessao,
lst_ausencia_sessao,
lst_expedientes,
lst_expediente_materia,
lst_expediente_materia_vot_nom,
lst_oradores_expediente,
lst_presenca_ordem_dia,
lst_votacao,
lst_votacao_vot_nom,
lst_oradores_ordemdia,
lst_oradores,
lst_ocorrencias) = get_sessao_plenaria(sessao, casa)
html_template = render_to_string('relatorios/relatorio_sessao_plenaria.html',
{
"inf_basicas_dic":inf_basicas_dic,
"lst_mesa":lst_mesa,
"lst_presenca_sessao":lst_presenca_sessao,
"lst_ausencia_sessao":lst_ausencia_sessao,
"lst_expedientes":lst_expedientes,
"lst_expediente_materia":lst_expediente_materia,
"lst_oradores_expediente":lst_oradores_expediente,
"lst_presenca_ordem_dia":lst_presenca_ordem_dia,
"lst_votacao":lst_votacao,
"lst_oradores":lst_oradores,
"lst_ocorrencias":lst_ocorrencias,
"rodape":rodape,
"data": dt.today().strftime('%d/%m/%Y')
})
info = "Resumo da {}ª Reunião {} \
da {}ª Sessão Legislativa da {} \
Legislatura".format(inf_basicas_dic['num_sessao_plen'],
inf_basicas_dic['nom_sessao'],
inf_basicas_dic['num_sessao_leg'],
inf_basicas_dic['num_legislatura'],
inf_basicas_dic['num_legislatura']
)
html_header = render_to_string('relatorios/header_ata.html',{"casa":casa,
"MEDIA_URL": MEDIA_URL,
"logotipo": casa.logotipo,
"info":info})
pdf_file = make_pdf(base_url=base_url, main_template=html_template, header_template=html_header)
response = HttpResponse(content_type='application/pdf;')
response['Content-Disposition'] = 'inline; filename=relatorio.pdf'
response['Content-Transfer-Encoding'] = 'binary'
response.write(pdf_file)
return response

11
sapl/rules/apps.py

@ -84,17 +84,6 @@ def create_proxy_permissions(
ctypes.add(ctype)
# FIXME: Retirar try except quando sapl passar a usar django 1.11
try:
logger.info("_get_all_permissions")
# Função não existe mais em Django 1.11
# como sapl ainda não foi para Django 1.11
# esta excessão foi adicionada para caso o
# Sapl esteja rodando em um projeto 1.11 não ocorra erros
_all_perms_of_klass = _get_all_permissions(klass._meta, ctype)
except Exception as e:
logger.error(str(e))
# Nova função usada em projetos com Django 1.11 e o sapl é uma app
_all_perms_of_klass = _get_all_permissions(klass._meta)
for perm in _all_perms_of_klass:

45
sapl/sessao/forms.py

@ -11,6 +11,7 @@ from django.forms.widgets import CheckboxSelectMultiple
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.crispy_layout_mixin import form_actions, to_row, SaplFormLayout
@ -149,6 +150,24 @@ class SessaoPlenariaForm(FileFieldCheckMixin, ModelForm):
"entre a data de início e fim tanto da "
"Legislatura quanto da Sessão Legislativa.")
upload_pauta = self.cleaned_data.get('upload_pauta', False)
upload_ata = self.cleaned_data.get('upload_ata', False)
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_pauta and upload_pauta.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Pauta da Sessão deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_pauta.size/1024)/1024))
if upload_ata and upload_ata.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Ata da Sessão deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_ata.size/1024)/1024))
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo da Sessão deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
return self.cleaned_data
@ -656,6 +675,12 @@ class OradorForm(ModelForm):
"Já existe orador nesta posição de ordem de pronunciamento"
))
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo do Orador deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
return self.cleaned_data
class Meta:
@ -691,6 +716,12 @@ class OradorExpedienteForm(ModelForm):
raise ValidationError(_(
'Já existe orador nesta posição da ordem de pronunciamento'))
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo do Orador deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
return self.cleaned_data
class Meta:
@ -728,6 +759,12 @@ class OradorOrdemDiaForm(ModelForm):
"Já existe orador nesta posição de ordem de pronunciamento"
))
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo do Orador deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
return self.cleaned_data
class Meta:
@ -966,9 +1003,15 @@ class JustificativaAusenciaForm(ModelForm):
sessao_plenaria = self.instance.sessao_plenaria
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo de Justificativa deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
if not sessao_plenaria.finalizada or sessao_plenaria.finalizada is None:
raise ValidationError(
"A sessão deve está finalizada para registrar uma Ausência")
"A sessão deve estar finalizada para registrar uma Ausência")
else:
return self.cleaned_data

20
sapl/sessao/migrations/0041_sessaoplenaria_tema_solene.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-05-31 12:37
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sessao', '0040_auto_20190523_1130'),
]
operations = [
migrations.AddField(
model_name='sessaoplenaria',
name='tema_solene',
field=models.TextField(blank=True, max_length=500, verbose_name='Tema da Sessão Solene'),
),
]

16
sapl/sessao/migrations/0042_merge_20190612_0925.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-06-12 12:25
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('sessao', '0041_auto_20190610_1300'),
('sessao', '0041_sessaoplenaria_tema_solene'),
]
operations = [
]

2
sapl/sessao/models.py

@ -208,6 +208,8 @@ class SessaoPlenaria(models.Model):
interativa = models.NullBooleanField(blank=True,
choices=YES_NO_CHOICES,
verbose_name=_('Sessão interativa'))
tema_solene = models.TextField(
blank=True, max_length=500, verbose_name=_('Tema da Sessão Solene'))
class Meta:
verbose_name = _('Sessão Plenária')

8
sapl/sessao/urls.py

@ -33,7 +33,8 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente,
renumerar_materias_expediente,
sessao_legislativa_legislatura_ajax,
VotacaoEmBlocoOrdemDia, VotacaoEmBlocoExpediente,
VotacaoEmBlocoSimbolicaView, VotacaoEmBlocoNominalView)
VotacaoEmBlocoSimbolicaView, VotacaoEmBlocoNominalView,
recuperar_nome_tipo_sessao)
from .apps import AppConfig
@ -68,6 +69,9 @@ urlpatterns = [
recuperar_numero_sessao_view,
name='recuperar_numero_sessao_view'
),
url(r'^sessao/recuperar-nome-tipo-sessao/',
recuperar_nome_tipo_sessao,
name='recuperar_nome_tipo_sessao'),
url(r'^sessao/sessao-legislativa-legislatura-ajax/',
sessao_legislativa_legislatura_ajax,
name='sessao_legislativa_legislatura_ajax_view'),
@ -173,7 +177,7 @@ urlpatterns = [
name='votacaonominalexpdetail'),
url(r'^sessao/(?P<pk>\d+)/matexp/votsimb/(?P<oid>\d+)/(?P<mid>\d+)$',
VotacaoExpedienteView.as_view(), name='votacaosimbolicaexp'),
url(r'^sessao/(?P<pk>\d+)/matexp/votsec/view/(?P<oid>\d+)/(?P<mid>\d+)$',
url(r'^sessao/(?P<pk>\d+)/matexp/votsimb/view/(?P<oid>\d+)/(?P<mid>\d+)$',
VotacaoExpedienteEditView.as_view(), name='votacaosimbolicaexpedit'),
url(r'^sessao/(?P<pk>\d+)/matexp/votsec/(?P<oid>\d+)/(?P<mid>\d+)$',
VotacaoExpedienteView.as_view(), name='votacaosecretaexp'),

260
sapl/sessao/views.py

@ -515,7 +515,7 @@ class MateriaOrdemDiaCrud(MasterDetailCrud):
form_class = OrdemDiaForm
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial = super().get_initial()
initial['tipo_materia'] = self.object.materia.tipo.id
initial['numero_materia'] = self.object.materia.numero
initial['ano_materia'] = self.object.materia.ano
@ -576,7 +576,7 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
form_class = ExpedienteMateriaForm
def get_initial(self):
initial = super(CreateView, self).get_initial()
initial = super().get_initial()
initial['data_ordem'] = SessaoPlenaria.objects.get(
pk=self.kwargs['pk']).data_inicio.strftime('%d/%m/%Y')
max_numero_ordem = ExpedienteMateria.objects.filter(
@ -594,7 +594,7 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
form_class = ExpedienteMateriaForm
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial = super().get_initial()
initial['tipo_materia'] = self.object.materia.tipo.id
initial['numero_materia'] = self.object.materia.numero
initial['ano_materia'] = self.object.materia.ano
@ -614,6 +614,15 @@ class OradorCrud(MasterDetailCrud):
class ListView(MasterDetailCrud.ListView):
ordering = ['numero_ordem', 'parlamentar']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
sessao_pk = context['root_pk']
sessao = SessaoPlenaria.objects.get(id=sessao_pk)
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
class CreateView(MasterDetailCrud.CreateView):
form_class = OradorForm
@ -625,18 +634,87 @@ class OradorCrud(MasterDetailCrud):
return reverse('sapl.sessao:orador_list',
kwargs={'pk': self.kwargs['pk']})
class UpdateView(MasterDetailCrud.UpdateView):
class UpdateView(MasterDetailCrud.UpdateView):
form_class = OradorForm
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial = super().get_initial()
initial.update({'id_sessao': self.object.sessao_plenaria.id})
initial.update({'numero': self.object.numero_ordem})
return initial
class OradorExpedienteCrud(OradorCrud):
model = OradorExpediente
class CreateView(MasterDetailCrud.CreateView):
form_class = OradorForm
def get_initial(self):
return {'id_sessao': self.kwargs['pk']}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
sessao_pk = context['root_pk']
sessao = SessaoPlenaria.objects.get(id=sessao_pk)
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
def get_success_url(self):
return reverse('sapl.sessao:orador_list',
kwargs={'pk': self.kwargs['pk']})
class UpdateView(MasterDetailCrud.UpdateView):
form_class = OradorForm
def get_initial(self):
initial = super().get_initial()
initial.update({'id_sessao': self.object.sessao_plenaria.id})
initial.update({'numero':self.object.numero_ordem})
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
sessao_pk = context['root_pk']
sessao = SessaoPlenaria.objects.get(id=sessao_pk)
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
class DetailView(MasterDetailCrud.DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
sessao_pk = context['root_pk']
sessao = SessaoPlenaria.objects.get(id=sessao_pk)
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
class DeleteView(MasterDetailCrud.DeleteView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
sessao_pk = context['root_pk']
sessao = SessaoPlenaria.objects.get(id=sessao_pk)
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
class OradorExpedienteCrud(OradorCrud):
model = OradorExpediente
@ -647,6 +725,17 @@ class OradorExpedienteCrud(OradorCrud):
def get_initial(self):
return {'id_sessao': self.kwargs['pk']}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = context['root_pk']
sessao = SessaoPlenaria.objects.get(id=pk)
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
def get_success_url(self):
return reverse('sapl.sessao:oradorexpediente_list',
kwargs={'pk': self.kwargs['pk']})
@ -659,6 +748,40 @@ class OradorExpedienteCrud(OradorCrud):
'numero': self.object.numero_ordem}
class ListView(MasterDetailCrud.ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = context['root_pk']
sessao = SessaoPlenaria.objects.get(id=pk)
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
class DetailView(MasterDetailCrud.DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = context['root_pk']
sessao = SessaoPlenaria.objects.get(id=pk)
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
class UpdateView(MasterDetailCrud.UpdateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
pk = context['root_pk']
sessao = SessaoPlenaria.objects.get(id=pk)
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
class OradorOrdemDiaCrud(OradorCrud):
model = OradorOrdemDia
@ -676,7 +799,7 @@ class OradorOrdemDiaCrud(OradorCrud):
form_class = OradorOrdemDiaForm
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial = super().get_initial()
initial.update({'id_sessao': self.object.sessao_plenaria.id})
initial.update({'numero': self.object.numero_ordem})
@ -735,6 +858,16 @@ def sessao_legislativa_legislatura_ajax(request):
return JsonResponse({'sessao_legislativa': lista_sessoes})
def recuperar_nome_tipo_sessao(request):
try:
tipo = TipoSessaoPlenaria.objects.get(pk=request.GET['tipo'])
tipo_nome = tipo.nome
except ObjectDoesNotExist:
tipo_nome = ''
return JsonResponse({'nome_tipo': tipo_nome})
class SessaoCrud(Crud):
model = SessaoPlenaria
help_topic = 'sessao_legislativa'
@ -764,6 +897,18 @@ class SessaoCrud(Crud):
form_class = SessaoPlenariaForm
@property
def layout_key(self):
return 'SessaoSolene'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
sessao = context['object']
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
def get_initial(self):
return {'sessao_legislativa': self.object.sessao_legislativa}
@ -772,6 +917,10 @@ class SessaoCrud(Crud):
form_class = SessaoPlenariaForm
logger = logging.getLogger(__name__)
@property
def layout_key(self):
return 'SessaoSolene'
@property
def cancel_url(self):
return self.search_url
@ -804,6 +953,26 @@ class SessaoCrud(Crud):
namespace = self.model._meta.app_config.name
return reverse('%s:%s' % (namespace, 'sessaoplenaria_list'))
class DetailView(Crud.DetailView):
@property
def layout_key(self):
sessao = self.object
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
return 'SessaoSolene'
return 'SessaoPlenaria'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
sessao = context['object']
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
# self.layout_key = 'SessaoSolene'
return context
class SessaoPermissionMixin(PermissionRequiredForAppCrudMixin,
FormMixin,
@ -837,6 +1006,10 @@ class PresencaView(FormMixin, PresencaMixin, DetailView):
context = FormMixin.get_context_data(self, **kwargs)
context['title'] = '%s <small>(%s)</small>' % (
_('Presença'), self.object)
sessao = context['object']
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
@method_decorator(permission_required(
@ -921,17 +1094,23 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
cronometro_ordem = cronometro_ordem.seconds
cronometro_consideracoes = cronometro_consideracoes.seconds
sessao_pk = kwargs['pk']
sessao = SessaoPlenaria.objects.get(pk=sessao_pk)
context = TemplateView.get_context_data(self, **kwargs)
context.update({
'head_title': str(_('Painel Plenário')),
'sessao_id': kwargs['pk'],
'root_pk': kwargs['pk'],
'sessaoplenaria': SessaoPlenaria.objects.get(pk=kwargs['pk']),
'sessao_id': sessao_pk,
'root_pk': sessao_pk,
'sessaoplenaria': sessao,
'cronometro_discurso': cronometro_discurso,
'cronometro_aparte': cronometro_aparte,
'cronometro_ordem': cronometro_ordem,
'cronometro_consideracoes': cronometro_consideracoes})
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
@ -1154,6 +1333,10 @@ class MesaView(FormMixin, DetailView):
context = FormMixin.get_context_data(self, **kwargs)
context['title'] = '%s <small>(%s)</small>' % (
_('Mesa Diretora'), self.object)
sessao = context['object']
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
def get_success_url(self):
@ -1363,14 +1546,18 @@ def get_identificação_basica(sessao_plenaria):
abertura = data_inicio.strftime('%d/%m/%Y') if data_inicio else ''
data_fim = sessao_plenaria.data_fim
encerramento = data_fim.strftime('%d/%m/%Y') + ' -' if data_fim else ''
return({'basica': [
tema_solene = sessao_plenaria.tema_solene
context = {'basica': [
_('Tipo de Sessão: %(tipo)s') % {'tipo': sessao_plenaria.tipo},
_('Abertura: %(abertura)s - %(hora_inicio)s') % {
'abertura': abertura, 'hora_inicio': sessao_plenaria.hora_inicio},
_('Encerramento: %(encerramento)s %(hora_fim)s') % {
'encerramento': encerramento, 'hora_fim': sessao_plenaria.hora_fim}
'encerramento': encerramento, 'hora_fim': sessao_plenaria.hora_fim},
],
'sessaoplenaria': sessao_plenaria})
'sessaoplenaria': sessao_plenaria}
if sessao_plenaria.tipo.nome == "Solene" and tema_solene:
context.update({'tema_solene': 'Tema da Sessão Solene: %s' % tema_solene})
return context
def get_conteudo_multimidia(sessao_plenaria):
@ -1531,10 +1718,11 @@ def get_assinaturas(sessao_plenaria):
context.update(
{'texto_assinatura': 'Assinatura da Mesa Diretora da Sessão'})
context.update({'assinatura_mesa': mesa_dia})
elif config_assinatura_ata == 'P' and presidente_dia:
elif config_assinatura_ata == 'P' and presidente_dia and presidente_dia[0]:
context.update(
{'texto_assinatura': 'Assinatura do Presidente da Sessão'})
context.update({'assinatura_mesa': presidente_dia})
assinatura_presidente = [{'parlamentar': presidente_dia[0], 'cargo': "Presidente"}]
context.update({'assinatura_mesa': assinatura_presidente})
return context
@ -1824,6 +2012,11 @@ class ResumoView(DetailView):
'decimo_terceiro_ordenacao': 'oradores_explicacoes.html',
'decimo_quarto_ordenacao': 'ocorrencias_da_sessao.html'
})
sessao = context['object']
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
def get(self, request, *args, **kwargs):
@ -1848,6 +2041,10 @@ class ExpedienteView(FormMixin, DetailView):
context = FormMixin.get_context_data(self, **kwargs)
context['title'] = '%s <small>(%s)</small>' % (
_('Expediente Diversos'), self.object)
sessao = context['object']
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
@method_decorator(permission_required('sessao.add_expedientesessao'))
@ -1937,6 +2134,10 @@ class OcorrenciaSessaoView(FormMixin, DetailView):
context = FormMixin.get_context_data(self, **kwargs)
context['title'] = 'Ocorrências da Sessão <small>(%s)</small>' % (
self.object)
sessao = context['object']
tipo_sessao = sessao.tipo
if tipo_sessao.nome == "Solene":
context.update({'subnav_template_name': 'sessao/subnav-solene.yaml'})
return context
def delete(self):
@ -2662,8 +2863,7 @@ class VotacaoNominalTransparenciaDetailView(TemplateView):
template_name = 'sessao/votacao/nominal_transparencia.html'
def get_context_data(self, **kwargs):
context = super(VotacaoNominalTransparenciaDetailView,
self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
materia_votacao = self.request.GET.get('materia', None)
@ -2747,8 +2947,7 @@ class VotacaoSimbolicaTransparenciaDetailView(TemplateView):
template_name = 'sessao/votacao/simbolica_transparencia.html'
def get_context_data(self, **kwargs):
context = super(VotacaoSimbolicaTransparenciaDetailView,
self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
materia_votacao = self.request.GET.get('materia', None)
@ -3015,7 +3214,7 @@ class SessaoListView(ListView):
return SessaoPlenaria.objects.all().order_by('-data_inicio')
def get_context_data(self, **kwargs):
context = super(SessaoListView, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
@ -3179,8 +3378,7 @@ class PesquisarSessaoPlenariaView(FilterView):
logger = logging.getLogger(__name__)
def get_filterset_kwargs(self, filterset_class):
super(PesquisarSessaoPlenariaView,
self).get_filterset_kwargs(filterset_class)
super().get_filterset_kwargs(filterset_class)
kwargs = {'data': self.request.GET or None}
@ -3196,8 +3394,7 @@ class PesquisarSessaoPlenariaView(FilterView):
return kwargs
def get_context_data(self, **kwargs):
context = super(PesquisarSessaoPlenariaView,
self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['title'] = _('Pesquisar Sessão Plenária')
paginator = context['paginator']
@ -3209,7 +3406,7 @@ class PesquisarSessaoPlenariaView(FilterView):
return context
def get(self, request, *args, **kwargs):
super(PesquisarSessaoPlenariaView, self).get(request)
super().get(request)
# Se a pesquisa estiver quebrando com a paginação
# Olhe esta função abaixo
@ -3247,8 +3444,7 @@ class PesquisarPautaSessaoView(PesquisarSessaoPlenariaView):
logger.debug('Pesquisa de PautaSessao.')
def get_context_data(self, **kwargs):
context = super(PesquisarPautaSessaoView,
self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['title'] = _('Pesquisar Pauta de Sessão')
return context
@ -3269,8 +3465,7 @@ class AdicionarVariasMateriasExpediente(PermissionRequiredForAppCrudMixin,
logger = logging.getLogger(__name__)
def get_filterset_kwargs(self, filterset_class):
super(AdicionarVariasMateriasExpediente,
self).get_filterset_kwargs(filterset_class)
super().get_filterset_kwargs(filterset_class)
kwargs = {'data': self.request.GET or None}
@ -3294,8 +3489,7 @@ class AdicionarVariasMateriasExpediente(PermissionRequiredForAppCrudMixin,
return kwargs
def get_context_data(self, **kwargs):
context = super(MateriaLegislativaPesquisaView,
self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['title'] = _('Pesquisar Matéria Legislativa')
context['root_pk'] = self.kwargs['pk']
@ -3362,8 +3556,7 @@ class AdicionarVariasMateriasOrdemDia(AdicionarVariasMateriasExpediente):
logger = logging.getLogger(__name__)
def get_filterset_kwargs(self, filterset_class):
super(AdicionarVariasMateriasExpediente,
self).get_filterset_kwargs(filterset_class)
super().get_filterset_kwargs(filterset_class)
kwargs = {'data': self.request.GET or None}
@ -3581,8 +3774,7 @@ class VotacaoEmBlocoExpediente(PermissionRequiredForAppCrudMixin, ListView):
retiradapauta=None)
def get_context_data(self, **kwargs):
context = super(VotacaoEmBlocoExpediente,
self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['pk'] = self.kwargs['pk']
context['root_pk'] = self.kwargs['pk']
if not verifica_sessao_iniciada(self.request, self.kwargs['pk']):

4
sapl/settings.py

@ -41,7 +41,7 @@ ALLOWED_HOSTS = ['*']
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/?next='
SAPL_VERSION = '3.1.158'
SAPL_VERSION = '3.1.159'
if DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
@ -239,7 +239,7 @@ DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='')
SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='')
EMAIL_RUNNING = None
MAX_DOC_UPLOAD_SIZE = 60 * 1024 * 1024 # 60MB
MAX_DOC_UPLOAD_SIZE = 80 * 1024 * 1024 # 80MB
MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB
# Internationalization

39
sapl/static/sapl/css/relatorio.css

@ -1,5 +1,5 @@
@page{
margin-top: 4.5cm;
margin-top: 5.2cm;
size: A4 portrait;
}
@ -54,3 +54,40 @@ fieldset {
margin:5px;
padding:0px;
}
table {
max-width: 520px;
}
table.grayTable {
border: 1px solid #6e6e6e;
width: 100%;
text-align: left;
border-collapse: collapse;
}
table.grayTable td, table.grayTable th {
border: 1px solid #000000;
}
table.grayTable tbody td {
font-size: 10px;
max-width: 80px;
overflow-wrap: break-word;
word-wrap: break-word;
text-align: justify;
}
table.grayTable tr:nth-child(even) {
background: #dddddd;
}
table.grayTable thead {
background: #BBBBBB;
border-bottom: 2px solid #000000;
}
table.grayTable thead th {
font-size:10px;
color: rgb(0, 0, 0);
border-left: 1px solid #000000;
}
table.grayTable thead th:first-child {
border-left: none;
}

2
sapl/templates/base.html

@ -179,7 +179,7 @@
<small>
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small>
<span>Release: 3.1.158</span>
<span>Release: 3.1.159</span>
</p>
</div>
<div class="col-md-4">

3
sapl/templates/base/RelatorioAtas_filter.html

@ -1,6 +1,7 @@
{% extends "crud/list.html" %}
{% load i18n %}
{% load crispy_forms_tags staticfiles %}
{% load webpack_static from webpack_loader %}
{% block base_content %}
{% if not filter_url %}
@ -26,7 +27,7 @@
<tr>
<td>{{sessao}}</td>
<td><a href="{{ sessao.upload_ata.url }}">
<img src="{% static 'img/file.png' %}">
<img src="{% webpack_static 'img/file.png' %}">
</a></td>
</tr>
{% endfor %}

2
sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html

@ -14,7 +14,7 @@
<b>PARÂMETROS DE PESQUISA:<br /></b>
&emsp;Período: {{ data_tramitacao }} <br />
&emsp;Tipo de matéria: {{ tipo }}<br />
&emsp;Status atual: {{ tramitacao__status }}<br />
&emsp;Status de tramitação: {{ tramitacao__status }}<br />
&emsp;Local de origem: {{ tramitacao__unidade_tramitacao_local }}<br />
&emsp;Local de destino: {{ tramitacao__unidade_tramitacao_destino }}<br /><br /><br />
{% if object_list %}

67
sapl/templates/base/RelatorioPresencaSessao_filter.html

@ -20,34 +20,93 @@
</div>
<br /><br /><br /><br />
<b>PERÍODO: {{periodo}}</b><br />
<b>TOTAIS NO PERÍODO - SESSÕES: {{total_sessao}} - ORDENS DO DIA: {{total_ordemdia}}</b>
<b>Legislatura: {{legislatura}}</b><br />
<b>Sessão Legislativa: {{sessao_legislativa}}</b><br />
<b>Tipo Sessão Plenária: {{tipo}}<br /> </b>
<b>TOTAIS NO PERÍODO - SESSÕES: {{total_sessao}} - ORDENS DO DIA: {{total_ordemdia}}</b><br />
<b>Exibir presença das Ordens do Dia: {% if exibir_ordem %} Sim {% else %} Não {% endif %}</b><br />
<table class="table table-bordered table-hover presenca_table">
<thead class="thead-default" align="center">
<tr class="active">
<th rowspan="2">Nome Parlamentar / Partido</th>
<th rowspan="2">Titular</th>
<th rowspan="2">Titular?</th>
<th rowspan="2">Ativo?</th>
<th colspan="2">Sessão</th>
<th colspan="2">Ordem do Dia</th>
{% if exibir_ordem %} <th colspan="2">Ordem do Dia</th> {% endif %}
</tr>
<tr class="active">
<th>(Qtd)</th>
<th>( % )</th>
{% if exibir_ordem %}
<th>(Qtd)</th>
<th>( % )</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for p in parlamentares %}
<tr>
<td><b>{{p.parlamentar}}</b> / {{p.parlamentar|filiacao_intervalo_filter:date_range|default:"Sem Partido"}}</td>
<td>{%if p.titular %} Sim {% else %} Não {% endif %}</td>
<td>{% if p.titular %} Sim {% else %} Não {% endif %}</td>
<td>{% if p.parlamentar.ativo %} Sim {% else %} Não {% endif %}</td>
<td>{{p.sessao_count}}</td>
<td>{{p.sessao_porc}}</td>
{% if exibir_ordem %}
<td>{{p.ordemdia_count}}</td>
<td>{{p.ordemdia_porc}}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endblock base_content %}
{% block extra_js %}
<script type="text/javascript">
$(document).ready(function(){
var original_options = [];
$.each($("#id_sessao_legislativa").children(), function(idx, obj) {
original_options.push($(obj));
});
$('#id_legislatura').change(function(event) {
let legislatura = $("#id_legislatura").val();
var json_data = {
legislatura : legislatura,
}
if(legislatura){
$.getJSON("{% url 'sapl.parlamentares:get_sessoes_legislatura' %}", json_data, function(data){
if (data) {
$("#id_sessao_legislativa").children().remove();
if (data.sessoes_legislativas.length > 1) {
sel = $("#id_sessao_legislativa").append("<option>---------</option>");
sel.children().last().attr("value", "");
}
$.each(data.sessoes_legislativas, function(idx, obj) {
$("#id_sessao_legislativa")
.append($("<option></option>")
.attr("value", obj[0])
.text(obj[1]));
});
}
});
}else{
$("#id_sessao_legislativa").children().remove();
$.each(original_options, function(idx, obj) {
$("#id_sessao_legislativa").append(obj);
});
$("#id_sessao_legislativa").children().first().attr('selected', true);
}
})
});
</script>
{% endblock %}

35
sapl/templates/base/anexadas_ciclicas.html

@ -0,0 +1,35 @@
{% extends "base.html" %}
{% load common_tags %}
{% block base_content %}
<fieldset>
<h1>Lista de Matérias Anexadas Cíclicas</h1>
<br/>
{% if not anexadas_ciclicas %}
<p>{{ NO_ENTRIES_MSG }}</p>
{% else %}
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Matéria Fim do Ciclo</th>
<th>Matéria Início do Ciclo</th>
</tr>
</thead>
<tbody>
{% for data, fim, inicio in anexadas_ciclicas %}
<tr>
<td>{{ data }}</td>
<td>
<a href="{% url 'sapl.materia:materialegislativa_detail' fim.pk %}">{{ fim }}</a>
</td>
<td>
<a href="{% url 'sapl.materia:materialegislativa_detail' inicio.pk %}">{{ inicio }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

36
sapl/templates/base/anexados_ciclicos.html

@ -0,0 +1,36 @@
{% extends "base.html" %}
{% load common_tags %}
{% block base_content %}
<fieldset>
<h1>Lista de Documentos Anexados Cíclicos</h1>
<br/>
{% if not anexados_ciclicos %}
<p>{{ NO_ENTRIES_MSG }}</p>
{% else %}
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Data Anexação</th>
<th>Documento Fim do Ciclo</th>
<th>Documento Início do Ciclo</th>
</tr>
</thead>
<tbody>
{% for data, fim, inicio in anexados_ciclicos %}
<tr>
<td>{{ data }}</td>
<td>
<a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' fim.pk %}">{{ fim }}</a>
</td>
<td>
<a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' inicio.pk %}">{{ inicio }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

2
sapl/templates/base/autores_duplicados.html

@ -25,6 +25,6 @@
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

4
sapl/templates/base/bancada_comissao_autor_externo.html

@ -23,7 +23,7 @@
<a href="{% url 'sapl_index' %}{{ link }}/{{ objeto.pk }}">{{ objeto }}</a>
</td>
<td>
<a href="{% url 'sapl_index' %}sistema/autor/{{ autor.pk }}">{{ autor.nome }}</a>
<a href="{% url 'sapl.base:autor_detail' autor.pk %}">{{ autor.nome }}</a>
</td>
</tr>
{% endfor %}
@ -31,6 +31,6 @@
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

4
sapl/templates/base/filiacoes_sem_data_filiacao.html

@ -18,7 +18,7 @@
{% for filiacao in filiacoes_sem_data_filiacao %}
<tr>
<td>
<a href="{% url 'sapl_index' %}parlamentar/filiacao/{{ filiacao.pk }}">{{ filiacao.parlamentar }}</a>
<a href="{% url 'sapl.parlamentares:filiacao_detail' filiacao.pk %}">{{ filiacao.parlamentar }}</a>
</td>
<td>{{ filiacao.partido }}</td>
</tr>
@ -27,6 +27,6 @@
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

2
sapl/templates/base/legislatura_infindavel.html

@ -27,6 +27,6 @@
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

2
sapl/templates/base/lista_inconsistencias.html

@ -17,6 +17,6 @@
</tbody>
</table>
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

4
sapl/templates/base/mandato_sem_data_inicio.html

@ -18,7 +18,7 @@
{% for mandato in mandato_sem_data_inicio %}
<tr>
<td>
<a href="{% url 'sapl_index' %}parlamentar/mandato/{{ mandato.pk }}">{{ mandato.parlamentar }}</a>
<a href="{% url 'sapl.parlamentares:mandato_detail' mandato.pk %}">{{ mandato.parlamentar }}</a>
</td>
<td>{{ mandato.legislatura }}</td>
</tr>
@ -27,6 +27,6 @@
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

4
sapl/templates/base/materias_protocolo_inexistente.html

@ -19,7 +19,7 @@
{% for materia, ano, numero_protocolo in materias_protocolo_inexistente %}
<tr>
<td>
<a href="{% url 'sapl_index' %}materia/{{ materia.pk }}">{{ materia }}</a>
<a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}">{{ materia }}</a>
</td>
<td>{{ ano }}</td>
<td>{{ numero_protocolo }}</td>
@ -29,6 +29,6 @@
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

6
sapl/templates/base/parlamentares_duplicados.html

@ -15,10 +15,10 @@
</tr>
</thead>
<tbody>
{% for parlamentar, quantidade in parlamentares_duplicados %}
{% for quantidade, parlamentar in parlamentares_duplicados %}
<tr>
<td>
<a href="{% url 'sapl.parlamentares:pesquisar_parlamentar' %}?nome_parlamentar={{parlamentar}}">{{ parlamentar }}</a>
<a href="{% url 'sapl.parlamentares:pesquisar_parlamentar' %}?nome_parlamentar={{ parlamentar }}">{{ parlamentar }}</a>
</td>
<td>{{ quantidade }}</td>
</tr>
@ -27,6 +27,6 @@
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

8
sapl/templates/base/parlamentares_filiacoes_intersecao.html

@ -19,16 +19,16 @@
{% for parlamentar, filiacao_a , filiacao_b in parlamentares_filiacoes_intersecao %}
<tr>
<td>
<a href="{% url 'sapl_index' %}parlamentar/{{ parlamentar.pk }}/filiacao">{{ parlamentar }}</a>
<a href="{% url 'sapl.parlamentares:filiacao_list' parlamentar.pk %}">{{ parlamentar }}</a>
</td>
<td>{{filiacao_a.data|date:"d/m/Y"}} - {{filiacao_a.data_desfiliacao|date:"d/m/Y"}}</td>
<td>{{filiacao_b.data|date:"d/m/Y"}} - {{filiacao_b.data_desfiliacao|date:"d/m/Y"}}</td>
<td>{{ filiacao_a.data|date:"d/m/Y" }} - {{ filiacao_a.data_desfiliacao|date:"d/m/Y" }}</td>
<td>{{ filiacao_b.data|date:"d/m/Y" }} - {{ filiacao_b.data_desfiliacao|date:"d/m/Y" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

8
sapl/templates/base/parlamentares_mandatos_intersecao.html

@ -19,16 +19,16 @@
{% for parlamentar, mandato_a, mandato_b in parlamentares_mandatos_intersecao %}
<tr>
<td>
<a href="{% url 'sapl_index' %}parlamentar/{{ parlamentar.pk }}/mandato">{{ parlamentar }}</a>
<a href="{% url 'sapl.parlamentares:mandato_list' parlamentar.pk %}">{{ parlamentar }}</a>
</td>
<td>{{ mandato_a.legislatura}}</br>{{mandato_a.data_inicio_mandato|date:"d/m/Y"}} - {{mandato_a.data_fim_mandato|date:"d/m/Y"}}</td>
<td>{{ mandato_b.legislatura }}</br>{{mandato_b.data_inicio_mandato|date:"d/m/Y"}} - {{mandato_b.data_fim_mandato|date:"d/m/Y"}}</td>
<td>{{ mandato_a.legislatura }}</br>{{ mandato_a.data_inicio_mandato|date:"d/m/Y" }} - {{ mandato_a.data_fim_mandato|date:"d/m/Y" }}</td>
<td>{{ mandato_b.legislatura }}</br>{{ mandato_b.data_inicio_mandato|date:"d/m/Y" }} - {{ mandato_b.data_fim_mandato|date:"d/m/Y" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

2
sapl/templates/base/protocolos_com_materias.html

@ -25,6 +25,6 @@
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

4
sapl/templates/base/protocolos_duplicados.html

@ -18,7 +18,7 @@
{% for protocolo, quantidade in protocolos_duplicados %}
<tr>
<td>
<a href="{% url 'sapl.protocoloadm:protocolo' %}?numero={{protocolo.numero}}&ano={{protocolo.ano}}">{{ protocolo }}</a>
<a href="{% url 'sapl.protocoloadm:protocolo' %}?numero={{ protocolo.numero }}&ano={{ protocolo.ano }}">{{ protocolo }}</a>
</td>
<td>{{ quantidade }}</td>
</tr>
@ -27,6 +27,6 @@
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% include 'paginacao.html' %}
<br/>
{% endblock base_content %}

4
sapl/templates/base/relatorios_list.html

@ -37,8 +37,8 @@
<td> Histórico de tramitações por período e local informados. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:data_fim_prazo_tramitacoes' %}">Tramitações de Matérias por fim de prazo</a></td>
<td> Tramitações com fim de prazo no intervalo informado. </td>
<td><a href="{% url 'sapl.base:data_fim_prazo_tramitacoes' %}">Tramitações de Matérias</a></td>
<td> Tramitações de matéria com status informado no intervalo. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:reuniao' %}">Reunião de Comissão</a></td>

2
sapl/templates/comissoes/reuniao_detail.html

@ -1,4 +1,4 @@
{% extends "crud/detail.html" %}
{% extends "crud/detail_detail.html" %}
{% load i18n %}
{% block detail_content %}

17
sapl/templates/crud/detail.html

@ -72,13 +72,21 @@
<p>Este navegador não suporta o elemento áudio.</p>
</audio>
</div>
{% elif column.text|video_url %}
{% elif column.text|is_video_url %}
<div class="form-control-static">
<video width="320" height="120" controls>
<video width="420" height="230" 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|youtube_url %}
<iframe id="ytplayer" type="text/html" width="420" height="230"
src="http://www.youtube.com/embed/{{ column.text|youtube_id }}"
frameborder="0"></iframe>
{% elif column.text|facebook_url %}
<div class="fb-video" data-href="{{ column.text|safe }}"
data-width="420" data-show-text="false">
</div>
{% elif column.text|url %}
<div class="form-control-static"><a href="{{ column.text|safe }}"> {{ column.text|safe|default:"" }} </a></div>
{% else %}
@ -131,4 +139,7 @@
{% endblock table_content %}
{% endblock base_content %}
{% block extra_js %}{% endblock %}
{% block extra_js %}
<div id="fb-root"></div>
<script async defer crossorigin="anonymous" src="https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v3.3"></script>
{% endblock %}

4
sapl/templates/floppyforms/image_thumbnail.html

@ -1,5 +1,5 @@
{% load i18n staticfiles thumbnail %}
{% load webpack_static from webpack_loader %}
<div id="div_{{ field.auto_id }}"
class="control-group
{% if form_show_errors %}
@ -43,7 +43,7 @@
{% else %}
<div class="row">
<div class="col-md-12">
<img src="{% static 'img/perfil.png' %}""
<img src="{% webpack_static 'img/perfil.png' %}"
class="img-thumbnail"/>
</div>
</div>

7
sapl/templates/materia/despachoinicial_multicreate_form.html

@ -0,0 +1,7 @@
{% extends "crud/form.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load common_tags %}
{% block extra_js %}
{% endblock %}

10
sapl/templates/materia/em_lote/acessorio.html

@ -22,7 +22,7 @@
<div class="col-md-4">
<div class="form-group">
<label>Tipo*</label>
<select name="tipo" class="form-control" required="True">
<select name="tipo" class="form-control" required>
{% for t in tipos_docs %} <option>{{t}}</option> {% endfor %}
</select>
</div>
@ -31,14 +31,14 @@
<div class="col-md-4">
<div class="form-group">
<label>Nome*</label>
<input type="text" name="nome" class="form-control" required="True">
<input type="text" name="nome" class="form-control" required>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Data*</label>
<input type="text" name="data" class="form-control dateinput" required="True">
<input type="text" name="data" class="form-control dateinput" required>
</div>
</div>
</div>
@ -47,7 +47,7 @@
<div class="col-md-12">
<div class="form-group">
<label>Autor:</label>
<input type="text" name="autor" class="form-control" required="False">
<input type="text" name="autor" class="form-control">
</div>
</div>
</div>
@ -65,7 +65,7 @@
<div class="col-md-12">
<div class="form-group">
<label>Texto Integral*</label>
<input type="file" name="arquivo" required="True">
<input type="file" name="arquivo" required>
</div>
</div>
</div>

118
sapl/templates/materia/em_lote/tramitacao.html

@ -1,134 +1,28 @@
{% extends "crud/detail.html" %}
{% load i18n crispy_forms_tags %}
{% block actions %}{% endblock %}
{% block detail_content %}
{% if not show_results %}
{% crispy filter.form %}
{% endif %}
{% if show_results %}
{% else %}
{% if object_list|length > 0 %}
{% if object_list|length == 1 %}
<h3 style="text-align: right;">{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 matéria.'%}</h3>
{% else %}
<h3 style="text-align: right;">Foram encontradas {{object_list|length}} matérias.</h3>
{% endif %}
<form method="POST">
{% csrf_token %}
<fieldset>
<legend>1. Detalhes da tramitação:</legend>
<div class="row">
<div class="col-md-4">
<label>Data da Tramitação*</label>
<input type="text" name="data_tramitacao" class="form-control dateinput" required="True">
</div>
<div class="col-md-4">
<label>Data Encaminhamento</label>
<input type="text" name="data_encaminhamento" class="form-control dateinput">
</div>
<div class="col-md-4">
<label>Data Fim do Prazo</label>
<input type="text" name="data_fim_prazo" class="form-control dateinput">
</div>
</div>
<div class="row">
<div class="col-md-6">
<label>Unidade Local*</label>
<select id="id_unidade_tramitacao_local" name="unidade_tramitacao_local" class="form-control" required="True">
{% if unidade_local|length > 1 %}<option></option>{% endif %}
{% for u in unidade_local %} <option value="{{u.id}}">{{u}}</option> {% endfor %}
</select>
</div>
<div class="col-md-6">
<label>Unidade Destino*</label>
<select name="unidade_tramitacao_destino" class="form-control" required="True">
<option></option>
{% for u in unidade_destino %} <option value="{{u.id}}">{{u}}</option> {% endfor %}
</select>
</div>
</div>
<div class="row">
<div class="col-md-4">
<label>Status*</label>
<select name="status" class="form-control" required="True">
<option></option>
{% for s in status_tramitacao %} <option value="{{s.id}}">{{s}}</option> {% endfor %}
</select>
</div>
<div class="col-md-4">
<label>Urgente?*</label>
<select name="urgente" class="form-control" required="True">
<option></option>
{% for u in urgente_tramitacao %} <option value="{{u|first}}">{{u|last}}</option> {% endfor %}
</select>
</div>
<div class="col-md-4">
<label>Turno</label>
<select name="turno" class="form-control">
<option></option>
{% for t in turnos_tramitacao %} <option value="{{t|first}}">{{t|last}}</option> {% endfor %}
</select>
</div>
</div>
<div class="row">
<div class="col-md-12">
<label>Texto da Ação*</label>
<textarea name="texto" class="textarea form-control" cols="40" rows="10" required="True"></textarea>
</div>
</div>
</fieldset>
<br /><br /><br />
<fieldset>
<legend>2. Selecione as matérias para tramitação:</legend>
<table class="table table-striped table-hover">
<div class="controls">
<div class="checkbox">
<label for="id_check_all">
<input type="checkbox" id="id_check_all" onchange="checkAll(this)" /> Marcar/Desmarcar Todos
</label>
</div>
</div>
<thead>
<tr><th>Matéria</th></tr>
</thead>
<tbody>
{% for materia in object_list %}
<tr>
<td>
<input type="checkbox" name="materia_id" value="{{materia.id}}" {% if check %} checked {% endif %}/>
<a href="{% url 'sapl.materia:materialegislativa_detail' materia.id %}">
{{materia.tipo.sigla}} {{materia.tipo.descricao}} {{materia.numero}}/{{materia.ano}}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</fieldset>
<input type="submit" value="Salvar" class="btn btn-primary"S>
</form>
{% crispy form %}
{% else %}
<tr><td><h3 style="text-align: right;">Nenhuma matéria encontrada.</h3></td></tr>
{% endif %}
{% endif %}
{% endif%}
{% endblock detail_content %}
{% block extra_js %}
<script language="JavaScript">
function checkAll(elem) {
let checkboxes = document.getElementsByName('materia_id');
let checkboxes = document.getElementsByName('materias');
for (let i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].type == 'checkbox')
checkboxes[i].checked = elem.checked;

46
sapl/templates/materia/materialegislativa_form.html

@ -31,6 +31,40 @@
return 0;
}
var modal_estilos = 'display: block;'
+'width: 85%; max-width: 600px;'
+'background: #fff; padding: 15px;'
+'border-radius: 5px;'
+'-webkit-box-shadow: 0px 6px 14px -2px rgba(0,0,0,0.75);'
+'-moz-box-shadow: 0px 6px 14px -2px rgba(0,0,0,0.75);'
+'box-shadow: 0px 6px 14px -2px rgba(0,0,0,0.75);'
+'position: fixed;'
+'top: 50%; left: 50%;'
+'transform: translate(-50%,-50%);'
+'z-index: 99999999; text-align: center';
var fundo_modal_estilos = 'top: 0; right: 0;'
+'bottom: 0; left: 0; position: fixed;'
+'background-color: rgba(0, 0, 0, 0.6); z-index: 99999999;'
+'display: none;';
var meu_modal = '<div id="fundo_modal" style="'+fundo_modal_estilos+'">'
+'<div id="meu_modal" style="'+modal_estilos+'">'
+'<h2>Atenção! Ano de apresentação e ano da matéria são diferentes.</h2><br />'
+'<button id="close_model_btn" type="button" class="btn btn-warning" data-dismiss="modal">'
+'Compreendo e quero continuar</button>'
+'</div></div>';
function verifica_ano(){
let ano = $("select#id_ano.select").val();
let data_apresentacao = $("input#id_data_apresentacao.dateinput").val();
let ano_apresentacao = data_apresentacao.substr(data_apresentacao.length - 4);
if(ano && ano_apresentacao && ano_apresentacao != ano){
$('#fundo_modal').fadeIn();
}
}
$(document).ready(function() {
$("#id_tipo_autor").change(function() {
var tipo_selecionado = $("#id_tipo_autor").val();
@ -59,6 +93,18 @@
}
});
$("#id_tipo_autor").trigger('change');
$("body").append(meu_modal);
$("#fundo_modal, #close_model_btn").click(function(){ $("#fundo_modal").hide(); });
$("#meu_modal").click(function(e){ e.stopPropagation(); });
$("select#id_ano.select.form-control").change(function(){
verifica_ano();
});
$("input#id_data_apresentacao.dateinput.form-control").change(function(){
verifica_ano();
});
});
</script>

2
sapl/templates/navbar.yaml

@ -35,7 +35,7 @@
url: sapl.protocoloadm:pesq_doc_adm
- title: {% trans 'Tramitação em Lote' %}
url: sapl.protocoloadm:primeira_tramitacao_em_lote_docadm
check_permission: sapl.protocoloadm:add_tramitacaoadministrativo
check_permission: protocoloadm.add_tramitacaoadministrativo
- title: {% trans 'Atividade Legislativa' %}
children:

32
sapl/templates/norma/normajuridica_detail.html

@ -87,6 +87,38 @@
</div>
</div>
<br><br>
{% if user.is_superuser %}
<div class="row">
{% if object.user %}
<div class="col-sm-6">
<div id="div_id_user" class="form-group">
<p class="control-label">Usuário</p>
<div class="controls">
<div class="form-control-static">
<div class="dont-break-out">
<a href="{% url 'sapl.base:user_edit' object.user.pk %}">{{object.user}}</a>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% if object.ip %}
<div class="col-sm-6">
<div id="div_ip_user" class="form-group">
<p class="control-label">IP</p>
<div class="controls">
<div class="form-control-static">
<div class="dont-break-out">{{object.ip}}</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
{% if object.texto_articulado.exists and object.texto_articulado.first.has_view_permission %}
<hr />
<div class="row">

48
sapl/templates/norma/normajuridica_form.html

@ -57,6 +57,54 @@
numeroField.val(numero.replace(/^0+/, ''));
}
});
var modal_estilos = 'display: block;'
+'width: 85%; max-width: 600px;'
+'background: #fff; padding: 15px;'
+'border-radius: 5px;'
+'-webkit-box-shadow: 0px 6px 14px -2px rgba(0,0,0,0.75);'
+'-moz-box-shadow: 0px 6px 14px -2px rgba(0,0,0,0.75);'
+'box-shadow: 0px 6px 14px -2px rgba(0,0,0,0.75);'
+'position: fixed;'
+'top: 50%; left: 50%;'
+'transform: translate(-50%,-50%);'
+'z-index: 99999999; text-align: center';
var fundo_modal_estilos = 'top: 0; right: 0;'
+'bottom: 0; left: 0; position: fixed;'
+'background-color: rgba(0, 0, 0, 0.6); z-index: 99999999;'
+'display: none;';
var meu_modal = '<div id="fundo_modal" style="'+fundo_modal_estilos+'">'
+'<div id="meu_modal" style="'+modal_estilos+'">'
+'<h2>Atenção! Ano de apresentação e ano da norma são diferentes.</h2><br />'
+'<button id="close_model_btn" type="button" class="btn btn-warning" data-dismiss="modal">'
+'Compreendo e quero continuar</button>'
+'</div></div>';
function verifica_ano(){
let ano = $("select#id_ano.select").val();
let data_apresentacao = $("input#id_data.dateinput").val();
let ano_apresentacao = data_apresentacao.substr(data_apresentacao.length - 4);
if(ano && ano_apresentacao && ano_apresentacao != ano){
$('#fundo_modal').fadeIn();
}
}
$(document).ready(function() {
$("body").append(meu_modal);
$("#fundo_modal, #close_model_btn").click(function(){ $("#fundo_modal").hide(); });
$("#meu_modal").click(function(e){ e.stopPropagation(); });
$("select#id_ano.select.form-control").blur(function(){
verifica_ano();
});
$("input#id_data.dateinput.form-control").blur(function(){
verifica_ano();
});
});
</script>
{% endblock %}

27
sapl/templates/painel/index.html

@ -90,17 +90,22 @@
</div>
</div>
<div class="col-md-6 text-center painel">
<div class="col-md-6 text-center painel" id="resultado_votacao_div">
<h2 class="text-subtitle">Resultado</h2>
<span id="votacao" class="text-value"></span>
<h2><span id="resultado_votacao" lass="text-title"></span>
</div>
<div class="col-md-6 text-center painel">
<h2 class="text-subtitle">Matéria em Votação</h2>
<div class="col-md-6 text-center painel" id="obs_materia_div">
<h2 class="text-subtitle" id="mat_em_votacao">Matéria em Votação</h2>
<span id="materia_legislativa_texto" class="text-value"></span>
<span id="observacao_materia" class="text-value"></span>
</div>
<div class="col-md-6 text-center painel" id="tema_solene_div" style="display: none">
<h2 class="text-subtitle">Tema da Sessão Solene</h2>
<span id="sessao_solene_tema" class="text-value"></span>
</div>
</div>
</div>
</div>
@ -234,6 +239,7 @@
$("#sessao_plenaria").text(data["sessao_plenaria"])
$("#sessao_plenaria_data").text("Data Início: " + data["sessao_plenaria_data"])
$("#sessao_plenaria_hora_inicio").text("Hora Início: " + data["sessao_plenaria_hora_inicio"])
$("#sessao_solene_tema").text(data["tema_solene"])
if (data["status_painel"] == false) {
$("#message").text("PAINEL ENCONTRA-SE FECHADO");
}
@ -241,6 +247,12 @@
$("#message").text("");
}
if (data["sessao_solene"]){
$("#resultado_votacao_div").hide();
$("#obs_materia_div").hide();
$('#tema_solene_div').show();
}
if (data["brasao"] != null)
$("#logo-painel").attr("src", data["brasao"]);
@ -381,7 +393,11 @@
audioAlertFinish.play();
}
if (data['materia_legislativa_texto']){
if(data['sessao_finalizada']){
$("#obs_materia_div").hide();
$("#resultado_votacao_div").hide();
}
else if (data['materia_legislativa_texto']){
if (data["status_painel"] == true){
$("#materia_legislativa_texto").text(data["materia_legislativa_texto"]);
}
@ -412,13 +428,16 @@
var resultado_votacao_upper = $("#resultado_votacao").text().toUpperCase();
if (resultado_votacao_upper.search("APROV") != -1){
$("#resultado_votacao").css("color", "green");
$("#mat_em_votacao").text("Matéria Votada");
}
if (resultado_votacao_upper.search("REJEIT") != -1){
$("#resultado_votacao").css("color", "red");
$("#mat_em_votacao").text("Matéria Votada");
}
}
else{
$("#resultado_votacao").text('');
$("#mat_em_votacao").text("Matéria em Votação");
}
},
error: function(err) {

36
sapl/templates/painel/voto_nominal.html

@ -90,25 +90,23 @@
<br /><br />
<form method='POST'>
{% csrf_token %}
<div class="row container-detail clearfix" style="text-align:center">
<div class="row">
<div class="row" align="center">
<div class="col-md-12" id="votos">
<div id="votosim"><input type="submit" class="btn btn-lg btn-success" id="voto" name="voto" type="submit" value="Sim" /></div>
&nbsp;
<div id="votonao"><input type="submit" class="btn btn-lg btn-danger" id="voto" name="voto" type="submit" value="Não"/></div>
&nbsp;
<div id="votoabstencao"><input type="submit" class="btn btn-lg btn-secondary" id="voto" name="voto" type="submit" value="Abstenção"/></div>
</div>
</div>
</br>
</br>
</br>
</br>
</br>
<div id='voltar'><center><button type="button" class="btn btn-lg btn-secondary" onclick="javascript:window.close()">Voltar</button></center></div>
</div>
</div>
<center><table style="width:30%">
<tr>
<td style="text-align:center"><button type="button" class="btn btn-lg btn-primary" onclick="javascript:window.location.reload(true)">Atualizar</button></td>
<td style="text-align:center"><button type="button" class="btn btn-lg btn-secondary" onclick="javascript:window.close()">Sair</button></td>
</tr>
</table></center>
</form>
</div>
{% else %}
@ -141,9 +139,11 @@
$(window).on('beforeunload', function () {
$("input[type=submit], input[type=button]").prop("disabled", "disabled");
});
$( "#votosim" ).mouseleave(function(){document.location.reload(true);});
$( "#votonao" ).mouseleave(function(){document.location.reload(true);});
$( "#votoabstencao" ).mouseleave(function(){document.location.reload(true);});
//TODO: Este código é necessário?
// $( "#votosim" ).mouseleave(function(){document.location.reload(true);});
// $( "#votonao" ).mouseleave(function(){document.location.reload(true);});
// $( "#votoabstencao" ).mouseleave(function(){document.location.reload(true);});
$(document).on('keyup', (e) => {
var tecla_press = e.keyCode;
@ -160,17 +160,15 @@
case 86: //86 = valor da tecla V
document.querySelectorAll("#voltar button")[0].click();
break;
};
});
$(document).ready(
function(){
function checkTime(i) {
if (i<10) {i = "0" + i}; // add zero in front of numbers < 10
if (i<10)
i = "0" + i; // add zero in front of numbers < 10
return i;
}
function startTime() {
var today=new Date();
var h=today.getHours();
@ -183,6 +181,8 @@
startTime()
}, 500);
}
$(document).ready(function(){
startTime();
setTimeout(function() {

8
sapl/templates/parlamentares/materias.html

@ -11,7 +11,7 @@
Matérias <small>({{nome_parlamentar}})</small>
</h1>
</div>
<center><h2>Primeiro Autor</h2></center>
<h2 style="text-align: center">Primeiro Autor</h2>
<br/>
<table class="table table-striped table-bordered">
{% for autoria in autoria.0 %}
@ -20,7 +20,7 @@
<tr>
<td>{{ materias.1 }}&nbsp;</td>
<td>
<a href="{% url 'sapl.materia:pesquisar_materia'%}?tipo={{materias.0}}&ano={{autoria.0}}&autoria__autor={{autor_pk}}&autoria__primeiro_autor=1">
<a href="{% url 'sapl.materia:pesquisar_materia'%}?tipo={{materias.0}}&ano={{autoria.0}}&autoria__autor={{autor_pk}}&autoria__primeiro_autor=True">
{{ materias.2}}
</a>
</td>
@ -32,7 +32,7 @@
<h2>Total: {{ autoria.1 }}</h2><br/>
<center><h2>Co-Autor</h2></center>
<h2 style="text-align: center">Co-Autor</h2>
<br/>
<table class="table table-striped table-bordered">
{% for coautoria in coautoria.0 %}
@ -41,7 +41,7 @@
<tr>
<td>{{ materias.1 }}&nbsp;</td>
<td>
<a href="{% url 'sapl.materia:pesquisar_materia'%}?tipo={{materias.0}}&ano={{coautoria.0}}&autoria__autor={{autor_pk}}&autoria__primeiro_autor=0">
<a href="{% url 'sapl.materia:pesquisar_materia'%}?tipo={{materias.0}}&ano={{coautoria.0}}&autoria__autor={{autor_pk}}&autoria__primeiro_autor=False">
{{ materias.2}}
</a>
</td>

1
sapl/templates/protocoloadm/protocolo_list.html

@ -2,7 +2,6 @@
{% load i18n %}
{% load tz %}
{% load crispy_forms_tags %}
{% load static %}
{% load webpack_static from webpack_loader %}
{% block detail_content %}

2
sapl/templates/protocoloadm/protocolo_mostrar.html

@ -22,7 +22,7 @@
{% endif %} <!-- TODO: convert if-else to custom tag -->
<strong>Natureza do Processo: </strong>{% if protocolo.tipo_processo == 0 %} Administrativo {% elif protocolo.tipo_processo == 1 %} Legislativo {% endif %}</br>
<strong>Número de Páginas: </strong> {{ protocolo.numero_paginas }} </br>
<strong>Número de Páginas: </strong> {{ protocolo.numero_paginas|default_if_none:"Não informado" }}</br>
<strong>Observação: </strong>{{ protocolo.observacao|default:"Não informado" }}</br>
<strong>Anulado: {% if protocolo.anulado %} <font color="red"> Sim {% else %} <font color="green"> Não {% endif %} </font></strong>
<br /><br />

8
sapl/templates/relatorios/header_ata.html

@ -15,17 +15,21 @@
<section id="informations">
<dl>
<dt class="image-header">
<img src="{% if logotipo %}{{ MEDIA_URL }}{{ logotipo }}{% else %}{% webpack_static 'img/logo.png' %}{% endif %}">
<img style="max-height:2cm;max-width:2cm" src="{% if logotipo %}{{ MEDIA_URL }}{{ logotipo }}{% else %}{% webpack_static 'img/logo.png' %}{% endif %}">
</dt>
<dd class="title">
<ul>
<li style="margin-top:10px"><h2>{{casa.nome}}</h2></li>
<li style="margin-top:10px;max-height:2cm;"><h2>{{casa.nome}}</h2></li>
<li><h3>Sistema de Apoio ao Processo Legislativo</h3></li>
</ul>
</dd>
</dl>
</section>
{% if info %}
<p><b>{{info}}</b></p>
{% else %}
<p></p>
{% endif %}
</body>
</html>

2
sapl/templates/relatorios/relatorio_ata.html

@ -33,7 +33,7 @@
}
}
</style>
<link rel="stylesheet" href="{% static '/sapl/css/relatorio.css'%}">
<link rel="stylesheet" href="{% static 'sapl/css/relatorio.css'%}">
</head>
<body>

5
sapl/templates/relatorios/relatorio_doc_administrativos.html

@ -1,12 +1,11 @@
{% load i18n %}
{% load common_tags %}
{% load static %}
{% load crispy_forms_tags staticfiles %}
<head>
<style>
@page{
margin-top: 4.5cm;
margin-top: 5cm;
size: A4 portrait;
@bottom-right {

183
sapl/templates/relatorios/relatorio_sessao_plenaria.html

@ -0,0 +1,183 @@
{% load static %}
<!DOCTYPE html>
<meta charset="utf-8">
</meta>
<html lang="pt-br">
<head>
<style>
@page{
margin-top: 5cm;
size: A4 portrait;
@bottom-right {
content: "Página" counter(page);
height: 3cm;
font-size: 8pt;
}
@bottom-center {
border-top: 1px solid black;
font-size: 8pt;
height: 1cm;
content: "{{rodape|safe}}";
font-style:italic;
}
@bottom-left {
content: "{{data}}";
height: 3cm;
font-size: 8pt;
}
@top-center {
content: string(title);
}
header {
width: 0;
height: 0;
visibility: hidden;
string-set: title content();
}
}
</style>
<link rel="stylesheet" href="{% static '/sapl/css/relatorio.css'%}">
</head>
<body>
<div style="margin-bottom: 3cm">
<h2 class="gray-title">Informações Básicas</h2>
<p><b>Tipo da Sessão:</b> {{inf_basicas_dic.nom_sessao}}</p>
<p><b>Abertura:</b> {{inf_basicas_dic.dat_inicio_sessao}} - {{inf_basicas_dic.hr_inicio_sessao}}</p>
<p><b>Encerramento:</b> {{inf_basicas_dic.dat_fim_sessao}} - {{inf_basicas_dic.hr_fim_sessao}}</p>
<h2 class="gray-title">Mesa Diretora</h2>
{% for membro in lst_mesa%}
<p><b>{{membro.des_cargo}}:</b> {{membro.nom_parlamentar}}/{{membro.sgl_partido}}</p>
{% endfor%}
<h2 class="gray-title">Lista de Presença da Sessão</h2>
{% for membro in lst_presenca_sessao%}
<p>{{membro.nom_parlamentar}}/{{membro.sgl_partido}}</p>
{% endfor%}
<h2 class="gray-title">Justificativas de Ausência da Sessão</h2>
<table class="grayTable">
<thead>
<tr>
<th>Parlamentar</th>
<th>Justificativa</th>
<th>Ausente em</th>
</tr>
</thead>
<tbody>
{% for ausencia in lst_ausencia_sessao%}
<tr>
<td>{{ausencia.parlamentar}}</td>
<td>{{ausencia.justificativa}}</td>
<td>{{ausencia.tipo}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2 class="gray-title">Expedientes</h2>
{% for expediente in lst_expedientes%}
<h3>{{expediente.nom_expediente}}</h3>
<p style="margin-bottom: 1cm">{{expediente.txt_expediente|safe}}</p>
{% endfor%}
<h2 class="gray-title">Matérias do Expediente</h2>
<table class="grayTable">
<thead>
<tr>
<th>Matéria</th>
<th>Ementa</th>
<th>Resultado da Votação</th>
</tr>
</thead>
<tbody>
{% for materia in lst_expediente_materia%}
<tr>
<td style="width:300px">
<dl>
<dt><b>{{materia.num_ordem}} -</b> {{materia.id_materia}}</dt>
<dt><b>Turno:</b> {{materia.des_turno}}</dt>
<dt><b>{{materia.num_autores}}: </b>{{materia.nom_autor}}</dt>
</dl>
</td>
<td><div style="margin:10px">{{materia.txt_ementa}}</div></td>
<td style="width:10px"><b>{{materia.nom_resultado}}</b></td>
</tr>
{% endfor %}
</tbody>
</table>
<h2 class="gray-title">Oradores do Expediente</h2>
{% for orador in lst_oradores_expediente%}
<tr>
<p> <b>{{orador.num_ordem}}</b> - {{orador.nom_parlamentar}}/{{orador.sgl_partido}}</p>
</tr>
{% endfor %}
<h2 class="gray-title">Lista de Presença da Ordem do Dia</h2>
{% for orador in lst_presenca_ordem_dia%}
<tr>
<p>{{orador.nom_parlamentar}}/{{orador.sgl_partido}}</p>
</tr>
{% endfor %}
<h2 class="gray-title">Matérias da Ordem do Dia</h2>
<table class="grayTable" style="height: 145px;" width="443">
<thead>
<tr>
<th>Matéria</th>
<th>Ementa</th>
<th>Resultado da Votação</th>
</tr>
</thead>
<tbody>
{% for materia in lst_votacao%}
<tr>
<td style="width:300px">
<dl>
<dt><b>{{materia.num_ordem}} -</b> {{materia.id_materia}}</dt>
<dt><b>Turno:</b> {{materia.des_turno}}</dt>
<dt><b>{{materia.num_autores}}: </b>{{materia.nom_autor}}</dt>
</dl>
</td>
<td><div style="margin:10px">{{materia.txt_ementa}}</div></td>
<td style="width:30px"><b>{{materia.nom_resultado}}</b></td>
</tr>
{% endfor %}
</tbody>
</table>
<div>
<h2 class="gray-title">Oradores das Explicações Pessoais</h2>
{% for orador in lst_oradores%}
<tr>
<p style="page-break-after: avoid;">{{orador.num_ordem}} - {{orador.nom_parlamentar}}/{{orador.sgl_partido}}</p>
</tr>
{% endfor %}
</div>
<h2 class="gray-title">Ocorrências da Sessão</h2>
{% for ocorrencia in lst_ocorrencias%}
<p>{{ocorrencia}}</p>
{% endfor %}
</div>
</body>
</html>

6
sapl/templates/rest_framework_docs/base.html

@ -1,4 +1,4 @@
{% load static from staticfiles %}
{% load webpack_static from webpack_loader %}
<!DOCTYPE html>
<html>
@ -10,7 +10,7 @@
<title>{% block title %}DRF Docs{% endblock %}</title>
{% block style %}
<link rel="stylesheet" href="{% static "rest_framework_docs/css/style.css" %}">
<link rel="stylesheet" href="{% webpack_static "rest_framework_docs/css/style.css" %}">
{% endblock %}
</head>
@ -76,6 +76,6 @@
</div>
<!-- Dist.js - Inlcuded Live API, jQuery, Bootstrap -->
<script type="text/javascript" src="{% static "rest_framework_docs/js/dist.min.js" %}"></script>
<script type="text/javascript" src="{% webpack_static "rest_framework_docs/js/dist.min.js" %}"></script>
</body>
</html>

8
sapl/templates/sessao/blocos_ata/identificacao_basica.html

@ -3,7 +3,13 @@
<strong>Identificação Básica: </strong>
{% for b in basica %}
{{b}}
{% if not forloop.last %} ; {% endif %}
{% if not forloop.last %}
;
{% else %}
{% if tema_solene %}
; {{tema_solene}}
{% endif %}
{% endif %}
{% endfor %}
</p>
</fieldset>

4
sapl/templates/sessao/blocos_resumo/identificacao_basica.html

@ -5,6 +5,10 @@
{% for b in basica %}
<div class="col-md-4">{{b}}</div>
{% endfor %}
{% if tema_solene %}
<br><br>
<div class="col-md-12">{{tema_solene}}</div>
{% endif %}
</div>
</fieldset>
<br /><br /><br />

9
sapl/templates/sessao/layouts.yaml

@ -11,6 +11,15 @@ SessaoPlenaria:
- upload_pauta upload_ata upload_anexo
- url_audio url_video
SessaoSolene:
{% trans 'Dados Básicos' %}:
- legislatura sessao_legislativa tipo:3 numero:1
- data_inicio:5 hora_inicio:5 iniciada
- data_fim:5 hora_fim:5 finalizada
- tema_solene
- upload_pauta upload_ata upload_anexo
- url_audio url_video
TipoResultadoVotacao:
{% trans 'Tipo de Resultado da Votação' %}:
- nome

4
sapl/templates/sessao/pauta_sessao_list.html

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% load i18n staticfiles %}
{% load webpack_static from webpack_loader %}
{% block base_content %}
{% if not page_obj %}
@ -18,7 +18,7 @@
<tr>
<td>{{sessao}}</td>
<td><a href="{% url 'sapl.relatorios:relatorio_sessao_plenaria' sessao.id %}">
<img src="{% static 'img/file.png' %}">
<img src="{% webpack_static 'img/file.png' %}">
</a></td>
</tr>
{% endfor %}

10
sapl/templates/sessao/resumo.html

@ -20,6 +20,16 @@
</p>
</div>
<div>
<p align="right">
<strong>
<a href="{% url 'sapl.relatorios:relatorio_sessao_plenaria_pdf' sessaoplenaria.pk %}">
Impressão PDF (Novo)
</a>
</strong>
</p>
</div>
{% include 'sessao/blocos_resumo/'|add:primeiro_ordenacao %}
{% include 'sessao/blocos_resumo/'|add:segundo_ordenacao %}

59
sapl/templates/sessao/sessaoplenaria_form.html

@ -7,14 +7,11 @@
<script language="Javascript">
{% if not object %}
// faz recuperação do próximo número apenas em caso de inclusão de sessão plenária
// Seleciona o numero de acordo com o tipo
function recuperar_numero_sessao() {
var tipo = $("#id_tipo").val()
var sessao = $("#id_sessao_legislativa").val()
var legislatura = $("#id_legislatura").val()
var data_ini = $("#id_data_inicio").val()
var tipo = $("#id_tipo").val();
var sessao = $("#id_sessao_legislativa").val();
var legislatura = $("#id_legislatura").val();
var data_ini = $("#id_data_inicio").val();
if (tipo) {
$.get("{% url 'sapl.sessao:recuperar_numero_sessao_view' %}",
@ -28,18 +25,28 @@
}
);
}
else{
$("#id_numero").val('');
}
}
$("#id_tipo").click(recuperar_numero_sessao);
$("#id_sessao_legislativa").click(recuperar_numero_sessao);
$("#id_legislatura").click(recuperar_numero_sessao);
{% endif %}
function recuperar_tipo_sessao(){
var tipo = $("#id_tipo").val();
var sessao = $("#id_sessao_legislativa").val();
// Filtra as choices de sessao legislativa pela legislatura
$(function() {
$("#div_id_tema_solene").hide();
if (tipo) {
$.get("{% url 'sapl.sessao:recuperar_nome_tipo_sessao' %}",
{
tipo: tipo,
sessao_legislativa:sessao
},
function(data, status) {
if(data.nome_tipo == "Solene"){
$("#div_id_tema_solene").show();
}
}
);
}
}
function altera_legislatura(){
var id_legislatura = $("#id_legislatura").val();
@ -61,9 +68,27 @@
}
}
$("#id_legislatura").ready(altera_legislatura);
$(document).ready(function(){
{% if not object %}
// faz recuperação do próximo número apenas em caso de inclusão de sessão plenária
// Seleciona o numero de acordo com o tipo
$("#id_tipo").click(recuperar_numero_sessao);
$("#id_sessao_legislativa").click(recuperar_numero_sessao);
$("#id_legislatura").click(recuperar_numero_sessao);
{% endif %}
// Filtra as choices de sessao legislativa pela legislatura
$("#id_legislatura").ready(altera_legislatura);
$("#id_legislatura").change(altera_legislatura);
// Referente a Sessão Solene
$("#id_tipo").ready(recuperar_tipo_sessao);
$("#id_tipo").change(recuperar_tipo_sessao);
});
</script>

34
sapl/templates/sessao/subnav-solene.yaml

@ -0,0 +1,34 @@
{% load i18n common_tags %}
- title: {% trans 'Abertura' %}
children:
- title: {% trans 'Dados Básicos' %}
url: sessaoplenaria_detail
- title: {% trans 'Mesa' %}
url: mesa
- title: {% trans 'Presença' %}
url: presenca
- title: {% trans 'Explicações Pessoais' %}
url: orador_list
- title: {% trans 'Ocorrências da Sessão' %}
url: ocorrencia_sessao
- title: {% trans 'Expedientes' %}
children:
- title: {% trans 'Expediente Diversos' %}
url: expediente
- title: {% trans 'Oradores do Expediente' %}
url: oradorexpediente_list
- title: {% trans 'Painel Eletrônico' %}
url: painel
{% if not 'painel_aberto'|get_config_attr %}check_permission: painel.list_painel{%endif%}
check_permission: painel.list_painel
- title: {% trans 'Resumo' %}
children:
- title: {% trans 'Resumo' %}
url: resumo
- title: {% trans 'Extrato' %}
url: resumo_ata
check_permission: sessao.add_sessaoplenaria

2
setup.py

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

1
solr/sapl_configset/conf/managed-schema

@ -120,6 +120,7 @@
<field name="django_ct" type="string" indexed="true" stored="true" multiValued="false"/>
<field name="django_id" type="string" indexed="true" stored="true" multiValued="false"/>
<field name="text" type="text_pt" indexed="true" stored="true" multiValued="false" />
<field name="last_update" type="pdate" indexed="true" stored="true" default="NOW" />
<!-- This can be enabled, in case the client does not know what fields may be searched. It isn't enabled by default
because it's very expensive to index everything twice. -->

BIN
solr/sapl_configset/conf/saplconfigset.zip

Binary file not shown.

166
solr/sapl_configset/conf/schema.xml

@ -1,166 +0,0 @@
<?xml version="1.0" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<schema name="default" version="1.6">
<types>
<fieldtype name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
<fieldType name="booleans" class="solr.BoolField" sortMissingLast="true" multiValued="true"/>
<fieldtype name="binary" class="solr.BinaryField"/>
<!-- Numeric field types that manipulate the value into
a string value that isn't human-readable in its internal form,
but with a lexicographic ordering the same as the numeric ordering,
so that range queries work correctly. -->
<fieldType name="pint" class="solr.IntPointField" docValues="true" />
<fieldType name="pfloat" class="solr.FloatPointField" docValues="true" />
<fieldType name="plong" class="solr.LongPointField" docValues="true" />
<fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>
<fieldType name="pdate" class="solr.DatePointField" docValues="true" />
<!-- A Trie based date field ifor faster date range queries and date faceting. -->
<fieldType name="pints" class="solr.IntPointField" docValues="true" multiValued="true"/>
<fieldType name="pfloats" class="solr.FloatPointField" docValues="true" multiValued="true"/>
<fieldType name="plongs" class="solr.LongPointField" docValues="true" multiValued="true"/>
<fieldType name="pdoubles" class="solr.DoublePointField" docValues="true" multiValued="true"/>
<fieldType name="pdates" class="solr.DatePointField" docValues="true" multiValued="true"/>
<fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
<fieldtype name="geohash" class="solr.GeoHashField"/>
<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<!-- in this example, we will only use synonyms at query time
<filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
-->
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<!-- Portuguese -->
<dynamicField name="*_txt_pt" type="text_pt" indexed="true" stored="true"/>
<fieldType name="text_pt" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_pt.txt" format="snowball" />
<filter class="solr.PortugueseLightStemFilterFactory"/>
<!-- less aggressive: <filter class="solr.PortugueseMinimalStemFilterFactory"/> -->
<!-- more aggressive: <filter class="solr.SnowballPorterFilterFactory" language="Portuguese"/> -->
<!-- most aggressive: <filter class="solr.PortugueseStemFilterFactory"/> -->
</analyzer>
</fieldType>
<fieldType name="text_en" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="lang/stopwords_en.txt"
/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.EnglishPossessiveFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
<filter class="solr.EnglishMinimalStemFilterFactory"/>
-->
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="lang/stopwords_en.txt"
/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.EnglishPossessiveFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
<filter class="solr.EnglishMinimalStemFilterFactory"/>
-->
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
</fieldType>
<fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
</analyzer>
</fieldType>
<fieldType name="ngram" class="solr.TextField" >
<analyzer type="index">
<tokenizer class="solr.KeywordTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.NGramFilterFactory" minGramSize="3" maxGramSize="15" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.KeywordTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<fieldType name="edge_ngram" class="solr.TextField" positionIncrementGap="1">
<analyzer type="index">
<tokenizer class="solr.WhitespaceTokenizerFactory" />
<filter class="solr.LowerCaseFilterFactory" />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory" />
<filter class="solr.LowerCaseFilterFactory" />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
</analyzer>
</fieldType>
</types>
<fields>
<!-- general -->
<field name="id" type="string" indexed="true" stored="true" multiValued="false" required="true"/>
<field name="django_ct" type="string" indexed="true" stored="true" multiValued="false"/>
<field name="django_id" type="string" indexed="true" stored="true" multiValued="false"/>
<field name="_version_" type="plong" indexed="true" stored ="true"/>
<field name="text" type="text_pt" indexed="true" stored="true" multiValued="false" />
<field name="last_update" type="pdate" indexed="true" stored="true" default="NOW" />
</fields>
<!-- field to use to determine and enforce document uniqueness. -->
<uniqueKey>id</uniqueKey>
<!-- field for the QueryParser to use when an explicit fieldname is absent -->
<df>text</df>
<!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
<solrQueryParser q.op="AND"/>
</schema>
Loading…
Cancel
Save