Browse Source

Merge tag '3.1.155' into migracao

migracao
Marcio Mazza 6 years ago
parent
commit
1e38601f85
  1. 1
      Dockerfile
  2. 13
      check_solr.sh
  3. 19
      docker-compose.yml
  4. 39
      docs/solr.rst
  5. 32
      sapl/api/views.py
  6. 3
      sapl/base/forms.py
  7. 20
      sapl/base/migrations/0033_auto_20190415_1050.py
  8. 29
      sapl/base/migrations/0034_auto_20190417_0941.py
  9. 20
      sapl/base/migrations/0035_auto_20190417_1009.py
  10. 20
      sapl/base/migrations/0036_auto_20190417_1432.py
  11. 16
      sapl/base/models.py
  12. 7
      sapl/base/views.py
  13. 4
      sapl/compilacao/forms.py
  14. 27
      sapl/compilacao/migrations/0012_bug_auto_inserido.py
  15. 33
      sapl/compilacao/views.py
  16. 4
      sapl/legacy/test_renames.py
  17. 37
      sapl/lexml/OAIServer.py
  18. 182
      sapl/materia/forms.py
  19. 20
      sapl/materia/migrations/0045_auto_20190415_1050.py
  20. 20
      sapl/materia/migrations/0046_auto_20190417_0941.py
  21. 28
      sapl/materia/migrations/0046_auto_20190417_1212.py
  22. 20
      sapl/materia/migrations/0047_auto_20190417_1432.py
  23. 16
      sapl/materia/migrations/0048_merge_20190426_0828.py
  24. 15
      sapl/materia/models.py
  25. 235
      sapl/materia/tests/test_materia.py
  26. 144
      sapl/materia/views.py
  27. 19
      sapl/norma/migrations/0024_auto_20190425_0917.py
  28. 10
      sapl/norma/models.py
  29. 52
      sapl/parlamentares/forms.py
  30. 37
      sapl/parlamentares/migrations/0026_bloco.py
  31. 19
      sapl/parlamentares/migrations/0027_auto_20190430_0839.py
  32. 34
      sapl/parlamentares/models.py
  33. 8
      sapl/parlamentares/urls.py
  34. 43
      sapl/parlamentares/views.py
  35. 265
      sapl/protocoloadm/forms.py
  36. 35
      sapl/protocoloadm/migrations/0018_auto_20190314_1532.py
  37. 28
      sapl/protocoloadm/migrations/0019_auto_20190426_0833.py
  38. 25
      sapl/protocoloadm/migrations/0019_auto_20190429_0828.py
  39. 21
      sapl/protocoloadm/migrations/0020_tramitacaoadministrativo_timestamp.py
  40. 16
      sapl/protocoloadm/migrations/0021_merge_20190429_1531.py
  41. 61
      sapl/protocoloadm/models.py
  42. 298
      sapl/protocoloadm/tests/test_protocoloadm.py
  43. 7
      sapl/protocoloadm/urls.py
  44. 255
      sapl/protocoloadm/views.py
  45. 24
      sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py
  46. 17
      sapl/relatorios/views.py
  47. 6
      sapl/rules/map_rules.py
  48. 165
      sapl/sessao/forms.py
  49. 21
      sapl/sessao/migrations/0036_auto_20190412_1106.py
  50. 33
      sapl/sessao/migrations/0037_auto_20190415_1324.py
  51. 85
      sapl/sessao/migrations/0037_auto_20190415_1635.py
  52. 16
      sapl/sessao/migrations/0038_merge_20190415_1800.py
  53. 22
      sapl/sessao/migrations/0039_auto_20190430_0825.py
  54. 142
      sapl/sessao/models.py
  55. 6
      sapl/sessao/urls.py
  56. 190
      sapl/sessao/views.py
  57. 2
      sapl/settings.py
  58. 2
      sapl/templates/base.html
  59. 8
      sapl/templates/base/autores_duplicados.html
  60. 8
      sapl/templates/base/layouts.yaml
  61. 27
      sapl/templates/compilacao/text_list_bloco.html
  62. 6
      sapl/templates/materia/em_lote/anexada.html
  63. 38
      sapl/templates/materia/tramitacao_detail.html
  64. 2
      sapl/templates/menu_tabelas_auxiliares.yaml
  65. 2
      sapl/templates/navbar.yaml
  66. 7
      sapl/templates/parlamentares/layouts.yaml
  67. 13
      sapl/templates/protocoloadm/anexado_list.html
  68. 5
      sapl/templates/protocoloadm/em_lote/anexado.html
  69. 19
      sapl/templates/protocoloadm/layouts.yaml
  70. 2
      sapl/templates/protocoloadm/subnav.yaml
  71. 37
      sapl/templates/protocoloadm/tramitacaoadministrativo_detail.html
  72. 10
      sapl/templates/relatorios/relatorio_ata.html
  73. 4
      sapl/templates/sessao/blocos_ata/assinaturas.html
  74. 22
      sapl/templates/sessao/blocos_ata/lista_presenca.html
  75. 2
      sapl/templates/sessao/blocos_ata/lista_presenca_ordem_dia.html
  76. 18
      sapl/templates/sessao/blocos_ata/materias_expediente.html
  77. 63
      sapl/templates/sessao/blocos_ata/materias_ordem_dia.html
  78. 15
      sapl/templates/sessao/blocos_ata/mesa_diretora.html
  79. 6
      sapl/templates/sessao/blocos_ata/ocorrencias_da_sessao.html
  80. 11
      sapl/templates/sessao/blocos_ata/oradores_expediente.html
  81. 6
      sapl/templates/sessao/blocos_ata/oradores_explicacoes.html
  82. 2
      sapl/templates/sessao/blocos_resumo/lista_presenca_ordem_dia.html
  83. 4
      sapl/templates/sessao/blocos_resumo/mesa_diretora.html
  84. 4
      sapl/templates/sessao/blocos_resumo/oradores_explicacoes.html
  85. 7
      sapl/templates/sessao/layouts.yaml
  86. 98
      sapl/templates/sessao/painel.html
  87. 160
      sapl/templates/sessao/votacao/votacao_bloco_expediente.html
  88. 168
      sapl/templates/sessao/votacao/votacao_bloco_ordem.html
  89. 2
      sapl/templates/sistema.html
  90. 28
      sapl/utils.py
  91. 27
      scripts/check_commits.py
  92. 105
      scripts/deduplica_parlamentares.py
  93. 84
      scripts/remove_multiplos_autores.py
  94. 13
      scripts/remove_protocolos_inexistentes_materias.py
  95. 2
      setup.py
  96. 14
      start.sh

1
Dockerfile

@ -39,6 +39,7 @@ RUN rm -rf /var/interlegis/sapl/sapl/.env && \
rm -rf /var/interlegis/sapl/sapl.db
RUN chmod +x /var/interlegis/sapl/start.sh && \
chmod +x /var/interlegis/sapl/check_solr.sh && \
ln -sf /dev/stdout /var/log/nginx/access.log && \
ln -sf /dev/stderr /var/log/nginx/error.log && \
mkdir /var/log/sapl/ && touch /var/interlegis/sapl/sapl.log && \

13
check_solr.sh

@ -4,15 +4,22 @@
SOLR_URL=$1
RETRY_COUNT=1
RETRY_LIMIT=4
echo "Waiting for solr connection at $SOLR_URL ..."
while true; do
while [[ $RETRY_COUNT < $RETRY_LIMIT ]]; do
echo "Attempt to connect to solr: $RETRY_COUNT of $RETRY_LIMIT"
let RETRY_COUNT=RETRY_COUNT+1;
echo "$SOLR_URL/solr/admin/collections?action=LIST"
RESULT=$(curl -s -o /dev/null -I "$SOLR_URL/solr/admin/collections?action=LIST" -w '%{http_code}')
echo $RESULT
if [ "$RESULT" -eq '200' ]; then
if [ $RESULT == 200 ]; then
echo "Solr server is up!"
break
exit 1
else
sleep 3
fi
done
echo "Solr connection failed."
exit 2

19
docker-compose.yml

@ -11,7 +11,8 @@ sapldb:
ports:
- "5432:5432"
sapl:
image: interlegis/sapl:3.1.153
image: interlegis/sapl:3.1.155
# build: .
restart: always
environment:
ADMIN_PASSWORD: interlegis
@ -23,11 +24,27 @@ 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"

39
docs/solr.rst

@ -2,6 +2,8 @@
Instruções para instalar o Solr
================================
**O servidor do Solr NÃO DEVE SER EXPOSTO NA INTERNET. Assim como o servidor de bancos de dados Postgres ele deve estar acessível pelo SAPL na rede interna (atrás de NATs/firewalls/proxies/etc).**
Solr é uma plataforma open source de indexação e busca textual utilizada pelo SAPL 3.1 para indexar documentos (normas jurídicas, matérias legislativas e documentos acessórios).
Observação: Se a execução do SAPL for mediante containers Docker então use o arquivo *docker-compose.yml* disponível em
@ -45,11 +47,42 @@ Observação: Se a execução do SAPL for mediante containers Docker então use
8) Enquanto o Solr realiza a indexação da base de dados do SAPL, inicie em uma outra tela o SAPL;
9) Após realizados os passos com sucesso, nas telas de busca de Matéria Legislativa e Normas deverá aparecer um botão
de 'Busca Textual' próximo ao botão de busca tradicional.
de 'Pesquisa Textual' na tela de busca tradicional.
**Observações:**
* Para parar o Solr execute o comando **$SOLR_HOME/bin/solr stop**
* Para reindexar os dados do SAPL execute o comando **python3 manage.py rebuild_index** (isso irá apagar todos os dados
do Solr e indexar tudo novamente).
* Comandos de manutenção da base textual do Solr:
1. **python3 manage.py rebuild_index** : Apaga os dados da coleção `sapl` no Solr e reindexa tudo do início;
2. **python3 manage.py clear_index** : Apaga todos os dados da coleção `sapl` do Solr. **Este comando não irá apagar os dados do BD Postgres, somente os dados do Solr serão apagados.**
3. **python3 manage.py update_index** : atualiza os dados do Solr:
3.1. **python3 manage.py update_index --remove** : remove objetos do Solr que não mais existem no BD Postgres (no caso do Postgres e Solr derem dessincronizados).
3.2. **python3 manage.py update_index --age <N>** : reindexa os documentos inseridos/alterados nas últimas <N> horas;
3.3. **python3 manage.py update_index -s YYYY-MM-DDTHH:MM:SS -e YYYY-MM-DDTHH:MM:SS** : reindexa os documentos que foram inseridos/atualizados entre a data inicial (-s) e a data final (-e). Ambos os argumentos de início e fim são opcionais.
### FAQ
1. Uma dúvida quanto a indexação do Solr, pelo que entendi de tempos e tempos tenho que rodar o comando para poder indexar novos arquivos certo?
Errado. Cada novo documento inserido, atualizado, ou removido do SAPL dispara uma nova indexação somente daquele documento no Solr automaticamente.
2. O comando **python3 solr_api.py -c sapl -u http://localhost:8983** indexa os novos arquivos?
Não. Este comando é para construir a coleção do Solr a primeira vez e, por acaso, faz a indexação inicial. Não deve ser usado se a coleção já foi criada.
3. Ou teria que reindexar do zero com *rebuild_index*?
Pode acontecer do Postgres e o Solr se dessincronizarem (ex: o Solr ficou fora do ar por um dia e foram inseridos registros no SAPL). Ou por algum motivo se deseja refazer o índice do Solr. Neste caso pode-se refazer a indexação no Solr com o comando : **python3 manage.py rebuild_index** (direto na linha de comando, a partir da pasta raiz do SAPL). Mas existem maneiras de atualizar somente os documentos inseridos/alterados a partir de uma determinada data ao invés de atualizar tudo do zero de novo.
4. Pergunto isso pois estou querendo criar um script para crontab para indexar esses novos arquivos
Desnecessário.

32
sapl/api/views.py

@ -26,8 +26,8 @@ from sapl.materia.models import Proposicao, TipoMateriaLegislativa,\
MateriaLegislativa, Tramitacao
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import DocumentoAdministrativo,\
DocumentoAcessorioAdministrativo, TramitacaoAdministrativo
from sapl.sessao.models import SessaoPlenaria
DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado
from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao
from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria
@ -489,6 +489,20 @@ class _TramitacaoAdministrativoViewSet(BusinessRulesNotImplementedMixin):
return qs
@customize(Anexado)
class _AnexadoViewSet(BusinessRulesNotImplementedMixin):
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, )
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_anonymous():
qs = qs.exclude(documento__restrito=True)
return qs
@customize(SessaoPlenaria)
class _SessaoPlenariaViewSet:
@ -498,3 +512,17 @@ class _SessaoPlenariaViewSet:
serializer = ChoiceSerializer(years, many=True)
return Response(serializer.data)
@action(detail=True)
def expedientes(self, request, *args, **kwargs):
sessao = self.get_object()
page = self.paginate_queryset(sessao.expedientesessao_set.all())
if page is not None:
serializer = SaplApiViewSetConstrutor.get_class_for_model(
ExpedienteSessao).serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(page, many=True)
return Response(serializer.data)

3
sapl/base/forms.py

@ -1203,7 +1203,8 @@ class ConfiguracoesAppForm(ModelForm):
class Meta:
model = AppConfig
fields = ['documentos_administrativos',
'sequencia_numeracao',
'sequencia_numeracao_protocolo',
'sequencia_numeracao_proposicao',
'esfera_federacao',
# 'painel_aberto', # TODO: a ser implementado na versão 3.2
'texto_articulado_proposicao',

20
sapl/base/migrations/0033_auto_20190415_1050.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-15 13:50
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0032_merge_20190219_0941'),
]
operations = [
migrations.AlterField(
model_name='appconfig',
name='sequencia_numeracao',
field=models.CharField(choices=[('A', 'Sequencial por ano para cada autor'), ('B', 'Sequencial por ano indepententemente do autor'), ('L', 'Sequencial por legislatura'), ('U', 'Sequencial único')], default='A', max_length=1, verbose_name='Sequência de numeração'),
),
]

29
sapl/base/migrations/0034_auto_20190417_0941.py

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-17 12:41
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0033_auto_20190415_1050'),
]
operations = [
migrations.RemoveField(
model_name='appconfig',
name='sequencia_numeracao',
),
migrations.AddField(
model_name='appconfig',
name='sequencia_numeracao_proposicao',
field=models.CharField(choices=[('A', 'Sequencial por ano para cada autor'), ('B', 'Sequencial por ano indepententemente do autor'), ('L', 'Sequencial por legislatura'), ('U', 'Sequencial único')], default='A', max_length=1, verbose_name='Sequência de numeração de proposições'),
),
migrations.AddField(
model_name='appconfig',
name='sequencia_numeracao_protocolo',
field=models.CharField(choices=[('A', 'Sequencial por ano para cada autor'), ('L', 'Sequencial por legislatura'), ('U', 'Sequencial único')], default='A', max_length=1, verbose_name='Sequência de numeração de protocolos'),
),
]

20
sapl/base/migrations/0035_auto_20190417_1009.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-17 13:09
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0034_auto_20190417_0941'),
]
operations = [
migrations.AlterField(
model_name='appconfig',
name='sequencia_numeracao_proposicao',
field=models.CharField(choices=[('A', 'Sequencial por ano para cada autor'), ('B', 'Sequencial por ano indepententemente do autor')], default='A', max_length=1, verbose_name='Sequência de numeração de proposições'),
),
]

20
sapl/base/migrations/0036_auto_20190417_1432.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-17 17:32
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0035_auto_20190417_1009'),
]
operations = [
migrations.AlterField(
model_name='appconfig',
name='sequencia_numeracao_protocolo',
field=models.CharField(choices=[('A', 'Sequencial por ano'), ('L', 'Sequencial por legislatura'), ('U', 'Sequencial único')], default='A', max_length=1, verbose_name='Sequência de numeração de protocolos'),
),
]

16
sapl/base/models.py

@ -18,10 +18,13 @@ TIPO_DOCUMENTO_ADMINISTRATIVO = ((DOC_ADM_OSTENSIVO, _('Ostensiva')),
RELATORIO_ATOS_ACESSADOS = (('S', _('Sim')),
('N', _('Não')))
SEQUENCIA_NUMERACAO = (('A', _('Sequencial por ano')),
SEQUENCIA_NUMERACAO_PROTOCOLO = (('A', _('Sequencial por ano')),
('L', _('Sequencial por legislatura')),
('U', _('Sequencial único')))
SEQUENCIA_NUMERACAO_PROPOSICAO = (('A', _('Sequencial por ano para cada autor')),
('B', _('Sequencial por ano indepententemente do autor')))
ESFERA_FEDERACAO_CHOICES = (('M', _('Municipal')),
('E', _('Estadual')),
('F', _('Federal')),
@ -95,10 +98,15 @@ class AppConfig(models.Model):
verbose_name=_('Estatísticas de acesso a normas'),
choices=RELATORIO_ATOS_ACESSADOS, default='N')
sequencia_numeracao = models.CharField(
sequencia_numeracao_proposicao = models.CharField(
max_length=1,
verbose_name=_('Sequência de numeração de proposições'),
choices=SEQUENCIA_NUMERACAO_PROPOSICAO, default='A')
sequencia_numeracao_protocolo = models.CharField(
max_length=1,
verbose_name=_('Sequência de numeração'),
choices=SEQUENCIA_NUMERACAO, default='A')
verbose_name=_('Sequência de numeração de protocolos'),
choices=SEQUENCIA_NUMERACAO_PROTOCOLO, default='A')
esfera_federacao = models.CharField(
max_length=1,

7
sapl/base/views.py

@ -1081,9 +1081,7 @@ class ListarBancadaComissaoAutorExternoView(PermissionRequiredMixin, ListView):
def autores_duplicados():
return [autor.values() for autor in Autor.objects.values(
'nome', 'tipo__descricao').order_by(
"nome").annotate(count=Count('nome')).filter(count__gt=1)]
return [autor for autor in Autor.objects.values('nome').annotate(count=Count('nome')).filter(count__gt=1)]
class ListarAutoresDuplicadosView(PermissionRequiredMixin, ListView):
@ -1097,8 +1095,7 @@ class ListarAutoresDuplicadosView(PermissionRequiredMixin, ListView):
return autores_duplicados()
def get_context_data(self, **kwargs):
context = super(
ListarAutoresDuplicadosView, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(

4
sapl/compilacao/forms.py

@ -220,7 +220,7 @@ class NotaForm(ModelForm):
publicacao = forms.DateField(
label=Nota._meta.get_field('publicacao').verbose_name,
input_formats=['%d/%m/%Y'],
input_formats=['%d/%m/%Y', '%d%m%Y'],
required=True,
widget=forms.DateInput(
format='%d/%m/%Y'),
@ -228,7 +228,7 @@ class NotaForm(ModelForm):
)
efetividade = forms.DateField(
label=Nota._meta.get_field('efetividade').verbose_name,
input_formats=['%d/%m/%Y'],
input_formats=['%d/%m/%Y', '%d%m%Y'],
required=True,
widget=forms.DateInput(
format='%d/%m/%Y'),

27
sapl/compilacao/migrations/0012_bug_auto_inserido.py

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-03-19 13:41
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
def adjust_bug_auto_inserido(apps, schema_editor):
Dispositivo = apps.get_model('compilacao', 'Dispositivo')
Dispositivo.objects.filter(
tipo_dispositivo__class_css__startswith='caput',
dispositivo_pai__tipo_dispositivo__class_css__startswith='artigo',
auto_inserido=False
).update(auto_inserido=True)
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0011_tipotextoarticulado_rodape_global'),
]
operations = [
migrations.RunPython(adjust_bug_auto_inserido),
]

33
sapl/compilacao/views.py

@ -1545,7 +1545,7 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
if not anterior:
self.logger.error("user=" + username + ". Não é possível excluir este Dispositivo (id={}) sem"
" excluir toda a sua estrutura!!!".format(base.ta_id))
" excluir toda a sua estrutura!!!".format(base.id))
raise Exception(
_('Não é possível excluir este Dispositivo sem'
' excluir toda a sua estrutura!!!'))
@ -1566,8 +1566,8 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
for candidato in parents:
if candidato == base:
self.logger.error("user=" + username + ". Não é possível excluir este "
"Dispositivo ({}) sem "
"excluir toda a sua estrutura!!!".format(candidato))
"Dispositivo (id={}) sem "
"excluir toda a sua estrutura!!!".format(candidato.id))
raise Exception(
_('Não é possível excluir este '
'Dispositivo sem '
@ -1604,8 +1604,8 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
break
else:
self.logger.error("user=" + username + ". Não é possível excluir este "
"Dispositivo ({}) sem excluir toda "
"a sua estrutura!!!".format(candidato))
"Dispositivo (id={}) sem excluir toda "
"a sua estrutura!!!".format(candidato.id))
raise Exception(
_('Não é possível excluir este '
'Dispositivo sem '
@ -1643,6 +1643,7 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
# excluir e renumerar irmaos
profundidade_base = base.get_profundidade()
auto_inserido_base = base.auto_inserido
base.delete()
for irmao in irmaos_posteriores:
@ -1666,6 +1667,11 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
i.set_numero_completo([0, 0, 0, 0, 0, 0, ])
i.rotulo = i.rotulo_padrao(local_insert=1)
i.save()
if not irmaos.exists() and \
auto_inserido_base and \
pai_base.nivel:
self.remover_dispositivo(pai_base, False)
else:
# Renumerar Dispostivos de Contagem Contínua
# de dentro da base se pai
@ -2224,9 +2230,15 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
dispositivo_pai=dp.dispositivo_pai).count()
if qtd_existente >= pp[0].quantidade_permitida:
data = {'pk': base.pk,
'pai': [base.dispositivo_pai.pk, ]}
self.set_message(data, 'warning',
data = {'pk': None
if base.dispositivo_pai else
base.pk,
'pai': [
base.dispositivo_pai.pk if
base.dispositivo_pai else
base.pk,
]}
self.set_message(data, 'danger',
_('Limite de inserções de '
'dispositivos deste tipo '
'foi excedido.'), time=6000)
@ -2512,7 +2524,7 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
local_add=local_add,
create_auto_inserts=True)
if data:
if data and data['pk']:
ndp = Dispositivo.objects.get(pk=data['pk'])
@ -2539,6 +2551,9 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
data.update({'pk': ndp.pk,
'pai': [bloco_alteracao.pk, ]})
else:
data.update({'pk': bloco_alteracao.pk,
'pai': [bloco_alteracao.pk, ]})
return data

4
sapl/legacy/test_renames.py

@ -13,9 +13,9 @@ from sapl.materia.models import (AcompanhamentoMateria, DocumentoAcessorio,
from sapl.norma.models import (AnexoNormaJuridica, NormaJuridica,
NormaRelacionada, TipoVinculoNormaJuridica)
from sapl.parlamentares.models import (Frente, Mandato, Parlamentar, Partido,
TipoAfastamento, Votante)
TipoAfastamento, Votante, Bloco)
from sapl.protocoloadm.models import DocumentoAdministrativo
from sapl.sessao.models import (Bancada, Bloco, CargoBancada,
from sapl.sessao.models import (Bancada, CargoBancada,
ExpedienteMateria, Orador, OradorExpediente,
OrdemDia, RegistroVotacao, ResumoOrdenacao,
SessaoPlenaria, TipoResultadoVotacao,

37
sapl/lexml/OAIServer.py

@ -1,3 +1,4 @@
import unicodedata
from datetime import datetime
import oaipmh
@ -11,6 +12,7 @@ from lxml.builder import ElementMaker
from sapl.base.models import AppConfig, CasaLegislativa
from sapl.lexml.models import LexmlPublicador, LexmlProvedor
from sapl.norma.models import NormaJuridica
from sapl.utils import LISTA_DE_UFS
class OAILEXML:
@ -122,22 +124,33 @@ class OAIServer:
else:
return None
@staticmethod
def remove_acentos(linha):
res = unicodedata.normalize('NFKD', linha).encode('ASCII', 'ignore')
res = res.decode("UTF-8")
remove_list = ["\'", "\"", "-"]
for i in remove_list:
res = res.replace(i, "")
return res
def monta_urn(self, norma, esfera):
if norma:
urn = 'urn:lex:br;'
esferas = {'M': 'municipal', 'E': 'estadual'}
municipio = casa.municipio.lower()
uf = casa.uf.lower()
municipio = self.remove_acentos(casa.municipio.lower())
uf_map = dict(LISTA_DE_UFS)
uf_desc = uf_map.get(casa.uf.upper(), '').lower()
uf_desc = self.remove_acentos(uf_desc)
for x in [' ', '.de.', '.da.', '.das.', '.do.', '.dos.']:
municipio = municipio.replace(x, '.')
uf = uf.replace(x, '.')
uf_desc = uf_desc.replace(x, '.')
if esfera == 'M':
urn += '{};{}:'.format(uf, municipio)
urn += '{};{}:'.format(uf_desc, municipio)
if norma.tipo.equivalente_lexml == 'regimento.interno' or norma.tipo.equivalente_lexml == 'resolucao':
urn += 'camara.'
urn += esferas[esfera] + ':'
elif esfera == 'E':
urn += '{}:{}:'.format(uf, esferas[esfera])
urn += '{}:{}:'.format(uf_desc, esferas[esfera])
else:
urn += ':'
if norma.tipo.equivalente_lexml:
@ -166,11 +179,14 @@ class OAIServer:
return ''
def monta_xml(self, urn, norma):
BASE_URL_SAPL = self.config['base_url']
BASE_URL_SAPL = BASE_URL_SAPL[:BASE_URL_SAPL.find('/', 8)]
publicador = LexmlPublicador.objects.first()
if norma and publicador:
LEXML = ElementMaker(namespace=self.ns['lexml'], nsmap=self.ns)
oai_lexml = LEXML.LexML()
oai_lexml.attrib['{{}}schemaLocation'.format(self.XSI_NS)] = '{} {}'.format(
oai_lexml.attrib['{{{pre}}}schemaLocation'.format(pre=self.XSI_NS)] = '{} {}'.format(
'http://www.lexml.gov.br/oai_lexml', 'http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd')
texto_integral = norma.texto_integral
mime_types = {'doc': 'application/msword',
@ -178,20 +194,21 @@ class OAIServer:
'odt': 'application/vnd.oasis.opendocument.text',
'pdf': 'application/pdf',
'rtf': 'application/rtf'}
if texto_integral:
url_conteudo = self.config['base_url'] + texto_integral.url
url_conteudo = BASE_URL_SAPL + texto_integral.url
extensao = texto_integral.url.split('.')[-1]
formato = mime_types.get(extensao, 'application/octet-stream')
else:
formato = 'text/html'
url_conteudo = self.config['base_url'] + reverse('sapl.norma:normajuridica_detail',
kwargs={'pk': norma.numero})
url_conteudo = BASE_URL_SAPL + reverse('sapl.norma:normajuridica_detail',
kwargs={'pk': norma.pk})
element_maker = ElementMaker()
id_publicador = str(publicador.id_publicador)
item_conteudo = element_maker.Item(url_conteudo, formato=formato, idPublicador=id_publicador,
tipo='conteudo')
oai_lexml.append(item_conteudo)
url = self.config['base_url'] + reverse('sapl.norma:normajuridica_detail', kwargs={'pk': norma.numero})
url = BASE_URL_SAPL + reverse('sapl.norma:normajuridica_detail', kwargs={'pk': norma.pk})
item_metadado = element_maker.Item(url, formato='text/html', idPublicador=id_publicador, tipo='metadado')
oai_lexml.append(item_metadado)
documento_individual = element_maker.DocumentoIndividual(urn)

182
sapl/materia/forms.py

@ -38,14 +38,15 @@ from sapl.materia.models import (AssuntoMateria, Autoria, MateriaAssunto,
from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica)
from sapl.parlamentares.models import Legislatura, Partido, Parlamentar
from sapl.protocoloadm.models import Protocolo, DocumentoAdministrativo
from sapl.protocoloadm.models import Protocolo, DocumentoAdministrativo, Anexado
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import (YES_NO_CHOICES, SEPARADOR_HASH_PROPOSICAO,
ChoiceWithoutValidationField,
MateriaPesquisaOrderingFilter, RangeWidgetOverride,
autor_label, autor_modal, gerar_hash_arquivo,
models_with_gr_for_model, qs_override_django_filter,
choice_anos_com_materias, FilterOverridesMetaMixin, FileFieldCheckMixin)
choice_anos_com_materias, FilterOverridesMetaMixin, FileFieldCheckMixin,
lista_anexados)
from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial,
DocumentoAcessorio, Numeracao, Proposicao, Relatoria,
@ -461,7 +462,11 @@ class TramitacaoForm(ModelForm):
'unidade_tramitacao_destino',
'data_encaminhamento',
'data_fim_prazo',
'texto']
'texto',
'user',
'ip']
widgets = {'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
super(TramitacaoForm, self).__init__(*args, **kwargs)
@ -549,17 +554,46 @@ class TramitacaoForm(ModelForm):
def save(self, commit=True):
tramitacao = super(TramitacaoForm, self).save(commit)
materia = tramitacao.materia
for ma in materia.anexadas.all():
materia.em_tramitacao = False if tramitacao.status.indicador == "F" else True
materia.save()
lista_tramitacao = []
lista_anexadas = lista_anexados(materia)
for ma in lista_anexadas:
if not ma.tramitacao_set.all() \
or ma.tramitacao_set.last().unidade_tramitacao_destino == tramitacao.unidade_tramitacao_local:
tramitacao_nova = tramitacao
tramitacao_nova.pk = None
tramitacao_nova.materia = ma
tramitacao_nova.save()
ma.em_tramitacao = False if tramitacao.status.indicador == "F" else True
ma.save()
lista_tramitacao.append(Tramitacao(
status=tramitacao.status,
materia=ma,
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
# Compara se os campos de duas tramitações são iguais,
# exceto os campos id, documento_id e timestamp
def compara_tramitacoes_mat(tramitacao1, tramitacao2):
if not tramitacao1 or not 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] != '_')]
return values == other_values
class TramitacaoUpdateForm(TramitacaoForm):
unidade_tramitacao_local = forms.ModelChoiceField(
queryset=UnidadeTramitacao.objects.all(),
@ -580,11 +614,15 @@ class TramitacaoUpdateForm(TramitacaoForm):
'data_encaminhamento',
'data_fim_prazo',
'texto',
'user',
'ip'
]
widgets = {
'data_encaminhamento': forms.DateInput(format='%d/%m/%Y'),
'data_fim_prazo': forms.DateInput(format='%d/%m/%Y'),
'user': forms.HiddenInput(),
'ip': forms.HiddenInput()
}
def clean(self):
@ -593,33 +631,73 @@ class TramitacaoUpdateForm(TramitacaoForm):
if not self.is_valid():
return self.cleaned_data
cd = self.cleaned_data
obj = self.instance
ultima_tramitacao = Tramitacao.objects.filter(
materia_id=self.instance.materia_id).order_by(
materia_id=obj.materia_id).order_by(
'-data_tramitacao',
'-id').first()
# Se a Tramitação que está sendo editada não for a mais recente,
# ela não pode ter seu destino alterado.
if ultima_tramitacao != self.instance:
if self.cleaned_data['unidade_tramitacao_destino'] != \
self.instance.unidade_tramitacao_destino:
if ultima_tramitacao != obj:
if cd['unidade_tramitacao_destino'] != \
obj.unidade_tramitacao_destino:
self.logger.error("Você não pode mudar a Unidade de Destino desta "
"tramitação para {}, pois irá conflitar com a Unidade "
"Local da tramitação seguinte ({})."
.format(self.cleaned_data['unidade_tramitacao_destino'],
self.instance.unidade_tramitacao_destino))
.format(cd['unidade_tramitacao_destino'],
obj.unidade_tramitacao_destino))
raise ValidationError(
'Você não pode mudar a Unidade de Destino desta '
'tramitação, pois irá conflitar com a Unidade '
'Local da tramitação seguinte')
# Se não houve qualquer alteração em um dos dados, mantém o usuário e ip
if not (cd['data_tramitacao'] != obj.data_tramitacao or \
cd['unidade_tramitacao_destino'] != obj.unidade_tramitacao_destino or \
cd['status'] != obj.status or cd['texto'] != obj.texto or \
cd['data_encaminhamento'] != obj.data_encaminhamento or \
cd['data_fim_prazo'] != obj.data_fim_prazo or \
cd['urgente'] != obj.urgente or \
cd['turno'] != obj.turno):
cd['user'] = obj.user
cd['ip'] = obj.ip
cd['data_tramitacao'] = obj.data_tramitacao
cd['unidade_tramitacao_local'] = obj.unidade_tramitacao_local
self.cleaned_data['data_tramitacao'] = \
self.instance.data_tramitacao
self.cleaned_data['unidade_tramitacao_local'] = \
self.instance.unidade_tramitacao_local
return cd
return self.cleaned_data
@transaction.atomic
def save(self, commit=True):
ant_tram_principal = Tramitacao.objects.get(id=self.instance.id)
nova_tram_principal = super(TramitacaoUpdateForm, self).save(commit)
materia = nova_tram_principal.materia
materia.em_tramitacao = False if nova_tram_principal.status.indicador == "F" else True
materia.save()
lista_anexadas = lista_anexados(materia)
for ma in lista_anexadas:
tram_anexada = ma.tramitacao_set.last()
if compara_tramitacoes_mat(ant_tram_principal, tram_anexada):
tram_anexada.status = nova_tram_principal.status
tram_anexada.data_tramitacao = nova_tram_principal.data_tramitacao
tram_anexada.unidade_tramitacao_local = nova_tram_principal.unidade_tramitacao_local
tram_anexada.data_encaminhamento = nova_tram_principal.data_encaminhamento
tram_anexada.unidade_tramitacao_destino = nova_tram_principal.unidade_tramitacao_destino
tram_anexada.urgente = nova_tram_principal.urgente
tram_anexada.turno = nova_tram_principal.turno
tram_anexada.texto = nova_tram_principal.texto
tram_anexada.data_fim_prazo = nova_tram_principal.data_fim_prazo
tram_anexada.user = nova_tram_principal.user
tram_anexada.ip = nova_tram_principal.ip
tram_anexada.save()
ma.em_tramitacao = False if nova_tram_principal.status.indicador == "F" else True
ma.save()
return nova_tram_principal
class LegislacaoCitadaForm(ModelForm):
@ -790,6 +868,13 @@ class AnexadaForm(ModelForm):
cleaned_data = self.cleaned_data
data_anexacao = cleaned_data['data_anexacao']
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."))
try:
self.logger.info("Tentando obter objeto MateriaLegislativa (numero={}, ano={}, tipo={})."
.format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
@ -817,6 +902,26 @@ class AnexadaForm(ModelForm):
if is_anexada:
self.logger.error("Matéria já se encontra anexada.")
raise ValidationError(_('Matéria já se encontra anexada'))
ciclico = False
anexadas_anexada = Anexada.objects.filter(materia_principal=materia_anexada)
while anexadas_anexada and not ciclico:
anexadas = []
for anexa in anexadas_anexada:
if materia_principal == anexa.materia_anexada:
ciclico = True
else:
for a in Anexada.objects.filter(materia_principal=anexa.materia_anexada):
anexadas.append(a)
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."))
cleaned_data['materia_anexada'] = materia_anexada
@ -1481,10 +1586,10 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
'hash_code': forms.HiddenInput(), }
def __init__(self, *args, **kwargs):
self.texto_articulado_proposicao = sapl.base.models.AppConfig.attr(
self.texto_articulado_proposicao = AppConfig.attr(
'texto_articulado_proposicao')
self.receber_recibo = sapl.base.models.AppConfig.attr(
self.receber_recibo = AppConfig.attr(
'receber_recibo_proposicao')
if not self.texto_articulado_proposicao:
@ -1506,7 +1611,7 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
]
if sapl.base.models.AppConfig.objects.last().escolher_numero_materia_proposicao:
if AppConfig.objects.last().escolher_numero_materia_proposicao:
fields.append(to_column(('numero_materia_futuro', 12)),)
else:
if 'numero_materia_futuro' in self._meta.fields:
@ -1639,12 +1744,17 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
return super().save(commit)
inst.ano = timezone.now().year
numero__max = Proposicao.objects.filter(
autor=inst.autor,
ano=timezone.now().year).aggregate(Max('numero_proposicao'))
sequencia_numeracao = AppConfig.attr('sequencia_numeracao_proposicao')
if sequencia_numeracao == 'A':
numero__max = Proposicao.objects.filter(
autor=inst.autor,
ano=timezone.now().year).aggregate(Max('numero_proposicao'))
elif sequencia_numeracao == 'B':
numero__max = Proposicao.objects.filter(
ano=timezone.now().year).aggregate(Max('numero_proposicao'))
numero__max = numero__max['numero_proposicao__max']
inst.numero_proposicao = (
numero__max + 1) if numero__max else 1
numero__max + 1) if numero__max else 1
self.gerar_hash(inst, receber_recibo)
@ -1741,7 +1851,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
required=False, widget=widgets.TextInput(
attrs={'readonly': 'readonly'}))
regime_tramitacao = forms.ModelChoiceField(
regime_tramitacao = forms.ModelChoiceField(label="Regime de tramitação",
required=False, queryset=RegimeTramitacao.objects.all())
gerar_protocolo = forms.ChoiceField(
@ -1774,8 +1884,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
def __init__(self, *args, **kwargs):
self.proposicao_incorporacao_obrigatoria = \
sapl.base.models.AppConfig.attr(
'proposicao_incorporacao_obrigatoria')
AppConfig.attr('proposicao_incorporacao_obrigatoria')
if self.proposicao_incorporacao_obrigatoria != 'C':
if 'gerar_protocolo' in self._meta.fields:
@ -1807,6 +1916,10 @@ class ConfirmarProposicaoForm(ProposicaoForm):
# esta chamada isola o __init__ de ProposicaoForm
super(ProposicaoForm, self).__init__(*args, **kwargs)
if self.instance.tipo.content_type.model_class() ==\
TipoMateriaLegislativa:
self.fields['regime_tramitacao'].required = True
fields = [
Fieldset(
_('Dados Básicos'),
@ -1823,7 +1936,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
)
]
if not sapl.base.models.AppConfig.objects.last().escolher_numero_materia_proposicao or \
if not AppConfig.objects.last().escolher_numero_materia_proposicao or \
not self.instance.numero_materia_futuro:
if 'numero_materia_futuro' in self._meta.fields:
del fields[0][0][3]
@ -1903,7 +2016,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
if not self.is_valid():
return self.cleaned_data
numeracao = sapl.base.models.AppConfig.attr('sequencia_numeracao')
numeracao = AppConfig.attr('sequencia_numeracao_proposicao')
if not numeracao:
self.logger.error("A sequência de numeração (por ano ou geral)"
@ -1980,8 +2093,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
try:
self.logger.debug(
"Tentando obter modelo de sequência de numeração.")
numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao
numeracao = AppConfig.objects.last(
).sequencia_numeracao_protocolo
except AttributeError as e:
self.logger.error("Erro ao obter modelo. " + str(e))
pass
@ -2007,7 +2120,6 @@ class ConfirmarProposicaoForm(ProposicaoForm):
elif numeracao == 'U':
numero = MateriaLegislativa.objects.filter(
tipo=tipo).aggregate(Max('numero'))
if numeracao is None:
numero['numero__max'] = 0
@ -2133,7 +2245,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
GenericForeignKey
"""
numeracao = sapl.base.models.AppConfig.attr('sequencia_numeracao')
numeracao = AppConfig.attr('sequencia_numeracao_protocolo')
if numeracao == 'A':
nm = Protocolo.objects.filter(
ano=timezone.now().year).aggregate(Max('numero'))

20
sapl/materia/migrations/0045_auto_20190415_1050.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-15 13:50
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0044_auto_20190327_1409'),
]
operations = [
migrations.AlterField(
model_name='tipomaterialegislativa',
name='sequencia_numeracao',
field=models.CharField(blank=True, choices=[('A', 'Sequencial por ano para cada autor'), ('B', 'Sequencial por ano indepententemente do autor'), ('L', 'Sequencial por legislatura'), ('U', 'Sequencial único')], max_length=1, verbose_name='Sequência de numeração'),
),
]

20
sapl/materia/migrations/0046_auto_20190417_0941.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-17 12:41
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0045_auto_20190415_1050'),
]
operations = [
migrations.AlterField(
model_name='tipomaterialegislativa',
name='sequencia_numeracao',
field=models.CharField(blank=True, choices=[('A', 'Sequencial por ano para cada autor'), ('L', 'Sequencial por legislatura'), ('U', 'Sequencial único')], max_length=1, verbose_name='Sequência de numeração'),
),
]

28
sapl/materia/migrations/0046_auto_20190417_1212.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-17 15:12
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),
('materia', '0045_auto_20190415_1050'),
]
operations = [
migrations.AddField(
model_name='tramitacao',
name='ip',
field=models.CharField(blank=True, default='', max_length=30, verbose_name='IP'),
),
migrations.AddField(
model_name='tramitacao',
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'),
),
]

20
sapl/materia/migrations/0047_auto_20190417_1432.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-17 17:32
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0046_auto_20190417_0941'),
]
operations = [
migrations.AlterField(
model_name='tipomaterialegislativa',
name='sequencia_numeracao',
field=models.CharField(blank=True, choices=[('A', 'Sequencial por ano'), ('L', 'Sequencial por legislatura'), ('U', 'Sequencial único')], max_length=1, verbose_name='Sequência de numeração'),
),
]

16
sapl/materia/migrations/0048_merge_20190426_0828.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-26 11:28
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0046_auto_20190417_1212'),
('materia', '0047_auto_20190417_1432'),
]
operations = [
]

15
sapl/materia/models.py

@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
import reversion
from sapl.base.models import SEQUENCIA_NUMERACAO, Autor
from sapl.base.models import SEQUENCIA_NUMERACAO_PROTOCOLO, Autor
from sapl.comissoes.models import Comissao
from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
TextoArticulado)
@ -18,7 +18,7 @@ from sapl.parlamentares.models import Parlamentar
#from sapl.protocoloadm.models import Protocolo
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey,
SaplGenericRelation, restringe_tipos_de_arquivo_txt,
texto_upload_path)
texto_upload_path, get_settings_auth_user_model)
EM_TRAMITACAO = [(1, 'Sim'),
@ -128,7 +128,7 @@ class TipoMateriaLegislativa(models.Model):
max_length=1,
blank=True,
verbose_name=_('Sequência de numeração'),
choices=SEQUENCIA_NUMERACAO)
choices=SEQUENCIA_NUMERACAO_PROTOCOLO)
sequencia_regimental = models.PositiveIntegerField(
default=0,
@ -1003,6 +1003,15 @@ class Tramitacao(models.Model):
texto = models.TextField(verbose_name=_('Texto da Ação'))
data_fim_prazo = models.DateField(
blank=True, null=True, verbose_name=_('Data Fim Prazo'))
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 = _('Tramitação')

235
sapl/materia/tests/test_materia.py

@ -1,3 +1,4 @@
from datetime import date
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile
@ -14,10 +15,67 @@ from sapl.materia.models import (Anexada, Autoria, DespachoInicial,
StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao,
Tramitacao, UnidadeTramitacao)
from sapl.materia.forms import (TramitacaoForm, compara_tramitacoes_mat,
TramitacaoUpdateForm)
from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica)
from sapl.parlamentares.models import Legislatura
from sapl.utils import models_with_gr_for_model
from sapl.utils import models_with_gr_for_model, lista_anexados
@pytest.mark.django_db(transaction=False)
def test_lista_materias_anexadas():
tipo_materia = mommy.make(
TipoMateriaLegislativa,
descricao="Tipo_Teste"
)
regime_tramitacao = mommy.make(
RegimeTramitacao,
descricao="Regime_Teste"
)
materia_principal = mommy.make(
MateriaLegislativa,
numero=20,
ano=2018,
data_apresentacao="2018-01-04",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
)
materia_anexada = mommy.make(
MateriaLegislativa,
numero=21,
ano=2019,
data_apresentacao="2019-05-04",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
)
materia_anexada_anexada = mommy.make(
MateriaLegislativa,
numero=22,
ano=2020,
data_apresentacao="2020-01-05",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
)
mommy.make(
Anexada,
materia_principal=materia_principal,
materia_anexada=materia_anexada,
data_anexacao="2019-05-11"
)
mommy.make(
Anexada,
materia_principal=materia_anexada,
materia_anexada=materia_anexada_anexada,
data_anexacao="2020-11-05"
)
lista = lista_anexados(materia_principal)
assert len(lista) == 2
assert lista[0] == materia_anexada
assert lista[1] == materia_anexada_anexada
@pytest.mark.django_db(transaction=False)
@ -581,3 +639,178 @@ def test_numeracao_materia_legislativa_por_ano(admin_client):
response_content = eval(response.content.decode('ascii'))
esperado_outro_ano = eval('{"ano": "2010", "numero": 1}')
assert response_content['numero'] == esperado_outro_ano['numero']
@pytest.mark.django_db(transaction=False)
def test_tramitacoes_materias_anexadas(admin_client):
tipo_materia = mommy.make(
TipoMateriaLegislativa,
descricao="Tipo_Teste"
)
materia_principal = mommy.make(
MateriaLegislativa,
ano=2018,
data_apresentacao="2018-01-04",
tipo=tipo_materia
)
materia_anexada = mommy.make(
MateriaLegislativa,
ano=2019,
data_apresentacao="2019-05-04",
tipo=tipo_materia
)
materia_anexada_anexada = mommy.make(
MateriaLegislativa,
ano=2020,
data_apresentacao="2020-01-05",
tipo=tipo_materia
)
mommy.make(
Anexada,
materia_principal=materia_principal,
materia_anexada=materia_anexada,
data_anexacao="2019-05-11"
)
mommy.make(
Anexada,
materia_principal=materia_anexada,
materia_anexada=materia_anexada_anexada,
data_anexacao="2020-11-05"
)
unidade_tramitacao_local_1 = make_unidade_tramitacao(descricao="Teste 1")
unidade_tramitacao_destino_1 = make_unidade_tramitacao(descricao="Teste 2")
unidade_tramitacao_destino_2 = make_unidade_tramitacao(descricao="Teste 3")
status = mommy.make(
StatusTramitacao,
indicador='R')
# Teste criação de Tramitacao
form = TramitacaoForm(data={})
form.data = {'data_tramitacao':date(2019, 5, 6),
'unidade_tramitacao_local':unidade_tramitacao_local_1.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk,
'status':status.pk,
'urgente': False,
'texto': "Texto de teste"}
form.instance.materia_id=materia_principal.pk
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.last()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.last()
# Verifica se foram criadas as tramitações para as matérias anexadas e anexadas às anexadas
assert materia_principal.tramitacao_set.last() == tramitacao_principal
assert tramitacao_principal.materia.em_tramitacao == (tramitacao_principal.status.indicador != "F")
assert compara_tramitacoes_mat(tramitacao_principal, tramitacao_anexada)
assert MateriaLegislativa.objects.get(id=materia_anexada.pk).em_tramitacao \
== (tramitacao_anexada.status.indicador != "F")
assert compara_tramitacoes_mat(tramitacao_anexada_anexada, tramitacao_principal)
assert MateriaLegislativa.objects.get(id=materia_anexada_anexada.pk).em_tramitacao \
== (tramitacao_anexada_anexada.status.indicador != "F")
# Teste Edição de Tramitacao
form = TramitacaoUpdateForm(data={})
# Alterando unidade_tramitacao_destino
form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao,
'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk,
'status':tramitacao_principal.status.pk,
'urgente': tramitacao_principal.urgente,
'texto': tramitacao_principal.texto}
form.instance = tramitacao_principal
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.last()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.last()
assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
# Teste Remoção de Tramitacao
url = reverse('sapl.materia:tramitacao_delete',
kwargs={'pk': tramitacao_principal.pk})
response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True)
assert Tramitacao.objects.filter(id=tramitacao_principal.pk).count() == 0
assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 0
assert Tramitacao.objects.filter(id=tramitacao_anexada_anexada.pk).count() == 0
# Testes para quando as tramitações das anexadas divergem
form = TramitacaoForm(data={})
form.data = {'data_tramitacao':date(2019, 5, 6),
'unidade_tramitacao_local':unidade_tramitacao_local_1.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk,
'status':status.pk,
'urgente': False,
'texto': "Texto de teste"}
form.instance.materia_id=materia_principal.pk
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.last()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.last()
form = TramitacaoUpdateForm(data={})
# Alterando unidade_tramitacao_destino
form.data = {'data_tramitacao':tramitacao_anexada.data_tramitacao,
'unidade_tramitacao_local':tramitacao_anexada.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk,
'status':tramitacao_anexada.status.pk,
'urgente': tramitacao_anexada.urgente,
'texto': tramitacao_anexada.texto}
form.instance = tramitacao_anexada
assert form.is_valid()
tramitacao_anexada = form.save()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.last()
assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_1
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
# Editando a tramitação principal, as tramitações anexadas não devem ser editadas
form = TramitacaoUpdateForm(data={})
# Alterando o texto
form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao,
'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':tramitacao_principal.unidade_tramitacao_destino.pk,
'status':tramitacao_principal.status.pk,
'urgente': tramitacao_principal.urgente,
'texto': "Testando a alteração"}
form.instance = tramitacao_principal
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = materia_anexada.tramitacao_set.last()
tramitacao_anexada_anexada = materia_anexada_anexada.tramitacao_set.last()
assert tramitacao_principal.texto == "Testando a alteração"
assert not tramitacao_anexada.texto == "Testando a alteração"
assert not tramitacao_anexada_anexada.texto == "Testando a alteração"
# Removendo a tramitação pricipal, as tramitações anexadas não devem ser removidas, pois divergiram
url = reverse('sapl.materia:tramitacao_delete',
kwargs={'pk': tramitacao_principal.pk})
response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True)
assert Tramitacao.objects.filter(id=tramitacao_principal.pk).count() == 0
assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 1
assert Tramitacao.objects.filter(id=tramitacao_anexada_anexada.pk).count() == 1
# Removendo a tramitação anexada, a tramitação anexada à anexada deve ser removida
url = reverse('sapl.materia:tramitacao_delete',
kwargs={'pk': tramitacao_anexada.pk})
response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True)
assert Tramitacao.objects.filter(id=tramitacao_anexada.pk).count() == 0
assert Tramitacao.objects.filter(id=tramitacao_anexada_anexada.pk).count() == 0

144
sapl/materia/views.py

@ -53,9 +53,9 @@ from sapl.parlamentares.models import Legislatura
from sapl.protocoloadm.models import Protocolo
from sapl.settings import MEDIA_ROOT
from sapl.utils import (YES_NO_CHOICES, autor_label, autor_modal, SEPARADOR_HASH_PROPOSICAO,
gerar_hash_arquivo, get_base_url,
gerar_hash_arquivo, get_base_url, get_client_ip,
get_mime_type_from_file_extension, montar_row_autor,
show_results_filter_set, mail_service_configured)
show_results_filter_set, mail_service_configured, lista_anexados)
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
AnexadaEmLoteFilterSet,
@ -69,7 +69,7 @@ from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
filtra_tramitacao_destino,
filtra_tramitacao_destino_and_status,
filtra_tramitacao_status,
ExcluirTramitacaoEmLote)
ExcluirTramitacaoEmLote, compara_tramitacoes_mat)
from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria,
DespachoInicial, DocumentoAcessorio, MateriaAssunto,
MateriaLegislativa, Numeracao, Orgao, Origem, Proposicao,
@ -333,7 +333,7 @@ def recuperar_materia(request):
logger.debug("user=" + username +
". Tentando obter numeração da matéria.")
numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao
).sequencia_numeracao_proposicao
except AttributeError as e:
logger.error("user=" + username + ". " + str(e) +
" Numeracao da matéria definida como None.")
@ -1189,6 +1189,8 @@ class TramitacaoCrud(MasterDetailCrud):
else:
initial['unidade_tramitacao_local'] = ''
initial['data_tramitacao'] = timezone.now().date()
initial['ip'] = get_client_ip(self.request)
initial['user'] = self.request.user
return initial
def get_context_data(self, **kwargs):
@ -1201,6 +1203,7 @@ class TramitacaoCrud(MasterDetailCrud):
'-timestamp',
'-id').first()
#TODO: Esta checagem foi inserida na issue #2027, mas é mesmo necessária?
if ultima_tramitacao:
if ultima_tramitacao.unidade_tramitacao_destino:
context['form'].fields[
@ -1214,6 +1217,15 @@ class TramitacaoCrud(MasterDetailCrud):
' da última tramitação não pode ser vazia!')
messages.add_message(self.request, messages.ERROR, msg)
primeira_tramitacao = not(Tramitacao.objects.filter(
materia_id=int(kwargs['root_pk'])).exists())
# Se não for a primeira tramitação daquela matéria, o campo
# não pode ser modificado
if not primeira_tramitacao:
context['form'].fields[
'unidade_tramitacao_local'].widget.attrs['disabled'] = True
return context
def form_valid(self, form):
@ -1221,12 +1233,6 @@ class TramitacaoCrud(MasterDetailCrud):
self.object = form.save()
username = self.request.user.username
if form.instance.status.indicador == 'F':
form.instance.materia.em_tramitacao = False
else:
form.instance.materia.em_tramitacao = True
form.instance.materia.save()
try:
self.logger.debug("user=" + username + ". Tentando enviar Tramitacao (sender={}, post={}, request={})."
.format(Tramitacao, self.object, self.request))
@ -1234,7 +1240,6 @@ class TramitacaoCrud(MasterDetailCrud):
post=self.object,
request=self.request)
except Exception as e:
# TODO log error
msg = _('Tramitação criada, mas e-mail de acompanhamento '
'de matéria não enviado. Há problemas na configuração '
'do e-mail.')
@ -1251,16 +1256,16 @@ class TramitacaoCrud(MasterDetailCrud):
layout_key = 'TramitacaoUpdate'
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial['ip'] = get_client_ip(self.request)
initial['user'] = self.request.user
return initial
def form_valid(self, form):
self.object = form.save()
username = self.request.user.username
if form.instance.status.indicador == 'F':
form.instance.materia.em_tramitacao = False
else:
form.instance.materia.em_tramitacao = True
form.instance.materia.save()
try:
self.logger.debug("user=" + username + ". Tentando enviar Tramitacao (sender={}, post={}, request={}"
.format(Tramitacao, self.object, self.request))
@ -1268,7 +1273,6 @@ class TramitacaoCrud(MasterDetailCrud):
post=self.object,
request=self.request)
except Exception:
# TODO log error
msg = _('Tramitação atualizada, mas e-mail de acompanhamento '
'de matéria não enviado. Há problemas na configuração '
'do e-mail.')
@ -1294,18 +1298,17 @@ class TramitacaoCrud(MasterDetailCrud):
def delete(self, request, *args, **kwargs):
tramitacao = Tramitacao.objects.get(id=self.kwargs['pk'])
materia = MateriaLegislativa.objects.get(id=tramitacao.materia.id)
materia = tramitacao.materia
url = reverse('sapl.materia:tramitacao_list',
kwargs={'pk': tramitacao.materia.id})
kwargs={'pk': materia.id})
ultima_tramitacao = materia.tramitacao_set.order_by(
'-data_tramitacao',
'-timestamp',
'-id').first()
username = request.user.username
if tramitacao.pk != ultima_tramitacao.pk:
username = request.user.username
self.logger.error("user=" + username + ". Não é possível deletar a tramitação de pk={}. "
"Somente a última tramitação (pk={}) pode ser deletada!."
.format(tramitacao.pk, ultima_tramitacao.pk))
@ -1313,9 +1316,24 @@ class TramitacaoCrud(MasterDetailCrud):
messages.add_message(request, messages.ERROR, msg)
return HttpResponseRedirect(url)
else:
tramitacao.delete()
tramitacoes_deletar = [tramitacao.id]
mat_anexadas = lista_anexados(materia)
for ma in mat_anexadas:
tram_anexada = ma.tramitacao_set.last()
if compara_tramitacoes_mat(tram_anexada, tramitacao):
tramitacoes_deletar.append(tram_anexada.id)
Tramitacao.objects.filter(id__in=tramitacoes_deletar).delete()
return HttpResponseRedirect(url)
class DetailView(MasterDetailCrud.DetailView):
template_name = "materia/tramitacao_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user'] = self.request.user
return context
def montar_helper_documento_acessorio(self):
autor_row = montar_row_autor('autor')
@ -1586,6 +1604,20 @@ class MateriaLegislativaCrud(Crud):
form_class = MateriaLegislativaForm
def form_valid(self, form):
self.object = form.save()
username = self.request.user.username
if Anexada.objects.filter(materia_principal=self.kwargs['pk']).exists():
materia = MateriaLegislativa.objects.get(pk=self.kwargs['pk'])
anexadas = lista_anexados(materia)
for anexada in anexadas:
anexada.em_tramitacao = True if form.instance.em_tramitacao else False
anexada.save()
return super().form_valid(form)
@property
def cancel_url(self):
return self.search_url
@ -2070,11 +2102,39 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
qr = self.request.GET.copy()
context['object_list'] = context['object_list'].order_by(
'ano', 'numero')
'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)
context['temp_object_list'] = context['object_list']
context['object_list'] = []
for obj in context['temp_object_list']:
materia_anexada = obj
ciclico = False
anexadas_anexada = Anexada.objects.filter(
materia_principal = materia_anexada
)
while anexadas_anexada and not ciclico:
anexadas = []
for anexa in anexadas_anexada:
if principal == anexa.materia_anexada:
ciclico = True
else:
for a in Anexada.objects.filter(materia_principal=anexa.materia_anexada):
anexadas.append(a)
anexadas_anexada = anexadas
if not ciclico:
context['object_list'].append(obj)
context['numero_res'] = len(context['object_list'])
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
@ -2084,19 +2144,31 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
def post(self, request, *args, **kwargs):
marcadas = request.POST.getlist('materia_id')
if len(marcadas) == 0:
msg = _('Nenhuma máteria foi selecionada.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
data_anexacao = datetime.strptime(
request.POST['data_anexacao'], "%d/%m/%Y").date()
if request.POST['data_desanexacao'] == '':
data_desanexacao = None
v_data_desanexacao = data_anexacao
else:
data_desanexacao = datetime.strptime(
request.POST['data_desanexacao'], "%d/%m/%Y").date()
v_data_desanexacao = data_desanexacao
if len(marcadas) == 0:
msg = _('Nenhuma máteria foi selecionada.')
messages.add_message(request, messages.ERROR, msg)
if data_anexacao > v_data_desanexacao:
msg = _('Data de anexação posterior à data de desanexação.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
if data_anexacao > v_data_desanexacao:
msg = _('Data de anexação posterior à data de desanexação.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
principal = MateriaLegislativa.objects.get(pk=kwargs['pk'])
for materia in MateriaLegislativa.objects.filter(id__in=marcadas):
@ -2108,9 +2180,11 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
anexada.data_desanexacao = data_desanexacao
anexada.save()
msg = _('Materia(s) anexada(s).')
msg = _('Matéria(s) anexada(s).')
messages.add_message(request, messages.SUCCESS, msg)
return self.get(request, self.kwargs)
sucess_url = reverse('sapl_index') + 'materia/' + kwargs['pk'] + '/anexada'
return HttpResponseRedirect(sucess_url)
class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
@ -2231,6 +2305,8 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
user = request.user
ip = get_client_ip(request)
t = Tramitacao(
materia=materia,
data_tramitacao=data_tramitacao,
@ -2243,7 +2319,9 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
urgente=urgente,
status_id=request.POST['status'],
turno=request.POST['turno'],
texto=request.POST['texto']
texto=request.POST['texto'],
user=user,
ip=ip
)
t.save()
try:

19
sapl/norma/migrations/0024_auto_20190425_0917.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-25 12:17
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('norma', '0023_auto_20190219_1535'),
]
operations = [
migrations.AlterModelOptions(
name='normarelacionada',
options={'ordering': ('norma_principal__data', 'norma_relacionada__data'), 'verbose_name': 'Norma Relacionada', 'verbose_name_plural': 'Normas Relacionadas'},
),
]

10
sapl/norma/models.py

@ -145,11 +145,11 @@ class NormaJuridica(models.Model):
def get_normas_relacionadas(self):
principais = NormaRelacionada.objects.filter(
norma_principal=self.id).order_by('norma_principal__ano',
'norma_relacionada__ano')
norma_principal=self.id).order_by('norma_principal__data',
'norma_relacionada__data')
relacionadas = NormaRelacionada.objects.filter(
norma_relacionada=self.id).order_by('norma_principal__ano',
'norma_relacionada__ano')
norma_relacionada=self.id).order_by('norma_principal__data',
'norma_relacionada__data')
return (principais, relacionadas)
def get_anexos_norma_juridica(self):
@ -313,7 +313,7 @@ class NormaRelacionada(models.Model):
class Meta:
verbose_name = _('Norma Relacionada')
verbose_name_plural = _('Normas Relacionadas')
ordering = ('norma_principal__ano', 'norma_relacionada__ano')
ordering = ('norma_principal__data', 'norma_relacionada__data')
def __str__(self):
return _('Principal: %(norma_principal)s'

52
sapl/parlamentares/forms.py

@ -23,7 +23,7 @@ from sapl.rules import SAPL_GROUP_VOTANTE
import django_filters
from .models import (ComposicaoColigacao, Filiacao, Frente, Legislatura,
Mandato, Parlamentar, Votante)
Mandato, Parlamentar, Votante, Bloco)
class ImageThumbnailFileInput(ClearableFileInput):
@ -243,6 +243,21 @@ class ParlamentarCreateForm(ParlamentarForm):
attrs={'id': 'texto-rico'})
}
def clean(self):
super().clean()
if not self.is_valid():
return self.cleaned_data
cleaned_data = self.cleaned_data
parlamentar = Parlamentar.objects.filter(nome_parlamentar=cleaned_data['nome_parlamentar']).exists()
if parlamentar:
self.logger.error('Parlamentar já cadastrado.')
raise ValidationError('Parlamentar já cadastrado.')
return cleaned_data
@transaction.atomic
def save(self, commit=True):
parlamentar = super(ParlamentarCreateForm, self).save(commit)
@ -568,3 +583,38 @@ class VincularParlamentarForm(forms.Form):
raise ValidationError(_('Data da Expedição do Diploma deve ser anterior a data de início da Legislatura.'))
return cleaned_data
class BlocoForm(ModelForm):
class Meta:
model = Bloco
fields = ['nome', 'partidos', 'data_criacao',
'data_extincao', 'descricao']
def clean(self):
super(BlocoForm, self).clean()
if not self.is_valid():
return self.cleaned_data
if self.cleaned_data['data_extincao']:
if (self.cleaned_data['data_extincao'] <
self.cleaned_data['data_criacao']):
msg = _('Data de extinção não pode ser menor que a de criação')
raise ValidationError(msg)
return self.cleaned_data
@transaction.atomic
def save(self, commit=True):
bloco = super(BlocoForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Bloco)
object_id = bloco.pk
tipo = TipoAutor.objects.get(content_type=content_type)
Autor.objects.create(
content_type=content_type,
object_id=object_id,
tipo=tipo,
nome=bloco.nome
)
return bloco

37
sapl/parlamentares/migrations/0026_bloco.py

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-30 11:28
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('parlamentares', '0025_auto_20180924_1724'),
('sessao', '0039_auto_20190430_0825')
]
state_operations = [
migrations.CreateModel(
name='Bloco',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nome', models.CharField(max_length=80, verbose_name='Nome do Bloco')),
('data_criacao', models.DateField(null=True, verbose_name='Data Criação')),
('data_extincao', models.DateField(blank=True, null=True, verbose_name='Data Dissolução')),
('descricao', models.TextField(blank=True, verbose_name='Descrição')),
('partidos', models.ManyToManyField(blank=True, to='parlamentares.Partido', verbose_name='Partidos')),
],
options={
'db_table': 'parlamentares_bloco',
'verbose_name': 'Bloco Parlamentar',
'verbose_name_plural': 'Blocos Parlamentares',
},
bases=(models.Model,),
),
]
operations = [
migrations.SeparateDatabaseAndState(state_operations=state_operations)
]

19
sapl/parlamentares/migrations/0027_auto_20190430_0839.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-30 11:39
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('parlamentares', '0026_bloco'),
]
operations = [
migrations.AlterModelTable(
name='bloco',
table=None,
),
]

34
sapl/parlamentares/models.py

@ -568,3 +568,37 @@ class Votante(models.Model):
def __str__(self):
return self.user.username
@reversion.register()
class Bloco(models.Model):
'''
* blocos podem existir por mais de uma legislatura
'''
nome = models.CharField(
max_length=80, verbose_name=_('Nome do Bloco'))
partidos = models.ManyToManyField(
Partido, blank=True, verbose_name=_('Partidos'))
data_criacao = models.DateField(
blank=False, null=True, verbose_name=_('Data Criação'))
data_extincao = models.DateField(
blank=True, null=True, verbose_name=_('Data Dissolução'))
descricao = models.TextField(blank=True, verbose_name=_('Descrição'))
# campo conceitual de reversão genérica para o model Autor que dá a
# o meio possível de localização de tipos de autores.
autor = SaplGenericRelation(Autor,
related_query_name='bloco_set',
fields_search=(
('nome', '__icontains'),
('descricao', '__icontains'),
('partidos__sigla', '__icontains'),
('partidos__nome', '__icontains'),
))
class Meta:
verbose_name = _('Bloco Parlamentar')
verbose_name_plural = _('Blocos Parlamentares')
def __str__(self):
return self.nome

8
sapl/parlamentares/urls.py

@ -18,7 +18,7 @@ from sapl.parlamentares.views import (CargoMesaCrud, ColigacaoCrud,
insere_parlamentar_composicao,
parlamentares_frente_selected,
remove_parlamentar_composicao,
parlamentares_filiados,
parlamentares_filiados, BlocoCrud,
PesquisarParlamentarView, VincularParlamentarView)
from .apps import AppConfig
@ -46,8 +46,9 @@ urlpatterns = [
url(r'^sistema/coligacao/',
include(ColigacaoCrud.get_urls() +
ComposicaoColigacaoCrud.get_urls())),
ComposicaoColigacaoCrud.get_urls())),
url(r'^sistema/bloco/',
include(BlocoCrud.get_urls())),
url(r'^sistema/frente/',
include(FrenteCrud.get_urls())),
url(r'^sistema/frente/atualiza-lista-parlamentares',
@ -89,4 +90,5 @@ urlpatterns = [
url(r'^mesa-diretora/remove-parlamentar-composicao/$',
remove_parlamentar_composicao, name='remove_parlamentar_composicao'),
]

43
sapl/parlamentares/views.py

@ -34,11 +34,15 @@ from sapl.parlamentares.apps import AppConfig
from sapl.utils import (parlamentares_ativos, show_results_filter_set)
from .forms import (FiliacaoForm, FrenteForm, LegislaturaForm, MandatoForm,
ParlamentarCreateForm, ParlamentarForm, VotanteForm, ParlamentarFilterSet, VincularParlamentarForm)
ParlamentarCreateForm, ParlamentarForm, VotanteForm,
ParlamentarFilterSet, VincularParlamentarForm,
BlocoForm)
from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa,
Dependente, Filiacao, Frente, Legislatura, Mandato,
NivelInstrucao, Parlamentar, Partido, SessaoLegislativa,
SituacaoMilitar, TipoAfastamento, TipoDependente, Votante)
SituacaoMilitar, TipoAfastamento, TipoDependente, Votante,
Bloco)
CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa')
@ -249,7 +253,6 @@ class ParticipacaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
comissoes = []
for p in object_list:
if p.cargo.nome != 'Relator':
comissao = [
(p.composicao.comissao.nome, reverse(
'sapl.comissoes:comissao_detail', kwargs={
@ -1112,16 +1115,20 @@ def altera_field_mesa_public_view(request):
partido_parlamentar_sessao_legislativa(sessao,
parlamentar))
if parlamentar.fotografia:
thumbnail_url = get_backend().get_thumbnail_url(
parlamentar.fotografia,
{
'size': (128, 128),
'box': parlamentar.cropping,
'crop': True,
'detail': True,
}
)
lista_fotos.append(thumbnail_url)
try:
thumbnail_url = get_backend().get_thumbnail_url(
parlamentar.fotografia,
{
'size': (128, 128),
'box': parlamentar.cropping,
'crop': True,
'detail': True,
}
)
lista_fotos.append(thumbnail_url)
except Exception as e:
logger.error(e)
logger.error('erro processando arquivo: %s' % parlamentar.fotografia.path)
else:
lista_fotos.append(None)
@ -1160,3 +1167,13 @@ class VincularParlamentarView(PermissionRequiredMixin, FormView):
mandato.save()
return HttpResponseRedirect(self.get_success_url())
class BlocoCrud(CrudAux):
model = Bloco
class CreateView(CrudAux.CreateView):
form_class = BlocoForm
def get_success_url(self):
return reverse('sapl.parlamentares:bloco_list')

265
sapl/protocoloadm/forms.py

@ -7,7 +7,7 @@ from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Div
from django import forms
from django.core.exceptions import (MultipleObjectsReturned,
ObjectDoesNotExist, ValidationError)
from django.db import models
from django.db import models, transaction
from django.db.models import Max
from django.forms import ModelForm
from django.utils import timezone
@ -24,12 +24,12 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter,
choice_anos_com_protocolo, choice_force_optional,
choice_anos_com_documentoadministrativo,
FilterOverridesMetaMixin, choice_anos_com_materias,
FileFieldCheckMixin)
FileFieldCheckMixin, lista_anexados)
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo)
TramitacaoAdministrativo, Anexado)
TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'),
@ -221,7 +221,7 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
)
class AnularProcoloAdmForm(ModelForm):
class AnularProtocoloAdmForm(ModelForm):
logger = logging.getLogger(__name__)
@ -240,7 +240,7 @@ class AnularProcoloAdmForm(ModelForm):
widget=forms.Textarea)
def clean(self):
super(AnularProcoloAdmForm, self).clean()
super(AnularProtocoloAdmForm, self).clean()
cleaned_data = self.cleaned_data
@ -313,7 +313,7 @@ class AnularProcoloAdmForm(ModelForm):
form_actions(label='Anular')
)
)
super(AnularProcoloAdmForm, self).__init__(
super(AnularProtocoloAdmForm, self).__init__(
*args, **kwargs)
@ -650,11 +650,29 @@ class TramitacaoAdmForm(ModelForm):
fields = ['data_tramitacao',
'unidade_tramitacao_local',
'status',
'urgente',
'unidade_tramitacao_destino',
'data_encaminhamento',
'data_fim_prazo',
'texto',
]
'user',
'ip']
widgets = {'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
super(TramitacaoAdmForm, 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? *"
def clean(self):
cleaned_data = super(TramitacaoAdmForm, self).clean()
@ -727,6 +745,51 @@ class TramitacaoAdmForm(ModelForm):
return self.cleaned_data
@transaction.atomic
def save(self, commit=True):
tramitacao = super(TramitacaoAdmForm, self).save(commit)
documento = tramitacao.documento
documento.tramitacao = False if tramitacao.status.indicador == "F" else True
documento.save()
lista_tramitacao = []
list_anexados = lista_anexados(documento, False)
for da in list_anexados:
if not da.tramitacaoadministrativo_set.all() \
or da.tramitacaoadministrativo_set.last() \
.unidade_tramitacao_destino == tramitacao.unidade_tramitacao_local:
da.tramitacao = False if tramitacao.status.indicador == "F" else True
da.save()
lista_tramitacao.append(TramitacaoAdministrativo(
status=tramitacao.status,
documento=da,
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,
texto=tramitacao.texto,
data_fim_prazo=tramitacao.data_fim_prazo,
user=tramitacao.user,
ip=tramitacao.ip
))
TramitacaoAdministrativo.objects.bulk_create(lista_tramitacao)
return tramitacao
# Compara se os campos de duas tramitações são iguais,
# exceto os campos id, documento_id e timestamp
def compara_tramitacoes_doc(tramitacao1, tramitacao2):
if not tramitacao1 or not tramitacao2:
return False
lst_items = ['id', 'documento_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] != '_')]
return values == other_values
class TramitacaoAdmEditForm(TramitacaoAdmForm):
@ -742,12 +805,16 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm):
model = TramitacaoAdministrativo
fields = ['data_tramitacao',
'unidade_tramitacao_local',
'status',
'status',
'urgente',
'unidade_tramitacao_destino',
'data_encaminhamento',
'data_fim_prazo',
'texto',
]
'user',
'ip']
widgets = {'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
def clean(self):
super(TramitacaoAdmEditForm, self).clean()
@ -755,30 +822,190 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm):
if not self.is_valid():
return self.cleaned_data
cd = self.cleaned_data
obj = self.instance
ultima_tramitacao = TramitacaoAdministrativo.objects.filter(
documento_id=self.instance.documento_id).order_by(
documento_id=obj.documento_id).order_by(
'-data_tramitacao',
'-id').first()
# Se a Tramitação que está sendo editada não for a mais recente,
# ela não pode ter seu destino alterado.
if ultima_tramitacao != self.instance:
if self.cleaned_data['unidade_tramitacao_destino'] != \
self.instance.unidade_tramitacao_destino:
if ultima_tramitacao != obj:
if cd['unidade_tramitacao_destino'] != \
obj.unidade_tramitacao_destino:
self.logger.error('Você não pode mudar a Unidade de Destino desta '
'tramitação (id={}), pois irá conflitar com a Unidade '
'Local da tramitação seguinte'.format(self.instance.documento_id))
'Local da tramitação seguinte'.format(obj.documento_id))
raise ValidationError(
'Você não pode mudar a Unidade de Destino desta '
'tramitação, pois irá conflitar com a Unidade '
'Local da tramitação seguinte')
self.cleaned_data['data_tramitacao'] = \
self.instance.data_tramitacao
self.cleaned_data['unidade_tramitacao_local'] = \
self.instance.unidade_tramitacao_local
# Se não houve qualquer alteração em um dos dados, mantém o usuário e ip
if not (cd['data_tramitacao'] != obj.data_tramitacao or \
cd['unidade_tramitacao_destino'] != obj.unidade_tramitacao_destino or \
cd['status'] != obj.status or cd['texto'] != obj.texto or \
cd['data_encaminhamento'] != obj.data_encaminhamento or \
cd['data_fim_prazo'] != obj.data_fim_prazo):
cd['user'] = obj.user
cd['ip'] = obj.ip
return self.cleaned_data
cd['data_tramitacao'] = obj.data_tramitacao
cd['unidade_tramitacao_local'] = obj.unidade_tramitacao_local
return cd
@transaction.atomic
def save(self, commit=True):
# tram_principal = super(TramitacaoAdmEditForm, self).save(commit)
ant_tram_principal = TramitacaoAdministrativo.objects.get(id=self.instance.id)
nova_tram_principal = super(TramitacaoAdmEditForm, self).save(commit)
documento = nova_tram_principal.documento
documento.tramitacao = False if nova_tram_principal.status.indicador == "F" else True
documento.save()
list_anexados = lista_anexados(documento, False)
for da in list_anexados:
tram_anexada = da.tramitacaoadministrativo_set.last()
if compara_tramitacoes_doc(ant_tram_principal, tram_anexada):
tram_anexada.status = nova_tram_principal.status
tram_anexada.data_tramitacao = nova_tram_principal.data_tramitacao
tram_anexada.unidade_tramitacao_local = nova_tram_principal.unidade_tramitacao_local
tram_anexada.data_encaminhamento = nova_tram_principal.data_encaminhamento
tram_anexada.unidade_tramitacao_destino = nova_tram_principal.unidade_tramitacao_destino
tram_anexada.urgente = nova_tram_principal.urgente
tram_anexada.texto = nova_tram_principal.texto
tram_anexada.data_fim_prazo = nova_tram_principal.data_fim_prazo
tram_anexada.user = nova_tram_principal.user
tram_anexada.ip = nova_tram_principal.ip
tram_anexada.save()
da.tramitacao = False if nova_tram_principal.status.indicador == "F" else True
da.save()
return nova_tram_principal
class AnexadoForm(ModelForm):
logger = logging.getLogger(__name__)
tipo = forms.ModelChoiceField(
label='Tipo',
required=True,
queryset=TipoDocumentoAdministrativo.objects.all(),
empty_label='Selecione'
)
numero = forms.CharField(label='Número', required=True)
ano = forms.CharField(label='Ano', required=True)
def __init__(self, *args, **kwargs):
return super(AnexadoForm, self).__init__(*args, **kwargs)
def clean(self):
super(AnexadoForm, self).clean()
if not self.is_valid():
return self.cleaned_data
cleaned_data = self.cleaned_data
data_anexacao = cleaned_data['data_anexacao']
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."))
try:
self.logger.info(
"Tentando obter objeto DocumentoAdministrativo (numero={}, ano={}, tipo={})."
.format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo'])
)
documento_anexado = DocumentoAdministrativo.objects.get(
numero=cleaned_data['numero'],
ano=cleaned_data['ano'],
tipo=cleaned_data['tipo']
)
except ObjectDoesNotExist:
msg = _('O {} {}/{} não existe no cadastro de documentos administrativos.'
.format(cleaned_data['tipo'], cleaned_data['numero'], cleaned_data['ano']))
self.logger.error("O documento a ser anexado não existe no cadastro"
" de documentos administrativos")
raise ValidationError(msg)
documento_principal = self.instance.documento_principal
if documento_principal == documento_anexado:
self.logger.error("O documento não pode ser anexado a si mesmo.")
raise ValidationError(_("O documento não pode ser anexado a si mesmo"))
is_anexado = Anexado.objects.filter(documento_principal=documento_principal,
documento_anexado=documento_anexado
).exclude(pk=self.instance.pk).exists()
if is_anexado:
self.logger.error("Documento já se encontra anexado.")
raise ValidationError(_('Documento já se encontra anexado'))
ciclico = False
anexados_anexado = Anexado.objects.filter(documento_principal=documento_anexado)
while(anexados_anexado and not ciclico):
anexados = []
for anexo in anexados_anexado:
if documento_principal == anexo.documento_anexado:
ciclico = True
else:
for a in Anexado.objects.filter(documento_principal=anexo.documento_anexado):
anexados.append(a)
anexados_anexado = anexados
if ciclico:
self.logger.error("O documento não pode ser anexado por um de seus anexados.")
raise ValidationError(_('O documento não pode ser anexado por um de seus anexados'))
cleaned_data['documento_anexado'] = documento_anexado
return cleaned_data
def save(self, commit=False):
anexado = super(AnexadoForm, self).save(commit)
anexado.documento_anexado = self.cleaned_data['documento_anexado']
anexado.save()
return anexado
class Meta:
model = Anexado
fields = ['tipo', 'numero', 'ano', 'data_anexacao', 'data_desanexacao']
class AnexadoEmLoteFilterSet(django_filters.FilterSet):
class Meta(FilterOverridesMetaMixin):
model = DocumentoAdministrativo
fields = ['tipo', 'data']
def __init__(self, *args, **kwargs):
super(AnexadoEmLoteFilterSet, self).__init__(*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Documento*'
self.filters['data'].label = 'Data (Inicial - Final)*'
row1 = to_row([('tipo', 12)])
row2 = to_row([('data', 12)])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Documentos'),
row1, row2, form_actions(label='Pesquisar'))
)
class DocumentoAdministrativoForm(FileFieldCheckMixin, ModelForm):

35
sapl/protocoloadm/migrations/0018_auto_20190314_1532.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-03-14 18:32
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0017_merge_20190121_1552'),
]
operations = [
migrations.CreateModel(
name='Anexado',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('data_anexacao', models.DateField(verbose_name='Data Anexação')),
('data_desanexacao', models.DateField(blank=True, null=True, verbose_name='Data Desanexação')),
('documento_anexado', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documento_anexado_set', to='protocoloadm.DocumentoAdministrativo', verbose_name='Documento Anexado')),
('documento_principal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documento_principal_set', to='protocoloadm.DocumentoAdministrativo', verbose_name='Documento Principal')),
],
options={
'verbose_name': 'Anexado',
'verbose_name_plural': 'Anexados',
},
),
migrations.AddField(
model_name='documentoadministrativo',
name='anexados',
field=models.ManyToManyField(blank=True, related_name='anexo_de', through='protocoloadm.Anexado', to='protocoloadm.DocumentoAdministrativo'),
),
]

28
sapl/protocoloadm/migrations/0019_auto_20190426_0833.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-26 11:33
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),
('protocoloadm', '0018_auto_20190314_1532'),
]
operations = [
migrations.AddField(
model_name='tramitacaoadministrativo',
name='ip',
field=models.CharField(blank=True, default='', max_length=30, verbose_name='IP'),
),
migrations.AddField(
model_name='tramitacaoadministrativo',
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'),
),
]

25
sapl/protocoloadm/migrations/0019_auto_20190429_0828.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-29 11:28
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0018_auto_20190314_1532'),
]
operations = [
migrations.AddField(
model_name='tramitacaoadministrativo',
name='urgente',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Urgente ?'),
),
migrations.AlterField(
model_name='tramitacaoadministrativo',
name='texto',
field=models.TextField(verbose_name='Texto da Ação'),
),
]

21
sapl/protocoloadm/migrations/0020_tramitacaoadministrativo_timestamp.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-29 12:15
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0019_auto_20190429_0828'),
]
operations = [
migrations.AddField(
model_name='tramitacaoadministrativo',
name='timestamp',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

16
sapl/protocoloadm/migrations/0021_merge_20190429_1531.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-29 18:31
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0020_tramitacaoadministrativo_timestamp'),
('protocoloadm', '0019_auto_20190426_0833'),
]
operations = [
]

61
sapl/protocoloadm/models.py

@ -6,7 +6,8 @@ import reversion
from sapl.base.models import Autor
from sapl.materia.models import TipoMateriaLegislativa, UnidadeTramitacao
from sapl.utils import RANGE_ANOS, YES_NO_CHOICES, texto_upload_path
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, texto_upload_path,
get_settings_auth_user_model)
@reversion.register()
@ -170,6 +171,18 @@ class DocumentoAdministrativo(models.Model):
verbose_name=_('Acesso Restrito'),
blank=True)
anexados = models.ManyToManyField(
'self',
blank=True,
through='Anexado',
symmetrical=False,
related_name='anexo_de',
through_fields=(
'documento_principal',
'documento_anexado'
)
)
class Meta:
verbose_name = _('Documento Administrativo')
verbose_name_plural = _('Documentos Administrativos')
@ -288,6 +301,7 @@ class TramitacaoAdministrativo(models.Model):
verbose_name=_('Status'))
documento = models.ForeignKey(DocumentoAdministrativo,
on_delete=models.PROTECT)
timestamp = models.DateTimeField(default=timezone.now)
data_tramitacao = models.DateField(
verbose_name=_('Data Tramitação'))
unidade_tramitacao_local = models.ForeignKey(
@ -302,10 +316,21 @@ class TramitacaoAdministrativo(models.Model):
related_name='adm_tramitacoes_destino',
on_delete=models.PROTECT,
verbose_name=_('Unidade Destino'))
texto = models.TextField(
blank=True, verbose_name=_('Texto da Ação'))
urgente = models.BooleanField(verbose_name=_('Urgente ?'),
choices=YES_NO_CHOICES,
default=False)
texto = models.TextField(verbose_name=_('Texto da Ação'))
data_fim_prazo = models.DateField(
blank=True, null=True, verbose_name=_('Data Fim do Prazo'))
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 = _('Tramitação de Documento Administrativo')
@ -317,6 +342,36 @@ class TramitacaoAdministrativo(models.Model):
}
@reversion.register()
class Anexado(models.Model):
documento_principal = models.ForeignKey(
DocumentoAdministrativo, related_name='documento_principal_set',
on_delete = models.CASCADE,
verbose_name=_('Documento Principal')
)
documento_anexado = models.ForeignKey(
DocumentoAdministrativo, related_name='documento_anexado_set',
on_delete = models.CASCADE,
verbose_name=_('Documento Anexado')
)
data_anexacao = models.DateField(verbose_name=_('Data Anexação'))
data_desanexacao = models.DateField(
blank=True, null=True, verbose_name=_('Data Desanexação')
)
class Meta:
verbose_name = _('Anexado')
verbose_name_plural = _('Anexados')
def __str__(self):
return _('Anexado: %(documento_anexado_tipo)s %(documento_anexado_numero)s'
'/%(documento_anexado_ano)s\n') % {
'documento_anexado_tipo': self.documento_anexado.tipo,
'documento_anexado_numero': self.documento_anexado.numero,
'documento_anexado_ano': self.documento_anexado.ano
}
@reversion.register()
class AcompanhamentoDocumento(models.Model):
usuario = models.CharField(max_length=50)

298
sapl/protocoloadm/tests/test_protocoloadm.py

@ -8,16 +8,20 @@ from model_mommy import mommy
import pytest
from sapl.base.models import AppConfig
from sapl.comissoes.models import Comissao, TipoComissao
from sapl.materia.models import UnidadeTramitacao
from sapl.protocoloadm.forms import (AnularProcoloAdmForm,
from sapl.protocoloadm.forms import (AnularProtocoloAdmForm,
DocumentoAdministrativoForm,
MateriaLegislativa, ProtocoloDocumentForm,
ProtocoloMateriaForm)
ProtocoloMateriaForm, TramitacaoAdmForm,
TramitacaoAdmEditForm,
compara_tramitacoes_doc)
from sapl.protocoloadm.models import (DocumentoAdministrativo, Protocolo,
StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo,
TipoMateriaLegislativa,
TipoMateriaLegislativa, Anexado,
TramitacaoAdministrativo)
from sapl.utils import lista_anexados
@pytest.mark.django_db(transaction=False)
@ -51,7 +55,7 @@ def test_anular_protocolo_submit(admin_client):
@pytest.mark.django_db(transaction=False)
def test_form_anular_protocolo_inexistente():
form = AnularProcoloAdmForm({'numero': '1',
form = AnularProtocoloAdmForm({'numero': '1',
'ano': '2016',
'justificativa_anulacao': 'TESTE'})
@ -64,7 +68,7 @@ def test_form_anular_protocolo_inexistente():
@pytest.mark.django_db(transaction=False)
def test_form_anular_protocolo_valido():
mommy.make(Protocolo, numero='1', ano='2016', anulado=False)
form = AnularProcoloAdmForm({'numero': '1',
form = AnularProtocoloAdmForm({'numero': '1',
'ano': '2016',
'justificativa_anulacao': 'TESTE'})
if not form.is_valid():
@ -74,7 +78,7 @@ def test_form_anular_protocolo_valido():
@pytest.mark.django_db(transaction=False)
def test_form_anular_protocolo_anulado():
mommy.make(Protocolo, numero='1', ano='2016', anulado=True)
form = AnularProcoloAdmForm({'numero': '1',
form = AnularProtocoloAdmForm({'numero': '1',
'ano': '2016',
'justificativa_anulacao': 'TESTE'})
assert form.errors['__all__'] == \
@ -88,7 +92,7 @@ def test_form_anular_protocolo_campos_obrigatorios():
# TODO: generalizar para diminuir o tamanho deste método
# numero ausente
form = AnularProcoloAdmForm({'numero': '',
form = AnularProtocoloAdmForm({'numero': '',
'ano': '2016',
'justificativa_anulacao': 'TESTE'})
if form.is_valid():
@ -98,7 +102,7 @@ def test_form_anular_protocolo_campos_obrigatorios():
assert form.errors['numero'] == [_('Este campo é obrigatório.')]
# ano ausente
form = AnularProcoloAdmForm({'numero': '1',
form = AnularProtocoloAdmForm({'numero': '1',
'ano': '',
'justificativa_anulacao': 'TESTE'})
if form.is_valid():
@ -108,7 +112,7 @@ def test_form_anular_protocolo_campos_obrigatorios():
assert form.errors['ano'] == [_('Este campo é obrigatório.')]
# justificativa_anulacao ausente
form = AnularProcoloAdmForm({'numero': '1',
form = AnularProtocoloAdmForm({'numero': '1',
'ano': '2016',
'justificativa_anulacao': ''})
if form.is_valid():
@ -157,12 +161,14 @@ def test_create_tramitacao(admin_client):
'unidade_tramitacao_destino': unidade_tramitacao_local_1.pk,
'documento': documento_adm.pk,
'status': status.pk,
'urgente': False,
'texto': 'teste',
'data_tramitacao': date(2016, 8, 21)},
follow=True)
msg = force_text(_('A origem da nova tramitação deve ser igual ao '
'destino da última adicionada!'))
# Verifica se a origem da nova tramitacao é igual ao destino da última
assert msg in response.context_data[
'form'].errors['__all__']
@ -175,6 +181,8 @@ def test_create_tramitacao(admin_client):
'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk,
'documento': documento_adm.pk,
'status': status.pk,
'urgente': False,
'texto': 'teste',
'data_tramitacao': date(2016, 8, 20)},
follow=True)
@ -193,6 +201,8 @@ def test_create_tramitacao(admin_client):
'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk,
'documento': documento_adm.pk,
'status': status.pk,
'urgente': False,
'texto': 'teste',
'data_tramitacao': timezone.now().date() + timedelta(
days=1)},
follow=True)
@ -212,6 +222,8 @@ def test_create_tramitacao(admin_client):
'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk,
'documento': documento_adm.pk,
'status': status.pk,
'urgente': False,
'texto': 'teste',
'data_tramitacao': date(2016, 8, 21),
'data_encaminhamento': date(2016, 8, 20)},
follow=True)
@ -231,6 +243,8 @@ def test_create_tramitacao(admin_client):
'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk,
'documento': documento_adm.pk,
'status': status.pk,
'urgente': False,
'texto': 'teste',
'data_tramitacao': date(2016, 8, 21),
'data_fim_prazo': date(2016, 8, 20)},
follow=True)
@ -250,6 +264,8 @@ def test_create_tramitacao(admin_client):
'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk,
'documento': documento_adm.pk,
'status': status.pk,
'urgente': False,
'texto': 'teste',
'data_tramitacao': date(2016, 8, 21)},
follow=True)
@ -261,7 +277,7 @@ def test_create_tramitacao(admin_client):
@pytest.mark.django_db(transaction=False)
def test_anular_protocolo_dados_invalidos():
form = AnularProcoloAdmForm(data={})
form = AnularProtocoloAdmForm(data={})
assert not form.is_valid()
@ -276,10 +292,10 @@ def test_anular_protocolo_dados_invalidos():
@pytest.mark.django_db(transaction=False)
def test_anular_protocolo_form_anula_protocolo_inexistente():
form = AnularProcoloAdmForm(data={'numero': '1',
form = AnularProtocoloAdmForm(data={'numero': '1',
'ano': '2017',
'justificativa_anulacao': 'teste'
})
})
assert not form.is_valid()
@ -291,10 +307,10 @@ def test_anular_protocolo_form_anula_protocolo_inexistente():
def test_anular_protocolo_form_anula_protocolo_anulado():
mommy.make(Protocolo, numero=1, ano=2017, anulado=True)
form = AnularProcoloAdmForm(data={'numero': '1',
form = AnularProtocoloAdmForm(data={'numero': '1',
'ano': '2017',
'justificativa_anulacao': 'teste'
})
})
assert not form.is_valid()
@ -316,10 +332,10 @@ def test_anular_protocolo_form_anula_protocolo_com_doc_vinculado():
ano=2017,
numero_protocolo=1)
form = AnularProcoloAdmForm(data={'numero': '1',
form = AnularProtocoloAdmForm(data={'numero': '1',
'ano': '2017',
'justificativa_anulacao': 'teste'
})
})
assert not form.is_valid()
@ -338,10 +354,10 @@ def test_anular_protocolo_form_anula_protocolo_com_doc_vinculado():
mommy.make(DocumentoAdministrativo,
protocolo=protocolo_documento)
form = AnularProcoloAdmForm(data={'numero': '2',
form = AnularProtocoloAdmForm(data={'numero': '2',
'ano': '2017',
'justificativa_anulacao': 'teste'
})
})
assert not form.is_valid()
@ -443,3 +459,247 @@ def test_protocolo_materia_invalido():
assert errors['vincular_materia'] == [_('Este campo é obrigatório.')]
assert len(errors) == 7
@pytest.mark.django_db(transaction=False)
def test_lista_documentos_anexados():
tipo_documento = mommy.make(
TipoDocumentoAdministrativo,
descricao="Tipo_Teste"
)
documento_principal = mommy.make(
DocumentoAdministrativo,
numero=20,
ano=2018,
data="2018-01-04",
tipo=tipo_documento
)
documento_anexado = mommy.make(
DocumentoAdministrativo,
numero=21,
ano=2019,
data="2019-05-04",
tipo=tipo_documento
)
documento_anexado_anexado = mommy.make(
DocumentoAdministrativo,
numero=22,
ano=2020,
data="2020-01-05",
tipo=tipo_documento
)
mommy.make(
Anexado,
documento_principal=documento_principal,
documento_anexado=documento_anexado,
data_anexacao="2019-05-11"
)
mommy.make(
Anexado,
documento_principal=documento_anexado,
documento_anexado=documento_anexado_anexado,
data_anexacao="2020-11-05"
)
lista = lista_anexados(documento_principal, False)
assert len(lista) == 2
assert lista[0] == documento_anexado
assert lista[1] == documento_anexado_anexado
@pytest.mark.django_db(transaction=False)
def make_unidade_tramitacao(descricao):
# Cria uma comissão para ser a unidade de tramitação
tipo_comissao = mommy.make(TipoComissao)
comissao = mommy.make(Comissao,
tipo=tipo_comissao,
nome=descricao,
sigla='T',
data_criacao='2016-03-21')
# Testa a comissão
assert comissao.tipo == tipo_comissao
assert comissao.nome == descricao
# Cria a unidade
unidade = mommy.make(UnidadeTramitacao, comissao=comissao)
assert unidade.comissao == comissao
return unidade
@pytest.mark.django_db(transaction=False)
def test_tramitacoes_documentos_anexados(admin_client):
tipo_documento = mommy.make(
TipoDocumentoAdministrativo,
descricao="Tipo_Teste"
)
documento_principal = mommy.make(
DocumentoAdministrativo,
numero=20,
ano=2018,
data="2018-01-04",
tipo=tipo_documento
)
documento_anexado = mommy.make(
DocumentoAdministrativo,
numero=21,
ano=2019,
data="2019-05-04",
tipo=tipo_documento
)
documento_anexado_anexado = mommy.make(
DocumentoAdministrativo,
numero=22,
ano=2020,
data="2020-01-05",
tipo=tipo_documento
)
mommy.make(
Anexado,
documento_principal=documento_principal,
documento_anexado=documento_anexado,
data_anexacao="2019-05-11"
)
mommy.make(
Anexado,
documento_principal=documento_anexado,
documento_anexado=documento_anexado_anexado,
data_anexacao="2020-11-05"
)
unidade_tramitacao_local_1 = make_unidade_tramitacao(descricao="Teste 1")
unidade_tramitacao_destino_1 = make_unidade_tramitacao(descricao="Teste 2")
unidade_tramitacao_destino_2 = make_unidade_tramitacao(descricao="Teste 3")
status = mommy.make(
StatusTramitacaoAdministrativo,
indicador='R')
# Teste criação de Tramitacao
form = TramitacaoAdmForm(data={})
form.data = {'data_tramitacao':date(2019, 5, 6),
'unidade_tramitacao_local':unidade_tramitacao_local_1.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk,
'status':status.pk,
'urgente': False,
'texto': "Texto de teste"}
form.instance.documento_id=documento_principal.pk
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = documento_anexado.tramitacaoadministrativo_set.last()
tramitacao_anexada_anexada = documento_anexado_anexado.tramitacaoadministrativo_set.last()
# Verifica se foram criadas as tramitações para os documentos anexados e anexados aos anexados
assert documento_principal.tramitacaoadministrativo_set.last() == tramitacao_principal
assert tramitacao_principal.documento.tramitacao == (tramitacao_principal.status.indicador != "F")
assert compara_tramitacoes_doc(tramitacao_principal, tramitacao_anexada)
assert DocumentoAdministrativo.objects.get(id=documento_anexado.pk).tramitacao \
== (tramitacao_anexada.status.indicador != "F")
assert compara_tramitacoes_doc(tramitacao_anexada_anexada, tramitacao_principal)
assert DocumentoAdministrativo.objects.get(id=documento_anexado_anexado.pk).tramitacao \
== (tramitacao_anexada_anexada.status.indicador != "F")
# Teste Edição de Tramitacao
form = TramitacaoAdmEditForm(data={})
# Alterando unidade_tramitacao_destino
form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao,
'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk,
'status':tramitacao_principal.status.pk,
'urgente': tramitacao_principal.urgente,
'texto': tramitacao_principal.texto}
form.instance = tramitacao_principal
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = documento_anexado.tramitacaoadministrativo_set.last()
tramitacao_anexada_anexada = documento_anexado_anexado.tramitacaoadministrativo_set.last()
assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
# Teste Remoção de Tramitacao
url = reverse('sapl.protocoloadm:tramitacaoadministrativo_delete',
kwargs={'pk': tramitacao_principal.pk})
response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True)
assert TramitacaoAdministrativo.objects.filter(id=tramitacao_principal.pk).count() == 0
assert TramitacaoAdministrativo.objects.filter(id=tramitacao_anexada.pk).count() == 0
assert TramitacaoAdministrativo.objects.filter(id=tramitacao_anexada_anexada.pk).count() == 0
# Testes para quando as tramitações das anexadas divergem
form = TramitacaoAdmForm(data={})
form.data = {'data_tramitacao':date(2019, 5, 6),
'unidade_tramitacao_local':unidade_tramitacao_local_1.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_1.pk,
'status':status.pk,
'urgente': False,
'texto': "Texto de teste"}
form.instance.documento_id=documento_principal.pk
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = documento_anexado.tramitacaoadministrativo_set.last()
tramitacao_anexada_anexada = documento_anexado_anexado.tramitacaoadministrativo_set.last()
form = TramitacaoAdmEditForm(data={})
# Alterando unidade_tramitacao_destino
form.data = {'data_tramitacao':tramitacao_anexada.data_tramitacao,
'unidade_tramitacao_local':tramitacao_anexada.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':unidade_tramitacao_destino_2.pk,
'status':tramitacao_anexada.status.pk,
'urgente': tramitacao_anexada.urgente,
'texto': tramitacao_anexada.texto}
form.instance = tramitacao_anexada
assert form.is_valid()
tramitacao_anexada = form.save()
tramitacao_anexada_anexada = documento_anexado_anexado.tramitacaoadministrativo_set.last()
assert tramitacao_principal.unidade_tramitacao_destino == unidade_tramitacao_destino_1
assert tramitacao_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
assert tramitacao_anexada_anexada.unidade_tramitacao_destino == unidade_tramitacao_destino_2
# Editando a tramitação principal, as tramitações anexadas não devem ser editadas
form = TramitacaoAdmEditForm(data={})
# Alterando o texto
form.data = {'data_tramitacao':tramitacao_principal.data_tramitacao,
'unidade_tramitacao_local':tramitacao_principal.unidade_tramitacao_local.pk,
'unidade_tramitacao_destino':tramitacao_principal.unidade_tramitacao_destino.pk,
'status':tramitacao_principal.status.pk,
'urgente': tramitacao_principal.urgente,
'texto': "Testando a alteração"}
form.instance = tramitacao_principal
assert form.is_valid()
tramitacao_principal = form.save()
tramitacao_anexada = documento_anexado.tramitacaoadministrativo_set.last()
tramitacao_anexada_anexada = documento_anexado_anexado.tramitacaoadministrativo_set.last()
assert tramitacao_principal.texto == "Testando a alteração"
assert not tramitacao_anexada.texto == "Testando a alteração"
assert not tramitacao_anexada_anexada.texto == "Testando a alteração"
# Removendo a tramitação pricipal, as tramitações anexadas não devem ser removidas, pois divergiram
url = reverse('sapl.protocoloadm:tramitacaoadministrativo_delete',
kwargs={'pk': tramitacao_principal.pk})
response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True)
assert TramitacaoAdministrativo.objects.filter(id=tramitacao_principal.pk).count() == 0
assert TramitacaoAdministrativo.objects.filter(id=tramitacao_anexada.pk).count() == 1
assert TramitacaoAdministrativo.objects.filter(id=tramitacao_anexada_anexada.pk).count() == 1
# Removendo a tramitação anexada, a tramitação anexada à anexada deve ser removida
url = reverse('sapl.protocoloadm:tramitacaoadministrativo_delete',
kwargs={'pk': tramitacao_anexada.pk})
response = admin_client.post(url, {'confirmar':'confirmar'} ,follow=True)
assert TramitacaoAdministrativo.objects.filter(id=tramitacao_anexada.pk).count() == 0
assert TramitacaoAdministrativo.objects.filter(id=tramitacao_anexada_anexada.pk).count() == 0

7
sapl/protocoloadm/urls.py

@ -21,7 +21,8 @@ from sapl.protocoloadm.views import (AcompanhamentoDocumentoView,
atualizar_numero_documento,
doc_texto_integral,
DesvincularDocumentoView,
DesvincularMateriaView)
DesvincularMateriaView,
AnexadoCrud, DocumentoAnexadoEmLoteView)
from .apps import AppConfig
@ -30,6 +31,7 @@ app_name = AppConfig.name
urlpatterns_documento_administrativo = [
url(r'^docadm/',
include(DocumentoAdministrativoCrud.get_urls() +
AnexadoCrud.get_urls() +
TramitacaoAdmCrud.get_urls() +
DocumentoAcessorioAdministrativoCrud.get_urls())),
@ -38,6 +40,9 @@ urlpatterns_documento_administrativo = [
url(r'^docadm/texto_integral/(?P<pk>\d+)$', doc_texto_integral,
name='doc_texto_integral'),
url(r'^docadm/(?P<pk>\d+)/anexado_em_lote', DocumentoAnexadoEmLoteView.as_view(),
name='anexado_em_lote'),
]
urlpatterns_protocolo = [

255
sapl/protocoloadm/views.py

@ -17,7 +17,7 @@ from django.http.response import HttpResponseRedirect
from django.shortcuts import redirect
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.views.generic import ListView, CreateView
from django.views.generic import ListView, CreateView, UpdateView
from django.views.generic.base import RedirectView, TemplateView
from django.views.generic.edit import FormView
from django_filters.views import FilterView
@ -27,16 +27,17 @@ from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa
from sapl.base.signals import tramitacao_signal
from sapl.comissoes.models import Comissao
from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination
from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud, make_pagination,
RP_LIST, RP_DETAIL)
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.materia.views import gerar_pdf_impressos
from sapl.parlamentares.models import Legislatura, Parlamentar
from sapl.protocoloadm.models import Protocolo
from sapl.utils import (create_barcode, get_base_url, get_client_ip,
get_mime_type_from_file_extension,
get_mime_type_from_file_extension, lista_anexados,
show_results_filter_set, mail_service_configured)
from .forms import (AcompanhamentoDocumentoForm, AnularProcoloAdmForm,
from .forms import (AcompanhamentoDocumentoForm, AnularProtocoloAdmForm,
DocumentoAcessorioAdministrativoForm,
DocumentoAdministrativoFilterSet,
DocumentoAdministrativoForm, FichaPesquisaAdmForm, FichaSelecionaAdmForm, ProtocoloDocumentForm,
@ -44,10 +45,12 @@ from .forms import (AcompanhamentoDocumentoForm, AnularProcoloAdmForm,
TramitacaoAdmEditForm, TramitacaoAdmForm,
DesvincularDocumentoForm, DesvincularMateriaForm,
filtra_tramitacao_adm_destino_and_status,
filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status)
filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status,
AnexadoForm, AnexadoEmLoteFilterSet,
compara_tramitacoes_doc)
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo, StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo, TramitacaoAdministrativo)
TipoDocumentoAdministrativo, TramitacaoAdministrativo, Anexado)
TipoDocumentoAdministrativoCrud = CrudAux.build(
@ -482,7 +485,7 @@ class ProtocoloListView(PermissionRequiredMixin, ListView):
class AnularProtocoloAdmView(PermissionRequiredMixin, CreateView):
template_name = 'protocoloadm/anular_protocoloadm.html'
form_class = AnularProcoloAdmForm
form_class = AnularProtocoloAdmForm
form_valid_message = _('Protocolo anulado com sucesso!')
permission_required = ('protocoloadm.action_anular_protocolo', )
@ -535,12 +538,12 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
def form_valid(self, form):
protocolo = form.save(commit=False)
username = self.request.user.username
try:
self.logger.debug("user=" + username +
". Tentando obter sequência de numeração.")
numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao
except AttributeError as e:
self.logger.debug("user=" + username +
". Tentando obter sequência de numeração.")
numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao_protocolo
if not numeracao:
self.logger.error("user=" + username + ". É preciso definir a sequencia de "
"numeração na tabelas auxiliares! " + str(e))
msg = _('É preciso definir a sequencia de ' +
@ -722,12 +725,11 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
def form_valid(self, form):
protocolo = form.save(commit=False)
username = self.request.user.username
try:
self.logger.debug("user=" + username +
". Tentando obter sequência de numeração.")
numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao
except AttributeError:
self.logger.debug("user=" + username +
". Tentando obter sequência de numeração.")
numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao_protocolo
if not numeracao:
self.logger.error("user=" + username + ". É preciso definir a sequencia de "
"numeração na tabelas auxiliares!")
msg = _('É preciso definir a sequencia de ' +
@ -941,6 +943,154 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
return self.render_to_response(context)
class AnexadoCrud(MasterDetailCrud):
model = Anexado
parent_field = 'documento_principal'
help_topic = 'documento_anexado'
public = [RP_LIST, RP_DETAIL]
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['documento_anexado', 'data_anexacao']
class CreateView(MasterDetailCrud.CreateView):
form_class = AnexadoForm
class UpdateView(MasterDetailCrud.UpdateView):
form_class = AnexadoForm
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial['tipo'] = self.object.documento_anexado.tipo.id
initial['numero'] = self.object.documento_anexado.numero
initial['ano'] = self.object.documento_anexado.ano
return initial
class DetailView(MasterDetailCrud.DetailView):
@property
def layout_key(self):
return 'AnexadoDetail'
class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = AnexadoEmLoteFilterSet
template_name = 'protocoloadm/em_lote/anexado.html'
permission_required = ('protocoloadm.add_anexado', )
def get_context_data(self, **kwargs):
context = super(
DocumentoAnexadoEmLoteView,self
).get_context_data(**kwargs)
context['root_pk'] = self.kwargs['pk']
context['subnav_template_name'] = 'protocoloadm/subnav.yaml'
context['title'] = _('Documentos Anexados em Lote')
# Verifica se os campos foram preenchidos
if not self.request.GET.get('tipo', " "):
msg =_('Por favor, selecione um tipo de documento.')
messages.add_message(self.request, messages.ERROR, msg)
if not self.request.GET.get('data_0', " ") or not self.request.GET.get('data_1', " "):
msg =_('Por favor, preencha as datas.')
messages.add_message(self.request, messages.ERROR, msg)
return context
if not self.request.GET.get('data_0', " ") or not self.request.GET.get('data_1', " "):
msg =_('Por favor, preencha as datas.')
messages.add_message(self.request, messages.ERROR, msg)
return context
qr = self.request.GET.copy()
context['temp_object_list'] = context['object_list'].order_by(
'numero', '-ano'
)
context['object_list'] = []
for obj in context['temp_object_list']:
if not obj.pk == int(context['root_pk']):
documento_principal = DocumentoAdministrativo.objects.get(id=context['root_pk'])
documento_anexado = obj
is_anexado = Anexado.objects.filter(documento_principal=documento_principal,
documento_anexado=documento_anexado).exists()
if not is_anexado:
ciclico = False
anexados_anexado = Anexado.objects.filter(documento_principal=documento_anexado)
while anexados_anexado and not ciclico:
anexados = []
for anexo in anexados_anexado:
if documento_principal == anexo.documento_anexado:
ciclico = True
else:
for a in Anexado.objects.filter(documento_principal=anexo.documento_anexado):
anexados.append(a)
anexados_anexado = anexados
if not ciclico:
context['object_list'].append(obj)
context['numero_res'] = len(context['object_list'])
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
return context
def post(self, request, *args, **kwargs):
marcados = request.POST.getlist('documento_id')
data_anexacao = datetime.strptime(
request.POST['data_anexacao'], "%d/%m/%Y"
).date()
if request.POST['data_desanexacao'] == '':
data_desanexacao = None
v_data_desanexacao = data_anexacao
else:
data_desanexacao = datetime.strptime(
request.POST['data_desanexacao'], "%d/%m/%Y"
).date()
v_data_desanexacao = data_desanexacao
if len(marcados) == 0:
msg =_('Nenhum documento foi selecionado')
messages.add_message(request, messages.ERROR, msg)
if data_anexacao > v_data_desanexacao:
msg=_('Data de anexação posterior à data de desanexação.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
if data_anexacao > v_data_desanexacao:
msg =_('Data de anexação posterior à data de desanexação.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, messages.ERROR, msg)
principal = DocumentoAdministrativo.objects.get(pk = kwargs['pk'])
for documento in DocumentoAdministrativo.objects.filter(id__in = marcados):
anexado = Anexado()
anexado.documento_principal = principal
anexado.documento_anexado = documento
anexado.data_anexacao = data_anexacao
anexado.data_desanexacao = data_desanexacao
anexado.save()
msg = _('Documento(s) anexado(s).')
messages.add_message(request, messages.SUCCESS, msg)
success_url = reverse('sapl_index') + 'docadm/' + kwargs['pk'] + '/anexado'
return HttpResponseRedirect(success_url)
class TramitacaoAdmCrud(MasterDetailCrud):
model = TramitacaoAdministrativo
parent_field = 'documento'
@ -954,6 +1104,10 @@ class TramitacaoAdmCrud(MasterDetailCrud):
form_class = TramitacaoAdmForm
logger = logging.getLogger(__name__)
def get_success_url(self):
return reverse('sapl.protocoloadm:tramitacaoadministrativo_list', kwargs={
'pk': self.kwargs['pk']})
def get_initial(self):
initial = super(CreateView, self).get_initial()
local = DocumentoAdministrativo.objects.get(
@ -967,10 +1121,33 @@ class TramitacaoAdmCrud(MasterDetailCrud):
else:
initial['unidade_tramitacao_local'] = ''
initial['data_tramitacao'] = timezone.now().date()
initial['ip'] = get_client_ip(self.request)
initial['user'] = self.request.user
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
username = self.request.user.username
ultima_tramitacao = TramitacaoAdministrativo.objects.filter(
documento_id=self.kwargs['pk']).order_by(
'-data_tramitacao',
'-timestamp',
'-id').first()
#TODO: Esta checagem foi inserida na issue #2027, mas é mesmo necessária?
if ultima_tramitacao:
if ultima_tramitacao.unidade_tramitacao_destino:
context['form'].fields[
'unidade_tramitacao_local'].choices = [
(ultima_tramitacao.unidade_tramitacao_destino.pk,
ultima_tramitacao.unidade_tramitacao_destino)]
else:
self.logger.error('user=' + username + '. Unidade de tramitação destino '
'da última tramitação não pode ser vazia!')
msg = _('Unidade de tramitação destino '
' da última tramitação não pode ser vazia!')
messages.add_message(self.request, messages.ERROR, msg)
primeira_tramitacao = not(TramitacaoAdministrativo.objects.filter(
documento_id=int(kwargs['root_pk'])).exists())
@ -990,7 +1167,6 @@ class TramitacaoAdmCrud(MasterDetailCrud):
post=self.object,
request=self.request)
except Exception as e:
# TODO log error
self.logger.error('user=' + username + '. Tramitação criada, mas e-mail de acompanhamento de documento '
'não enviado. A não configuração do servidor de e-mail '
'impede o envio de aviso de tramitação. ' + str(e))
@ -1005,6 +1181,12 @@ class TramitacaoAdmCrud(MasterDetailCrud):
form_class = TramitacaoAdmEditForm
logger = logging.getLogger(__name__)
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial['ip'] = get_client_ip(self.request)
initial['user'] = self.request.user
return initial
def form_valid(self, form):
self.object = form.save()
username = self.request.user.username
@ -1013,7 +1195,6 @@ class TramitacaoAdmCrud(MasterDetailCrud):
post=self.object,
request=self.request)
except Exception as e:
# TODO log error
self.logger.error('user=' + username + '. Tramitação criada, mas e-mail de acompanhamento de documento '
'não enviado. A não configuração do servidor de e-mail '
'impede o envio de aviso de tramitação. ' + str(e))
@ -1034,18 +1215,26 @@ class TramitacaoAdmCrud(MasterDetailCrud):
class DetailView(DocumentoAdministrativoMixin,
MasterDetailCrud.DetailView):
pass
template_name = 'protocoloadm/tramitacaoadministrativo_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user'] = self.request.user
return context
class DeleteView(MasterDetailCrud.DeleteView):
logger = logging.getLogger(__name__)
def delete(self, request, *args, **kwargs):
tramitacao = TramitacaoAdministrativo.objects.get(
id=self.kwargs['pk'])
documento = DocumentoAdministrativo.objects.get(
id=tramitacao.documento.id)
documento = tramitacao.documento
url = reverse(
'sapl.protocoloadm:tramitacaoadministrativo_list',
kwargs={'pk': tramitacao.documento.id})
kwargs={'pk': documento.id})
ultima_tramitacao = \
documento.tramitacaoadministrativo_set.order_by(
@ -1053,11 +1242,21 @@ class TramitacaoAdmCrud(MasterDetailCrud):
'-id').first()
if tramitacao.pk != ultima_tramitacao.pk:
username = request.user.username
self.logger.error("user=" + username + ". Não é possível deletar a tramitação de pk={}. "
"Somente a última tramitação (pk={}) pode ser deletada!."
.format(tramitacao.pk, ultima_tramitacao.pk))
msg = _('Somente a última tramitação pode ser deletada!')
messages.add_message(request, messages.ERROR, msg)
return HttpResponseRedirect(url)
else:
tramitacao.delete()
tramitacoes_deletar = [tramitacao.id]
docs_anexados = lista_anexados(documento, False)
for da in docs_anexados:
tram_anexada = da.tramitacaoadministrativo_set.last()
if compara_tramitacoes_doc(tram_anexada, tramitacao):
tramitacoes_deletar.append(tram_anexada.id)
TramitacaoAdministrativo.objects.filter(id__in=tramitacoes_deletar).delete()
return HttpResponseRedirect(url)
@ -1232,8 +1431,8 @@ class FichaSelecionaAdmView(PermissionRequiredMixin, FormView):
self.messages.add_message(self.request, messages.INFO, mensagem)
return self.render_to_response(context)
if len(documento.assunto) > 301:
documento.assunto = documento.assunto[0:300] + '[...]'
if len(documento.assunto) > 201:
documento.assunto = documento.assunto[0:200] + '[...]'
context['documento'] = documento
return gerar_pdf_impressos(self.request, context,

24
sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py

@ -128,6 +128,24 @@ def inf_basicas(inf_basicas_dic):
return tmp
def multimidia(cont_mult_dic):
"""
"""
tmp = ""
mul_audio = cont_mult_dic['multimidia_audio']
mul_video = cont_mult_dic['multimidia_video']
tmp += '\t\t<para style="P1">Conteúdo Multimídia</para>\n'
tmp += '\t\t<para style="P2">\n'
tmp += '\t\t\t<font color="white"> <br/></font>\n'
tmp += '\t\t</para>\n'
tmp += '\t\t<para style="P2" spaceAfter="5"><b>Audio: </b> ' + mul_audio + '</para>\n'
tmp += '\t\t<para style="P2" spaceAfter="5"><b>Video: </b> ' + mul_video + '</para>\n'
return tmp
def mesa(lst_mesa):
"""
@ -392,7 +410,7 @@ def ocorrencias(lst_ocorrencias):
return tmp
def principal(rodape_dic, imagem, inf_basicas_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):
def principal(rodape_dic, imagem, 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):
"""
"""
arquivoPdf = str(int(time.time() * 100)) + ".pdf"
@ -416,7 +434,7 @@ def principal(rodape_dic, imagem, inf_basicas_dic, lst_mesa, lst_presenca_sessao
ordenacao = ResumoOrdenacao.objects.first()
dict_ord_template = {
'cont_mult': '',
'cont_mult': multimidia(cont_mult_dic),
'exp': expedientes(lst_expedientes),
'id_basica': inf_basicas(inf_basicas_dic),
'lista_p': presenca(lst_presenca_sessao, lst_ausencia_sessao),
@ -452,6 +470,7 @@ def principal(rodape_dic, imagem, inf_basicas_dic, lst_mesa, lst_presenca_sessao
logger.error("KeyError: " + str(e) + ". Erro ao tentar utilizar "
"configuração de ordenação. Utilizando ordenação padrão.")
tmp += inf_basicas(inf_basicas_dic)
tmp += multimidia(cont_mult_dic)
tmp += mesa(lst_mesa)
tmp += presenca(lst_presenca_sessao, lst_ausencia_sessao)
tmp += expedientes(lst_expedientes)
@ -467,6 +486,7 @@ def principal(rodape_dic, imagem, inf_basicas_dic, lst_mesa, lst_presenca_sessao
else:
tmp += inf_basicas(inf_basicas_dic)
tmp += multimidia(cont_mult_dic)
tmp += mesa(lst_mesa)
tmp += presenca(lst_presenca_sessao, lst_ausencia_sessao)
tmp += expedientes(lst_expedientes)

17
sapl/relatorios/views.py

@ -519,6 +519,18 @@ def get_sessao_plenaria(sessao, casa):
inf_basicas_dic["dat_fim_sessao"] = ''
inf_basicas_dic["hr_fim_sessao"] = sessao.hora_fim
inf_basicas_dic["nom_camara"] = casa.nome
# Conteudo multimidia
cont_mult_dic = {}
if sessao.url_audio:
cont_mult_dic['multimidia_audio'] = str(sessao.url_audio)
else:
cont_mult_dic['multimidia_audio'] = 'Indisponível'
if sessao.url_video:
cont_mult_dic['multimidia_video'] = str(sessao.url_video)
else:
cont_mult_dic['multimidia_video'] = 'Indisponível'
# Lista da composicao da mesa diretora
lst_mesa = []
@ -580,7 +592,9 @@ def get_sessao_plenaria(sessao, casa):
# https://github.com/interlegis/sapl/issues/1046
conteudo = re.sub('style=".*?"', '', conteudo)
conteudo = re.sub('class=".*?"', '', conteudo)
conteudo = re.sub('align=".*?"', '', conteudo) # OSTicket Ticket #796450
conteudo = re.sub('<p\s+>', '<p>', conteudo)
conteudo = re.sub('<br\s+/>', '<br/>', conteudo) # OSTicket Ticket #796450
conteudo = html.unescape(conteudo)
# escape special character '&'
@ -850,6 +864,7 @@ def get_sessao_plenaria(sessao, casa):
lst_ocorrencias.append(o)
return (inf_basicas_dic,
cont_mult_dic,
lst_mesa,
lst_presenca_sessao,
lst_ausencia_sessao,
@ -908,6 +923,7 @@ def relatorio_sessao_plenaria(request, pk):
raise Http404('Essa página não existe')
(inf_basicas_dic,
cont_mult_dic,
lst_mesa,
lst_presenca_sessao,
lst_ausencia_sessao,
@ -932,6 +948,7 @@ def relatorio_sessao_plenaria(request, pk):
rodape,
imagem,
inf_basicas_dic,
cont_mult_dic,
lst_mesa,
lst_presenca_sessao,
lst_ausencia_sessao,

6
sapl/rules/map_rules.py

@ -60,6 +60,7 @@ rules_group_administrativo = {
'can_access_impressos'], __perms_publicas__),
# TODO: tratar em sapl.api a questão de ostencivo e restritivo
(protocoloadm.DocumentoAdministrativo, __base__, set()),
(protocoloadm.Anexado, __base__, set()),
(protocoloadm.DocumentoAcessorioAdministrativo, __base__, set()),
(protocoloadm.TramitacaoAdministrativo, __base__, set()),
]
@ -118,6 +119,8 @@ rules_group_materia = {
(materia.Autoria, __base__, __perms_publicas__),
(materia.DespachoInicial, __base__, __perms_publicas__),
(materia.DocumentoAcessorio, __base__, __perms_publicas__),
(materia.MateriaAssunto, __base__, __perms_publicas__),
(materia.AssuntoMateria, __base__, __perms_publicas__),
(materia.MateriaLegislativa, __base__ +
['can_access_impressos'], __perms_publicas__),
@ -275,6 +278,8 @@ rules_group_geral = {
(parlamentares.ComposicaoMesa, __base__, __perms_publicas__),
(parlamentares.Frente, __base__, __perms_publicas__),
(parlamentares.Votante, __base__, __perms_publicas__),
(parlamentares.Bloco, __base__, __perms_publicas__),
(sessao.CargoBancada, __base__, __perms_publicas__),
(sessao.Bancada, __base__, __perms_publicas__),
@ -283,7 +288,6 @@ rules_group_geral = {
(sessao.TipoExpediente, __base__, __perms_publicas__),
(sessao.TipoJustificativa, __base__, __perms_publicas__),
(sessao.JustificativaAusencia, __base__, __perms_publicas__),
(sessao.Bloco, __base__, __perms_publicas__),
(sessao.ResumoOrdenacao, __base__, __perms_publicas__),
(sessao.TipoRetiradaPauta, __base__, __perms_publicas__),

165
sapl/sessao/forms.py

@ -23,32 +23,17 @@ from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES,
autor_modal, timezone, choice_anos_com_sessaoplenaria,
FileFieldCheckMixin)
from .models import (Bancada, Bloco, ExpedienteMateria, JustificativaAusencia,
from .models import (Bancada, ExpedienteMateria, JustificativaAusencia,
Orador, OradorExpediente, OrdemDia, PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, TipoResultadoVotacao,
OcorrenciaSessao, RetiradaPauta, TipoRetiradaPauta, OradorOrdemDia)
OcorrenciaSessao, RetiradaPauta, TipoRetiradaPauta, OradorOrdemDia, ORDENACAO_RESUMO,
ResumoOrdenacao)
MES_CHOICES = RANGE_MESES
DIA_CHOICES = RANGE_DIAS_MES
ORDENACAO_RESUMO = [('cont_mult', 'Conteúdo Multimídia'),
('exp', 'Expedientes'),
('id_basica', 'Identificação Básica'),
('lista_p', 'Lista de Presença'),
('lista_p_o_d', 'Lista de Presença Ordem do Dia'),
('mat_exp', 'Matérias do Expediente'),
('mat_o_d', 'Matérias da Ordem do Dia'),
('mesa_d', 'Mesa Diretora'),
('oradores_exped', 'Oradores do Expediente'),
('oradores_expli', 'Oradores das Explicações Pessoais'),
('ocorr_sessao', 'Ocorrências da Sessão'),
('v_n_mat_exp', 'Votações Nominais - Matérias do Expediente'),
('v_n_mat_o_d', 'Votações Nominais - Matérias da Ordem do Dia'),
('oradores_o_d', 'Oradores da Ordem do Dia')]
class SessaoPlenariaForm(FileFieldCheckMixin, ModelForm):
class Meta:
@ -331,41 +316,6 @@ class BancadaForm(ModelForm):
return bancada
class BlocoForm(ModelForm):
class Meta:
model = Bloco
fields = ['nome', 'partidos', 'data_criacao',
'data_extincao', 'descricao']
def clean(self):
super(BlocoForm, self).clean()
if not self.is_valid():
return self.cleaned_data
if self.cleaned_data['data_extincao']:
if (self.cleaned_data['data_extincao'] <
self.cleaned_data['data_criacao']):
msg = _('Data de extinção não pode ser menor que a de criação')
raise ValidationError(msg)
return self.cleaned_data
@transaction.atomic
def save(self, commit=True):
bloco = super(BlocoForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Bloco)
object_id = bloco.pk
tipo = TipoAutor.objects.get(content_type=content_type)
Autor.objects.create(
content_type=content_type,
object_id=object_id,
tipo=tipo,
nome=bloco.nome
)
return bloco
class ExpedienteMateriaForm(ModelForm):
_model = ExpedienteMateria
@ -807,38 +757,64 @@ class PautaSessaoFilterSet(SessaoPlenariaFilterSet):
class ResumoOrdenacaoForm(forms.Form):
primeiro = forms.ChoiceField(label=_(''),
choices=ORDENACAO_RESUMO)
segundo = forms.ChoiceField(label=_(''),
choices=ORDENACAO_RESUMO)
terceiro = forms.ChoiceField(label='',
choices=ORDENACAO_RESUMO)
quarto = forms.ChoiceField(label=_(''),
choices=ORDENACAO_RESUMO)
quinto = forms.ChoiceField(label=_(''),
choices=ORDENACAO_RESUMO)
sexto = forms.ChoiceField(label=_(''),
choices=ORDENACAO_RESUMO)
setimo = forms.ChoiceField(label=_(''),
choices=ORDENACAO_RESUMO)
oitavo = forms.ChoiceField(label=_(''),
choices=ORDENACAO_RESUMO)
nono = forms.ChoiceField(label=_(''),
choices=ORDENACAO_RESUMO)
decimo = forms.ChoiceField(label='10°',
choices=ORDENACAO_RESUMO)
decimo_primeiro = forms.ChoiceField(label='11°',
choices=ORDENACAO_RESUMO)
decimo_segundo = forms.ChoiceField(label='12°',
choices=ORDENACAO_RESUMO)
decimo_terceiro = forms.ChoiceField(label='13°',
choices=ORDENACAO_RESUMO)
decimo_quarto = forms.ChoiceField(label='14°',
choices=ORDENACAO_RESUMO)
primeiro = forms.ChoiceField(
label='',
choices=ORDENACAO_RESUMO
)
segundo = forms.ChoiceField(
label='',
choices=ORDENACAO_RESUMO
)
terceiro = forms.ChoiceField(
label='',
choices=ORDENACAO_RESUMO
)
quarto = forms.ChoiceField(
label='',
choices=ORDENACAO_RESUMO
)
quinto = forms.ChoiceField(
label='',
choices=ORDENACAO_RESUMO
)
sexto = forms.ChoiceField(
label='',
choices=ORDENACAO_RESUMO
)
setimo = forms.ChoiceField(
label='',
choices=ORDENACAO_RESUMO
)
oitavo = forms.ChoiceField(
label='',
choices=ORDENACAO_RESUMO
)
nono = forms.ChoiceField(
label='',
choices=ORDENACAO_RESUMO
)
decimo = forms.ChoiceField(
label='10°',
choices=ORDENACAO_RESUMO
)
decimo_primeiro = forms.ChoiceField(
label='11°',
choices=ORDENACAO_RESUMO
)
decimo_segundo = forms.ChoiceField(
label='12°',
choices=ORDENACAO_RESUMO
)
decimo_terceiro = forms.ChoiceField(
label='13°',
choices=ORDENACAO_RESUMO
)
decimo_quarto = forms.ChoiceField(
label='14°',
choices=ORDENACAO_RESUMO
)
def __init__(self, *args, **kwargs):
super(ResumoOrdenacaoForm, self).__init__(*args, **kwargs)
row1 = to_row(
[('primeiro', 12)])
row2 = to_row(
@ -878,6 +854,8 @@ class ResumoOrdenacaoForm(forms.Form):
form_actions(label='Atualizar'))
)
super().__init__(*args, **kwargs)
def clean(self):
super(ResumoOrdenacaoForm, self).clean()
@ -896,6 +874,27 @@ class ResumoOrdenacaoForm(forms.Form):
'Não é possível ter campos repetidos'))
return self.cleaned_data
def save(self):
ordenacao = ResumoOrdenacao.objects.get()
cleaned_data = self.cleaned_data
ordenacao.primeiro = cleaned_data['primeiro']
ordenacao.segundo = cleaned_data['segundo']
ordenacao.terceiro = cleaned_data['terceiro']
ordenacao.quarto = cleaned_data['quarto']
ordenacao.quinto = cleaned_data['quinto']
ordenacao.sexto = cleaned_data['sexto']
ordenacao.setimo = cleaned_data['setimo']
ordenacao.oitavo = cleaned_data['oitavo']
ordenacao.nono = cleaned_data['nono']
ordenacao.decimo = cleaned_data['decimo']
ordenacao.decimo_primeiro = cleaned_data['decimo_primeiro']
ordenacao.decimo_segundo = cleaned_data['decimo_segundo']
ordenacao.decimo_terceiro = cleaned_data['decimo_terceiro']
ordenacao.decimo_quarto = cleaned_data['decimo_quarto']
ordenacao.save()
class JustificativaAusenciaForm(ModelForm):

21
sapl/sessao/migrations/0036_auto_20190412_1106.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-12 14:06
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sessao', '0035_resumoordenacao_decimo_quarto'),
]
operations = [
migrations.AlterField(
model_name='expedientesessao',
name='sessao_plenaria',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='expedientesessao_set', to='sessao.SessaoPlenaria'),
),
]

33
sapl/sessao/migrations/0037_auto_20190415_1324.py

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-15 16:24
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),
('sessao', '0036_auto_20190412_1106'),
]
operations = [
migrations.AddField(
model_name='registrovotacao',
name='data_hora',
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Data/Hora'),
),
migrations.AddField(
model_name='registrovotacao',
name='ip',
field=models.CharField(blank=True, default='', max_length=30, verbose_name='IP'),
),
migrations.AddField(
model_name='registrovotacao',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
]

85
sapl/sessao/migrations/0037_auto_20190415_1635.py

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-15 19:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sessao', '0036_auto_20190412_1106'),
]
operations = [
migrations.AlterField(
model_name='resumoordenacao',
name='decimo',
field=models.CharField(default='mat_o_d', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='decimo_primeiro',
field=models.CharField(default='v_n_mat_o_d', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='decimo_quarto',
field=models.CharField(default='ocorr_sessao', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='decimo_segundo',
field=models.CharField(default='oradores_o_d', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='decimo_terceiro',
field=models.CharField(default='oradores_expli', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='nono',
field=models.CharField(default='lista_p_o_d', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='oitavo',
field=models.CharField(default='oradores_exped', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='primeiro',
field=models.CharField(default='id_basica', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='quarto',
field=models.CharField(default='lista_p', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='quinto',
field=models.CharField(default='exp', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='segundo',
field=models.CharField(default='cont_mult', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='setimo',
field=models.CharField(default='v_n_mat_exp', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='sexto',
field=models.CharField(default='mat_exp', max_length=50),
),
migrations.AlterField(
model_name='resumoordenacao',
name='terceiro',
field=models.CharField(default='mesa_d', max_length=50),
),
]

16
sapl/sessao/migrations/0038_merge_20190415_1800.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-15 21:00
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('sessao', '0037_auto_20190415_1635'),
('sessao', '0037_auto_20190415_1324'),
]
operations = [
]

22
sapl/sessao/migrations/0039_auto_20190430_0825.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-30 11:25
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('sessao', '0038_merge_20190415_1800'),
]
database_operations = [migrations.AlterModelTable('Bloco', 'parlamentares_bloco')]
state_operations = [migrations.DeleteModel('Bloco')]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=database_operations,
state_operations=state_operations)
]

142
sapl/sessao/models.py

@ -290,8 +290,11 @@ class TipoExpediente(models.Model):
@reversion.register()
class ExpedienteSessao(models.Model): # ExpedienteSessaoPlenaria
sessao_plenaria = models.ForeignKey(SessaoPlenaria,
on_delete=models.CASCADE)
sessao_plenaria = models.ForeignKey(
SessaoPlenaria,
on_delete=models.CASCADE,
related_name='expedientesessao_set'
)
tipo = models.ForeignKey(TipoExpediente, on_delete=models.PROTECT)
conteudo = models.TextField(
blank=True, verbose_name=_('Conteúdo do expediente'))
@ -379,7 +382,7 @@ class OradorExpediente(AbstractOrador): # OradoresExpediente
@reversion.register()
class OradorOrdemDia(AbstractOrador): # OradoresOrdemDia
class OradorOrdemDia(AbstractOrador): # OradoresOrdemDia
class Meta:
verbose_name = _('Orador da Ordem do Dia')
@ -453,6 +456,19 @@ class RegistroVotacao(models.Model):
verbose_name=_('Abstenções'))
observacao = models.TextField(
blank=True, verbose_name=_('Observações'))
user = models.ForeignKey(get_settings_auth_user_model(),
on_delete=models.PROTECT,
null=True,
blank=True)
ip = models.CharField(verbose_name=_('IP'),
max_length=30,
blank=True,
default='')
data_hora = models.DateTimeField(
verbose_name=_('Data/Hora'),
auto_now_add=True,
blank=True,
null=True)
class Meta:
verbose_name = _('Votação')
@ -534,38 +550,22 @@ class SessaoPlenariaPresenca(models.Model):
ordering = ['parlamentar__nome_parlamentar']
@reversion.register()
class Bloco(models.Model):
'''
* blocos podem existir por mais de uma legislatura
'''
nome = models.CharField(
max_length=80, verbose_name=_('Nome do Bloco'))
partidos = models.ManyToManyField(
Partido, blank=True, verbose_name=_('Partidos'))
data_criacao = models.DateField(
blank=False, null=True, verbose_name=_('Data Criação'))
data_extincao = models.DateField(
blank=True, null=True, verbose_name=_('Data Dissolução'))
descricao = models.TextField(blank=True, verbose_name=_('Descrição'))
# campo conceitual de reversão genérica para o model Autor que dá a
# o meio possível de localização de tipos de autores.
autor = SaplGenericRelation(Autor,
related_query_name='bloco_set',
fields_search=(
('nome', '__icontains'),
('descricao', '__icontains'),
('partidos__sigla', '__icontains'),
('partidos__nome', '__icontains'),
))
class Meta:
verbose_name = _('Bloco Parlamentar')
verbose_name_plural = _('Blocos Parlamentares')
def __str__(self):
return self.nome
ORDENACAO_RESUMO = [
('id_basica', 'Identificação Básica'),
('cont_mult', 'Conteúdo Multimídia'),
('mesa_d', 'Mesa Diretora'),
('lista_p', 'Lista de Presença'),
('exp', 'Expedientes'),
('mat_exp', 'Matérias do Expediente'),
('v_n_mat_exp', 'Votações Nominais - Matérias do Expediente'),
('oradores_exped', 'Oradores do Expediente'),
('lista_p_o_d', 'Lista de Presença Ordem do Dia'),
('mat_o_d', 'Matérias da Ordem do Dia'),
('v_n_mat_o_d', 'Votações Nominais - Matérias da Ordem do Dia'),
('oradores_o_d', 'Oradores da Ordem do Dia'),
('oradores_expli', 'Oradores das Explicações Pessoais'),
('ocorr_sessao', 'Ocorrências da Sessão')
]
@reversion.register()
@ -574,20 +574,62 @@ class ResumoOrdenacao(models.Model):
Tabela para registrar em qual ordem serão renderizados os componentes
da tela de resumo de uma sessão
'''
primeiro = models.CharField(max_length=30)
segundo = models.CharField(max_length=30)
terceiro = models.CharField(max_length=30)
quarto = models.CharField(max_length=30)
quinto = models.CharField(max_length=30)
sexto = models.CharField(max_length=30)
setimo = models.CharField(max_length=30)
oitavo = models.CharField(max_length=30)
nono = models.CharField(max_length=30)
decimo = models.CharField(max_length=30)
decimo_primeiro = models.CharField(max_length=30,default="Ocorrências da Sessão")
decimo_segundo = models.CharField(max_length=30, default="Votos Nominais Mat Expediente")
decimo_terceiro = models.CharField(max_length=30, default="Votos Nominais Mat Ordem Dia")
decimo_quarto = models.CharField(max_length=30, default="Oradores da Ordem do Dia")
primeiro = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[0][0]
)
segundo = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[1][0]
)
terceiro = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[2][0]
)
quarto = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[3][0]
)
quinto = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[4][0]
)
sexto = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[5][0]
)
setimo = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[6][0]
)
oitavo = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[7][0]
)
nono = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[8][0]
)
decimo = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[9][0]
)
decimo_primeiro = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[10][0]
)
decimo_segundo = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[11][0]
)
decimo_terceiro = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[12][0]
)
decimo_quarto = models.CharField(
max_length=50,
default=ORDENACAO_RESUMO[13][0]
)
class Meta:
verbose_name = _('Ordenação do Resumo de uma Sessão')
@ -596,6 +638,7 @@ class ResumoOrdenacao(models.Model):
def __str__(self):
return 'Ordenação do Resumo de uma Sessão'
@reversion.register()
class TipoRetiradaPauta(models.Model):
descricao = models.CharField(max_length=150, verbose_name=_('Descrição'))
@ -687,6 +730,7 @@ class JustificativaAusencia(models.Model):
using=using,
update_fields=update_fields)
class RetiradaPauta(models.Model):
materia = models.ForeignKey(MateriaLegislativa,
on_delete=models.CASCADE,

6
sapl/sessao/urls.py

@ -2,8 +2,8 @@ from django.conf.urls import include, url
from sapl.sessao.views import (AdicionarVariasMateriasExpediente,
AdicionarVariasMateriasOrdemDia, BancadaCrud,
BlocoCrud, CargoBancadaCrud,
ExpedienteMateriaCrud, ExpedienteView, JustificativaAusenciaCrud,
CargoBancadaCrud, ExpedienteMateriaCrud,
ExpedienteView, JustificativaAusenciaCrud,
OcorrenciaSessaoView, MateriaOrdemDiaCrud, OradorOrdemDiaCrud,
MesaView, OradorCrud,
OradorExpedienteCrud, PainelView,
@ -96,8 +96,6 @@ urlpatterns = [
include(TipoRetiradaPautaCrud.get_urls())),
url(r'^sistema/bancada/',
include(BancadaCrud.get_urls())),
url(r'^sistema/bloco/',
include(BlocoCrud.get_urls())),
url(r'^sistema/cargo-bancada/',
include(CargoBancadaCrud.get_urls())),
url(r'^sistema/resumo-ordenacao/',

190
sapl/sessao/views.py

@ -35,21 +35,21 @@ from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato,
Parlamentar, SessaoLegislativa)
from sapl.sessao.apps import AppConfig
from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm
from sapl.utils import show_results_filter_set, remover_acentos
from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip
from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, BlocoForm,
from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm,
ExpedienteForm, JustificativaAusenciaForm, OcorrenciaSessaoForm, ListMateriaForm,
MesaForm, OradorExpedienteForm, OradorForm, PautaSessaoFilterSet,
PresencaForm, ResumoOrdenacaoForm, SessaoPlenariaFilterSet,
SessaoPlenariaForm, VotacaoEditForm, VotacaoForm,
VotacaoNominalForm, RetiradaPautaForm, OradorOrdemDiaForm)
from .models import (Bancada, Bloco, CargoBancada, CargoMesa,
from .models import (Bancada, CargoBancada, CargoMesa,
ExpedienteMateria, ExpedienteSessao, OcorrenciaSessao, IntegranteMesa,
MateriaLegislativa, Orador, OradorExpediente, OrdemDia,
PresencaOrdemDia, RegistroVotacao, ResumoOrdenacao,
SessaoPlenaria, SessaoPlenariaPresenca, TipoExpediente,
TipoResultadoVotacao, TipoSessaoPlenaria, VotoParlamentar, TipoRetiradaPauta,
RetiradaPauta, TipoJustificativa, JustificativaAusencia, OradorOrdemDia)
RetiradaPauta, TipoJustificativa, JustificativaAusencia, OradorOrdemDia, ORDENACAO_RESUMO)
TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria')
@ -679,16 +679,6 @@ class BancadaCrud(CrudAux):
return reverse('sapl.sessao:bancada_list')
class BlocoCrud(CrudAux):
model = Bloco
class CreateView(CrudAux.CreateView):
form_class = BlocoForm
def get_success_url(self):
return reverse('sapl.sessao:bloco_list')
def recuperar_numero_sessao(request):
try:
sessao = SessaoPlenaria.objects.filter(
@ -1296,49 +1286,38 @@ class ResumoOrdenacaoView(PermissionRequiredMixin, FormView):
form_class = ResumoOrdenacaoForm
permission_required = {'sessao.change_resumoordenacao'}
def get_success_url(self):
return reverse('sapl.base:sistema')
def get_tupla(self, tupla_key):
for tupla in ORDENACAO_RESUMO:
if tupla[0] == tupla_key:
return tupla
def get_initial(self):
initial = super(ResumoOrdenacaoView, self).get_initial()
ordenacao = ResumoOrdenacao.objects.first()
if ordenacao:
initial.update({'primeiro': ordenacao.primeiro,
'segundo': ordenacao.segundo,
'terceiro': ordenacao.terceiro,
'quarto': ordenacao.quarto,
'quinto': ordenacao.quinto,
'sexto': ordenacao.sexto,
'setimo': ordenacao.setimo,
'oitavo': ordenacao.oitavo,
'nono': ordenacao.nono,
'decimo': ordenacao.decimo,
'decimo_primeiro': ordenacao.decimo_primeiro,
'decimo_segundo': ordenacao.decimo_segundo,
'decimo_terceiro': ordenacao.decimo_terceiro,
'decimo_quarto': ordenacao.decimo_quarto})
return initial
def form_valid(self, form):
ordenacao = ResumoOrdenacao.objects.get_or_create()[0]
ordenacao.primeiro = form.cleaned_data['primeiro']
ordenacao.segundo = form.cleaned_data['segundo']
ordenacao.terceiro = form.cleaned_data['terceiro']
ordenacao.quarto = form.cleaned_data['quarto']
ordenacao.quinto = form.cleaned_data['quinto']
ordenacao.sexto = form.cleaned_data['sexto']
ordenacao.setimo = form.cleaned_data['setimo']
ordenacao.oitavo = form.cleaned_data['oitavo']
ordenacao.nono = form.cleaned_data['nono']
ordenacao.decimo = form.cleaned_data['decimo']
ordenacao.decimo_primeiro = form.cleaned_data['decimo_primeiro']
ordenacao.decimo_segundo = form.cleaned_data['decimo_segundo']
ordenacao.decimo_terceiro = form.cleaned_data['decimo_terceiro']
ordenacao.decimo_quarto = form.cleaned_data['decimo_quarto']
ordenacao.save()
initial = {
'primeiro': self.get_tupla(ordenacao.primeiro),
'segundo': self.get_tupla(ordenacao.segundo),
'terceiro': self.get_tupla(ordenacao.terceiro),
'quarto': self.get_tupla(ordenacao.quarto),
'quinto': self.get_tupla(ordenacao.quinto),
'sexto': self.get_tupla(ordenacao.sexto),
'setimo': self.get_tupla(ordenacao.setimo),
'oitavo': self.get_tupla(ordenacao.oitavo),
'nono': self.get_tupla(ordenacao.nono),
'decimo': self.get_tupla(ordenacao.decimo),
'decimo_primeiro': self.get_tupla(ordenacao.decimo_primeiro),
'decimo_segundo': self.get_tupla(ordenacao.decimo_segundo),
'decimo_terceiro': self.get_tupla(ordenacao.decimo_terceiro),
'decimo_quarto': self.get_tupla(ordenacao.decimo_quarto)
}
return initial
def get_success_url(self):
return reverse('sapl.base:sistema')
def form_valid(self, form):
form.save()
return HttpResponseRedirect(self.get_success_url())
@ -1761,7 +1740,6 @@ class ResumoView(DetailView):
context.update(get_ocorrencias_da_sessão(self.object))
# =====================================================================
# Indica a ordem com a qual o template será renderizado
ordenacao = ResumoOrdenacao.objects.first()
dict_ord_template = {
'cont_mult': 'conteudo_multimidia.html',
'exp': 'expedientes.html',
@ -1779,59 +1757,23 @@ class ResumoView(DetailView):
'ocorr_sessao': 'ocorrencias_da_sessao.html'
}
if ordenacao:
try:
context.update(
{'primeiro_ordenacao': dict_ord_template[ordenacao.primeiro],
'segundo_ordenacao': dict_ord_template[ordenacao.segundo],
'terceiro_ordenacao': dict_ord_template[ordenacao.terceiro],
'quarto_ordenacao': dict_ord_template[ordenacao.quarto],
'quinto_ordenacao': dict_ord_template[ordenacao.quinto],
'sexto_ordenacao': dict_ord_template[ordenacao.sexto],
'setimo_ordenacao': dict_ord_template[ordenacao.setimo],
'oitavo_ordenacao': dict_ord_template[ordenacao.oitavo],
'nono_ordenacao': dict_ord_template[ordenacao.nono],
'decimo_ordenacao': dict_ord_template[ordenacao.decimo],
'decimo_primeiro_ordenacao': dict_ord_template[ordenacao.decimo_primeiro],
'decimo_segundo_ordenacao': dict_ord_template[ordenacao.decimo_segundo],
'decimo_terceiro_ordenacao': dict_ord_template[ordenacao.decimo_terceiro],
'decimo_quarto_ordenacao': dict_ord_template[ordenacao.decimo_quarto]})
except KeyError as e:
self.logger.error('user=' + self.request.user.username + '. ' + "KeyError: " + str(e) + ". Erro "
"ao tentar utilizar configuração de ordenação. Utilizando ordenação padrão.")
context.update(
{'primeiro_ordenacao': dict_ord_template['id_basica'],
'segundo_ordenacao': dict_ord_template['cont_mult'],
'terceiro_ordenacao': dict_ord_template['mesa_d'],
'quarto_ordenacao': dict_ord_template['lista_p'],
'quinto_ordenacao': dict_ord_template['exp'],
'sexto_ordenacao': dict_ord_template['mat_exp'],
'setimo_ordenacao': dict_ord_template['v_n_mat_exp'],
'oitavo_ordenacao': dict_ord_template['oradores_exped'],
'nono_ordenacao': dict_ord_template['lista_p_o_d'],
'decimo_ordenacao': dict_ord_template['mat_o_d'],
'decimo_primeiro_ordenacao': dict_ord_template['v_n_mat_o_d'],
'decimo_segundo_ordenacao': dict_ord_template['oradores_o_d'],
'decimo_terceiro_ordenacao': dict_ord_template['oradores_expli'],
'decimo_quarto_ordenacao': dict_ord_template['ocorr_sessao']
})
else:
context.update(
{'primeiro_ordenacao': dict_ord_template['id_basica'],
'segundo_ordenacao': dict_ord_template['cont_mult'],
'terceiro_ordenacao': dict_ord_template['mesa_d'],
'quarto_ordenacao': dict_ord_template['lista_p'],
'quinto_ordenacao': dict_ord_template['exp'],
'sexto_ordenacao': dict_ord_template['mat_exp'],
'setimo_ordenacao': dict_ord_template['v_n_mat_exp'],
'oitavo_ordenacao': dict_ord_template['oradores_exped'],
'nono_ordenacao': dict_ord_template['lista_p_o_d'],
'decimo_ordenacao': dict_ord_template['mat_o_d'],
'decimo_primeiro_ordenacao': dict_ord_template['v_n_mat_o_d'],
'decimo_segundo_ordenacao': dict_ord_template['oradores_o_d'],
'decimo_terceiro_ordenacao': dict_ord_template['oradores_expli'],
'decimo_quarto_ordenacao': dict_ord_template['ocorr_sessao']
})
ordenacao = ResumoOrdenacao.objects.get_or_create()[0]
context.update({
'primeiro_ordenacao': dict_ord_template[ordenacao.primeiro],
'segundo_ordenacao': dict_ord_template[ordenacao.segundo],
'terceiro_ordenacao': dict_ord_template[ordenacao.terceiro],
'quarto_ordenacao': dict_ord_template[ordenacao.quarto],
'quinto_ordenacao': dict_ord_template[ordenacao.quinto],
'sexto_ordenacao': dict_ord_template[ordenacao.sexto],
'setimo_ordenacao': dict_ord_template[ordenacao.setimo],
'oitavo_ordenacao': dict_ord_template[ordenacao.oitavo],
'nono_ordenacao': dict_ord_template[ordenacao.nono],
'decimo_ordenacao': dict_ord_template[ordenacao.decimo],
'decimo_primeiro_ordenacao': dict_ord_template[ordenacao.decimo_primeiro],
'decimo_segundo_ordenacao': dict_ord_template[ordenacao.decimo_segundo],
'decimo_terceiro_ordenacao': dict_ord_template[ordenacao.decimo_terceiro],
'decimo_quarto_ordenacao': dict_ord_template[ordenacao.decimo_quarto]
})
return context
@ -2163,6 +2105,8 @@ class VotacaoView(SessaoPermissionMixin):
votacao.ordem_id = ordem_id
votacao.tipo_resultado_votacao_id = int(
request.POST['resultado_votacao'])
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save()
except Exception as e:
username = request.user.username
@ -2383,6 +2327,8 @@ class VotacaoNominalAbstract(SessaoPermissionMixin):
votacao.numero_votos_nao = votos_nao
votacao.numero_abstencoes = abstencoes
votacao.observacao = request.POST.get('observacao', None)
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.materia_id = materia_votacao.materia.id
if self.ordem:
@ -2410,6 +2356,8 @@ class VotacaoNominalAbstract(SessaoPermissionMixin):
voto_parlamentar.voto = voto
voto_parlamentar.parlamentar_id = parlamentar_id
voto_parlamentar.votacao_id = votacao.id
voto_parlamentar.user = request.user
voto_parlamentar.ip = get_client_ip(request)
voto_parlamentar.save()
resultado = form.cleaned_data['resultado_votacao']
@ -2847,10 +2795,10 @@ class VotacaoExpedienteView(SessaoPermissionMixin):
if (int(request.POST['voto_presidente']) == 0):
qtde_presentes -= 1
if (qtde_votos > qtde_presentes or qtde_votos < qtde_presentes):
if qtde_votos != qtde_presentes:
form._errors["total_votos"] = ErrorList([u""])
return self.render_to_response(context)
elif (qtde_presentes == qtde_votos):
else:
try:
votacao = RegistroVotacao()
votacao.numero_votos_sim = int(request.POST['votos_sim'])
@ -2861,6 +2809,8 @@ class VotacaoExpedienteView(SessaoPermissionMixin):
votacao.expediente_id = expediente_id
votacao.tipo_resultado_votacao_id = int(
request.POST['resultado_votacao'])
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save()
except Exception as e:
username = request.user.username
@ -3544,9 +3494,13 @@ class VotacaoEmBlocoExpediente(PermissionRequiredForAppCrudMixin, ListView):
def get_context_data(self, **kwargs):
context = super(VotacaoEmBlocoExpediente,
self).get_context_data(**kwargs)
context['turno_choices'] = Tramitacao.TURNO_CHOICES
context['pk'] = self.kwargs['pk']
context['root_pk'] = self.kwargs['pk']
if not verifica_sessao_iniciada(self.request, self.kwargs['pk']):
context['sessao_iniciada'] = False
return context
context['sessao_iniciada'] = True
context['turno_choices'] = Tramitacao.TURNO_CHOICES
context['title'] = SessaoPlenaria.objects.get(id=self.kwargs['pk'])
return context
@ -3566,9 +3520,13 @@ class VotacaoEmBlocoOrdemDia(PermissionRequiredForAppCrudMixin, ListView):
def get_context_data(self, **kwargs):
context = super(VotacaoEmBlocoOrdemDia,
self).get_context_data(**kwargs)
context['turno_choices'] = Tramitacao.TURNO_CHOICES
context['pk'] = self.kwargs['pk']
context['root_pk'] = self.kwargs['pk']
if not verifica_sessao_iniciada(self.request, self.kwargs['pk']):
context['sessao_iniciada'] = False
return context
context['sessao_iniciada'] = True
context['turno_choices'] = Tramitacao.TURNO_CHOICES
context['title'] = SessaoPlenaria.objects.get(id=self.kwargs['pk'])
return context
@ -3640,6 +3598,8 @@ class VotacaoEmBlocoSimbolicaView(PermissionRequiredForAppCrudMixin, TemplateVie
resultado = TipoResultadoVotacao.objects.get(
id=request.POST['resultado_votacao'])
votacao.tipo_resultado_votacao = resultado
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save()
except Exception as e:
username = request.user.username
@ -3671,6 +3631,8 @@ class VotacaoEmBlocoSimbolicaView(PermissionRequiredForAppCrudMixin, TemplateVie
resultado = TipoResultadoVotacao.objects.get(
id=request.POST['resultado_votacao'])
votacao.tipo_resultado_votacao = resultado
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save()
except Exception as e:
username = request.user.username
@ -3848,6 +3810,8 @@ class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView)
votacao.materia = ordem.materia
votacao.ordem = ordem
votacao.tipo_resultado_votacao = form.cleaned_data['resultado_votacao']
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save()
for votos in request.POST.getlist('voto_parlamentar'):
@ -3862,6 +3826,8 @@ class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView)
voto_parlamentar.voto = voto
voto_parlamentar.parlamentar_id = parlamentar_id
voto_parlamentar.votacao_id = votacao.id
voto_parlamentar.user = request.user
voto_parlamentar.ip = get_client_ip(request)
voto_parlamentar.save()
ordem.resultado = form.cleaned_data['resultado_votacao'].nome
@ -3889,6 +3855,8 @@ class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView)
votacao.materia = expediente.materia
votacao.expediente = expediente
votacao.tipo_resultado_votacao = form.cleaned_data['resultado_votacao']
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save()
# Salva os votos de cada parlamentar
@ -3904,6 +3872,8 @@ class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView)
voto_parlamentar.voto = voto
voto_parlamentar.parlamentar_id = parlamentar_id
voto_parlamentar.votacao_id = votacao.id
voto_parlamentar.user = request.user
voto_parlamentar.ip = get_client_ip(request)
voto_parlamentar.save()
expediente.resultado = form.cleaned_data['resultado_votacao'].nome

2
sapl/settings.py

@ -41,7 +41,7 @@ ALLOWED_HOSTS = ['*']
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/?next='
SAPL_VERSION = '3.1.153'
SAPL_VERSION = '3.1.155'
if DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

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.153</span>
<span>Release: 3.1.155</span>
</p>
</div>
<div class="col-md-4">

8
sapl/templates/base/autores_duplicados.html

@ -11,16 +11,14 @@
<thead>
<tr>
<th>Autor</th>
<th>Tipo de Autor</th>
<th>Quantidade</th>
</tr>
</thead>
<tbody>
{% for autor, tipo, quantidade in autores_duplicados %}
{% for autor in autores_duplicados %}
<tr>
<td>{{ autor }}</td>
<td>{{ tipo }}</td>
<td>{{ quantidade }}</td>
<td>{{ autor.nome }}</td>
<td>{{ autor.count }}</td>
</tr>
{% endfor %}
</tbody>

8
sapl/templates/base/layouts.yaml

@ -18,8 +18,9 @@ AppConfig:
- esfera_federacao
{% trans 'Proposições e Protocolo' %}:
- sequencia_numeracao proposicao_incorporacao_obrigatoria receber_recibo_proposicao
- escolher_numero_materia_proposicao protocolo_manual
- sequencia_numeracao_proposicao sequencia_numeracao_protocolo
- protocolo_manual receber_recibo_proposicao
- proposicao_incorporacao_obrigatoria escolher_numero_materia_proposicao
{% trans 'Textos Articulados' %}:
- texto_articulado_proposicao texto_articulado_materia texto_articulado_norma
@ -31,7 +32,8 @@ AppConfig:
- assinatura_ata
{% trans 'Cronômetros do Painel' %}:
- cronometro_discurso cronometro_aparte cronometro_ordem cronometro_consideracoes
- cronometro_discurso cronometro_aparte
- cronometro_ordem cronometro_consideracoes
{% trans 'Configurações do Painel' %}:
- mostrar_brasao_painel

27
sapl/templates/compilacao/text_list_bloco.html

@ -20,7 +20,6 @@
<div class="{{ dpt.tipo_dispositivo.class_css }} {% dispositivo_desativado dpt view.inicio_vigencia view.fim_vigencia %} ">
<div class="dptt {% dispositivo_desativado dpt view.inicio_vigencia view.fim_vigencia %}" id="dptt{{dpt.pk}}" >
{% if not dpt.tipo_dispositivo.dispositivo_de_articulacao or dpt.tipo_dispositivo.dispositivo_de_articulacao and dpt.dispositivo_subsequente %}
{% if dpt.auto_inserido %}
{{ dpt.dispositivo_pai.tipo_dispositivo.rotulo_prefixo_html|safe }}
@ -31,6 +30,7 @@
<a class="dpt-link" name="{{dpt.pk}}" title="{{dpt.pk}}">{{ dpt.rotulo }}</a>
{{ dpt.tipo_dispositivo.rotulo_sufixo_html|safe }}
{% endif %}
{% endif %}
<span class="dtxt" id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}" pks="{{dpt.dispositivo_substituido_id|default:''}}" pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|safe }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
@ -43,20 +43,21 @@
&nbsp;
{% endif %}
</a>
{% endif %}
{% if user.is_authenticated and not dpt.tipo_dispositivo.dispositivo_de_articulacao%}
{% if perms.compilacao.add_nota or perms.compilacao.add_vide or perms.compilacao.change_dispositivo%}
<div class="dne" id="dne{{dpt.pk}}" pk="{{dpt.pk}}">
<ul class="btns-action">
{% if perms.compilacao.change_dispositivo %}<li><a href = "{% url 'sapl.compilacao:ta_text_edit' dpt.ta.pk%}#{{dpt.pk}}" class="btn-action" title="{% trans 'Editar Dispositivo'%}">Ed</a></li>{% endif %}
{% if perms.compilacao.add_nota %}<li><a class="btn-action btn-nota-create" model="nota" pk="{{dpt.pk}}" title="{% trans 'Adicionar Nota'%}">N</a></li>{% endif %}
{% if perms.compilacao.add_vide %}<li><a class="btn-action btn-vide-create" model="vide" pk="{{dpt.pk}}" title="{% trans 'Adicionar Vide'%}">V</a></li>{% endif %}
</ul>
<div class="dne-form clearfix"></div>
</div>
{% endif %}
{% if user.is_authenticated and not dpt.tipo_dispositivo.dispositivo_de_articulacao%}
{% if perms.compilacao.add_nota or perms.compilacao.add_vide or perms.compilacao.change_dispositivo%}
<div class="dne" id="dne{{dpt.pk}}" pk="{{dpt.pk}}">
<ul class="btns-action">
{% if perms.compilacao.change_dispositivo %}<li><a href = "{% url 'sapl.compilacao:ta_text_edit' dpt.ta.pk%}#{{dpt.pk}}" class="btn-action" title="{% trans 'Editar Dispositivo'%}">Ed</a></li>{% endif %}
{% if perms.compilacao.add_nota %}<li><a class="btn-action btn-nota-create" model="nota" pk="{{dpt.pk}}" title="{% trans 'Adicionar Nota'%}">N</a></li>{% endif %}
{% if perms.compilacao.add_vide %}<li><a class="btn-action btn-vide-create" model="vide" pk="{{dpt.pk}}" title="{% trans 'Adicionar Vide'%}">V</a></li>{% endif %}
</ul>
<div class="dne-form clearfix"></div>
</div>
{% endif %}
{% endif %}
</div>
{% if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}

6
sapl/templates/materia/em_lote/anexada.html

@ -8,11 +8,11 @@
{% endif %}
{% if show_results %}
{% if object_list.count > 0 %}
{% if object_list.count == 1 %}
{% if numero_res > 0 %}
{% if numero_res == 1 %}
<h3 style="text-align: right;">{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 matéria.'%}</h3>
{% else %}
<h3 style="text-align: right;">{% blocktrans with object_list.count as total_materias %}Foram encontradas {{total_materias}} matérias.{% endblocktrans %}</h3>
<h3 style="text-align: right;">Foram encontradas {{ numero_res }} matérias.</h3>
{% endif %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}

38
sapl/templates/materia/tramitacao_detail.html

@ -0,0 +1,38 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% block detail_content %}
{{ block.super }}
{% if user.is_superuser %}
<div class="row">
{% if tramitacao.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' user.pk %}">{{tramitacao.user}}</a>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% if tramitacao.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">
{{tramitacao.ip}}
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
{% endblock detail_content %}

2
sapl/templates/menu_tabelas_auxiliares.yaml

@ -78,7 +78,7 @@
url: sapl.parlamentares:frente_list
css_class: btn btn-link
- title: {% trans 'Bloco Parlamentar' %}
url: sapl.sessao:bloco_list
url: sapl.parlamentares:bloco_list
css_class: btn btn-link
- title: {% trans 'Módulo Proposições' %}
css_class: head_title

2
sapl/templates/navbar.yaml

@ -75,7 +75,7 @@
- title: {% trans 'Administração de Usuários' %}
url: {% url 'sapl.base:usuario' %}
check_permission: user.is_superuser
- title: {% trans 'Inconsistências da Aplicação' %}
- title: {% trans 'Inconsistências de Dados' %}
url: {% url 'sapl.base:lista_inconsistencias' %}
check_permission: user.is_superuser

7
sapl/templates/parlamentares/layouts.yaml

@ -127,3 +127,10 @@ Votante:
{% trans 'Votante' %}:
- parlamentar user
- data
Bloco:
{% trans 'Bloco' %}:
- nome
- data_criacao data_extincao
- partidos
- descricao

13
sapl/templates/protocoloadm/anexado_list.html

@ -0,0 +1,13 @@
{% extends "crud/list.html" %}
{% load i18n %}
{% load common_tags %}
{% block more_buttons %}
{% if perms|get_add_perm:view %}
<a href="{% url 'sapl.protocoloadm:anexado_em_lote' root_pk %}" class="btn btn-outline-primary">
{% trans "Adicionar Anexado em Lote" %}
</a>
{% endif %}
{% endblock more_buttons %}

5
sapl/templates/protocoloadm/em_lote/anexado.html

@ -54,7 +54,7 @@
<tr>
<td>
<input type="checkbox" name="documento_id" value="{{documento.id}}" {% if check %} checked {% endif %}/>
{{documento.tipo.sigla}} {{documento.numero}}/{{documento.ano}} - {{documento.tipo.descricao}}
<a href="{% url 'sapl_index' %}docadm/{{documento.pk}}">{{documento.tipo.sigla}} {{documento.numero}}/{{documento.ano}} - {{documento.tipo.descricao}}</a>
</td>
</tr>
{% endfor %}
@ -63,6 +63,7 @@
</fieldset>
<input type="submit" value="Salvar" class="btn btn-primary"S>
</form>
<br/>
{% else %}
<tr>
<td>
@ -75,7 +76,7 @@
{% block extra_js %}
<script language="JavaScript">
function checkAll(elem) {
let checkboxes = document.getElementsByName('materia_id');
let checkboxes = document.getElementsByName('documento_id');
for (let i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].type == 'checkbox')
checkboxes[i].checked = elem.checked;

19
sapl/templates/protocoloadm/layouts.yaml

@ -10,6 +10,8 @@ DocumentoAdministrativo:
- assunto
- interessado tramitacao
- texto_integral
- documento_anexado_set__documento_principal|m2m_urlize_for_detail
- documento_principal_set__documento_anexado|m2m_urlize_for_detail
{% trans 'Outras Informações' %}:
- numero_externo
- dias_prazo data_fim_prazo
@ -28,11 +30,22 @@ StatusTramitacaoAdministrativo:
TramitacaoAdministrativo:
{% trans 'Tramitação' %}:
- data_tramitacao:4 unidade_tramitacao_local
- status:4 unidade_tramitacao_destino
- data_encaminhamento data_fim_prazo
- data_tramitacao unidade_tramitacao_local
- unidade_tramitacao_destino data_encaminhamento data_fim_prazo
- status urgente
- texto
Anexado:
{% trans 'Documento Anexado' %}:
- tipo numero ano
- data_anexacao data_desanexacao
AnexadoDetail:
{% trans 'Documento Anexado' %}:
- documento_principal|fk_urlize_for_detail
- documento_anexado|fk_urlize_for_detail
- data_anexacao data_desanexacao
Protocolo:
{% trans 'Indentificação Documento' %}:
- tipo_protocolo

2
sapl/templates/protocoloadm/subnav.yaml

@ -1,6 +1,8 @@
{% load i18n common_tags %}
- title: {% trans 'Início' %}
url: documentoadministrativo_detail
- title: {% trans 'Anexado' %}
url: anexado_list
- title: {% trans 'Tramitação' %}
url: tramitacaoadministrativo_list
- title: {% trans 'Documento Acessório' %}

37
sapl/templates/protocoloadm/tramitacaoadministrativo_detail.html

@ -1,5 +1,6 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% block actions %}
{% load common_tags %}
@ -13,3 +14,39 @@
{% endif %}
</div>
{% endblock actions %}
{% block detail_content %}
{{ block.super }}
{% if user.is_superuser %}
<div class="row">
{% if tramitacaoadministrativo.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' user.pk %}">{{tramitacaoadministrativo.user}}</a>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% if tramitacaoadministrativo.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">
{{tramitacaoadministrativo.ip}}
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
{% endblock detail_content %}

10
sapl/templates/relatorios/relatorio_ata.html

@ -52,7 +52,7 @@
{% if assinatura_mesa or assinatura_presentes %}
<div style="page-break-before: always;padding:5px;margin-top:0px;border-top: 1px solid black;">
<div style="padding:5px;margin-top:50px;border-top: 1px solid black;">
<legend >{{texto_assinatura}}</legend>
<table style="margin-top:80px">
@ -65,12 +65,12 @@
<tr style="margin-top:20px">
<td>
<div style="float: left; position: relative; top: -50px; left: 8px; width: 120px">____________________ </br>
<p style="font-size:8pt"><b>{{p.cargo}}: </b> {{p.parlamentar.nome_completo}} / {{ p.parlamentar|filiacao_data_filter:object.data_inicio }}</p>
<p style="font-size:8pt"><b>{{p.cargo}}: </b> {{p.parlamentar.nome_completo}} / {% if p.parlamentar|filiacao_data_filter:object.data_inicio %} {{ p.parlamentar|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}</p>
</br></br></br>
</div>
{% else %}
<div style="float: left; position: relative; top: -50px; left: 142px; width: 120px; margin-right:-220px;">____________________ </br>
<p style="font-size:8pt"><b>{{p.cargo}}: </b> {{p.parlamentar.nome_completo}} / {{ p.parlamentar|filiacao_data_filter:object.data_inicio }}</p>
<p style="font-size:8pt"><b>{{p.cargo}}: </b> {{p.parlamentar.nome_completo}} / {% if p.parlamentar|filiacao_data_filter:object.data_inicio %} {{ p.parlamentar|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}</p>
</br></br></br>
</div>
</td>
@ -84,12 +84,12 @@
<td>
<div style="float: left; position: relative;top: -50px; left: 8px; width: 120px;">_____________________</br>
<p style="font-size:8pt">
{{p.nome_completo}} / {{ p|filiacao_data_filter:object.data_inicio }}</p>
{{p.nome_completo}} / {% if p|filiacao_data_filter:object.data_inicio %} {{ p|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}</p>
</br></br></br>
</div>
{% else %}
<div style="float: left; position: relative; top: -50px;left: 142px; width: 120px; margin-right:-220px;">_____________________ </br>
<p style="font-size:8pt">{{p.nome_completo}} / {{ p|filiacao_data_filter:object.data_inicio }}</p>
<p style="font-size:8pt">{{p.nome_completo}} / {% if p|filiacao_data_filter:object.data_inicio %} {{ p|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}</p>
</br></br></br>
</div>
</td>

4
sapl/templates/sessao/blocos_ata/assinaturas.html

@ -5,13 +5,13 @@
</br></br>
{% for p in assinatura_mesa %}
<div class="col-md-6">___________________________________________ </br>
<b>{{p.cargo}}: </b> {{p.parlamentar.nome_completo}} / {{ p.parlamentar|filiacao_data_filter:object.data_inicio }}
<b>{{p.cargo}}: </b> {{p.parlamentar.nome_completo}} / {% if p.parlamentar|filiacao_data_filter:object.data_inicio %} {{ p.parlamentar|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}
</br></br></br>
</div>
{% endfor %}
{% for p in assinatura_presentes %}
<div class="col-md-6">___________________________________________ </br>
{{p.nome_completo}} / {{ p|filiacao_data_filter:object.data_inicio }}
{{p.nome_completo}} / {% if p|filiacao_data_filter:object.data_inicio %} {{ p|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}
</br></br></br>
</div>
{% endfor %}

22
sapl/templates/sessao/blocos_ata/lista_presenca.html

@ -1,22 +1,22 @@
{% load common_tags %}
<fieldset>
<p align="justify">
<p align="justify">
{% if presenca_sessao %}
<strong>Lista de Presença na Sessão: </strong>
{% for p in presenca_sessao %}
{{p.nome_completo}} / {{ p|filiacao_data_filter:object.data_inicio }}
{% if not forloop.last %} ; {% endif %}
{{p.nome_completo}} / {% if p|filiacao_data_filter:object.data_inicio %} {{ p|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}
{% if not forloop.last %} ; {% endif %}
{% endfor %}
{% endif %}
</p>
<p align="justify">
{% if justificativa_ausencia %}
{% endif %}
</p>
<p align="justify">
{% if justificativa_ausencia %}
<strong>Justificativas de Ausências na Sessão: </strong>
{% for j in justificativa_ausencia %}
{{j.parlamentar.nome_completo}} / {{ j.tipo_ausencia }}
{% if not forloop.last %} ; {% endif %}
{{j.parlamentar.nome_completo}} / {{ j.tipo_ausencia }}
{% if not forloop.last %} ; {% endif %}
{% endfor %}
{% endif %}
</p>
{% endif %}
</p>
</fieldset>

2
sapl/templates/sessao/blocos_ata/lista_presenca_ordem_dia.html

@ -5,7 +5,7 @@
{% if presenca_ordem %}
<strong>Lista de Presença na Ordem do Dia: </strong>
{% for p in presenca_ordem %}
{{p.nome_completo}} / {{ p|filiacao_data_filter:object.data_inicio }}
{{p.nome_completo}} / {% if p|filiacao_data_filter:object.data_inicio %} {{ p|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}
{% if not forloop.last %} ; {% endif %}
{% endfor %}
{% endif %}

18
sapl/templates/sessao/blocos_ata/materias_expediente.html

@ -1,9 +1,9 @@
<fieldset>
<p align="justify">
<p align="justify">
{% if materia_expediente %}
<strong>Matérias do Expediente: </strong>
{% for m in materia_expediente %}
<b>{{m.numero}} - {{m.titulo}}</b>,
<strong>Matérias do Expediente: </strong>
{% for m in materia_expediente %}
<b>{{m.numero}} - {{m.titulo}}</b>,
{{m.ementa|safe}}
Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }},
{% if m.numero_protocolo %}
@ -12,19 +12,19 @@
{% if m.numero_processo %}
Processo: {{ m.numero_processo }},
{% endif %}
{%if m.turno %}
{% if m.turno %}
Turno: {{m.turno}},
{%endif %}
{%if m.tipo_votacao %}
{% endif %}
{% if m.tipo_votacao %}
Tipo: {{m.tipo_votacao}},
Sim: {{ m.voto_sim }},
Não: {{ m.voto_nao }},
Abstenções: {{m.voto_abstencoes}},
{% endif %}
Resultado:</b> {{m.resultado}}
{% if m.resultado_observacao %} - {{m.resultado_observacao}} {% endif %}
{% if m.resultado_observacao %} - {{m.resultado_observacao}} {% endif %}
{% if m.voto_nominal%}
<b>Votos Nominais :</b>
<b>Votos Nominais :</b>
{% for voto in m.voto_nominal %}
{{voto.0}} - {{voto.1}}
{% if not forloop.last %} ; {% endif %}

63
sapl/templates/sessao/blocos_ata/materias_ordem_dia.html

@ -1,33 +1,36 @@
<fieldset>
<p align="justify">
<strong>Matérias da Ordem do Dia: </strong>
{% for m in materias_ordem %}
<b>{{m.numero}} - {{m.titulo}}</b>,
{{m.ementa|safe}}
Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }},
{% if m.numero_protocolo %}
Número de Protocolo: {{ m.numero_protocolo }},
{% endif %}
{% if m.numero_processo %}
Processo: {{ m.numero_processo }},
{% endif %}
{%if m.turno %}
Turno: {{m.turno}},
{%endif %}
{%if m.tipo_votacao %}
Tipo: {{m.tipo_votacao}},
Sim: {{ m.voto_sim }},
Não: {{ m.voto_nao }},
Abstenções: {{m.voto_abstencoes}},
{% endif %}
Resultado:</b> {{m.resultado}}
{% if m.resultado_observacao %} - {{m.resultado_observacao}} {% endif %}
{% if m.voto_nominal%}
<b>Votos Nominais :</b>
{% for voto in m.voto_nominal %}
{{voto.0}} - {{voto.1}}
{% if not forloop.last %} ; {% endif %}
<p align="justify">
{% if materias_ordem %}
<strong>Matérias da Ordem do Dia: </strong>
{% for m in materias_ordem %}
<b>{{m.numero}} - {{m.titulo}}</b>,
{{m.ementa|safe}}
Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }},
{% if m.numero_protocolo %}
Número de Protocolo: {{ m.numero_protocolo }},
{% endif %}
{% if m.numero_processo %}
Processo: {{ m.numero_processo }},
{% endif %}
{% if m.turno %}
Turno: {{m.turno}},
{% endif %}
{% if m.tipo_votacao %}
Tipo: {{m.tipo_votacao}},
Sim: {{ m.voto_sim }},
Não: {{ m.voto_nao }},
Abstenções: {{m.voto_abstencoes}},
{% endif %}
Resultado:</b> {{m.resultado}}
{% if m.resultado_observacao %} - {{m.resultado_observacao}} {% endif %}
{% if m.voto_nominal%}
<b>Votos Nominais :</b>
{% for voto in m.voto_nominal %}
{{voto.0}} - {{voto.1}}
{% if not forloop.last %} ; {% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
</p>
</fieldset>

15
sapl/templates/sessao/blocos_ata/mesa_diretora.html

@ -1,13 +1,14 @@
{% load common_tags %}
<fieldset>
<p align="justify">
{% if mesa %}
<p align="justify">
{% if mesa %}
<strong>Mesa Diretora: </strong>
{% for m in mesa %}
{{m.cargo}}:
{{m.parlamentar.nome_completo}} / {{ m.parlamentar.filiacao_atual }}
{% if not forloop.last %} ; {% endif %}
{{m.parlamentar.nome_completo}} / {% if m.parlamentar|filiacao_data_filter:object.data_inicio %} {{ m.parlamentar|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}
{% if not forloop.last %} ; {% endif %}
{% endfor %}
{% endif %}
{% endif %}
</p>
</fieldset>
</fieldset>

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

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

11
sapl/templates/sessao/blocos_ata/oradores_expediente.html

@ -1,13 +1,14 @@
{% load common_tags %}
<fieldset>
<p align="justify">
<p align="justify">
{% if oradores %}
<strong>Oradores do Expediente: </strong>
{% for o in oradores %}
<b>{{o.numero_ordem}}</b> - {{o.parlamentar.nome_completo}} / {{ o.parlamentar.filiacao_atual }}
<b>{{o.numero_ordem}}</b> - {{o.parlamentar.nome_completo}} / {% if o.parlamentar|filiacao_data_filter:object.data_inicio %} {{ o.parlamentar|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}
{% if o.observacao %} - {{o.observacao}} {% endif %}
{% if not forloop.last %} ; {% endif %}
{% if not forloop.last %} ; {% endif %}
{% endfor %}
{% endif %}
</p>
</div>
</p>
</fieldset>

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

@ -1,9 +1,11 @@
{% load common_tags %}
<fieldset>
<p align="justify">
{% if oradores_explicacoes %}
<strong>Oradores das Explicações Pessoais: </strong>
<strong>Oradores das Explicações Pessoais: </strong>
{% for o in oradores_explicacoes %}
<b>{{o.numero_ordem}}</b> - {{o.parlamentar.nome_completo}} / {{ o.parlamentar.filiacao_atual }}
<b>{{o.numero_ordem}}</b> - {{o.parlamentar.nome_completo}} / {% if o.parlamentar|filiacao_data_filter:object.data_inicio %} {{ o.parlamentar|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}
{% if o.observacao %} - {{o.observacao}} {% endif %}
{% if not forloop.last %} ; {% endif %}
{% endfor %}

2
sapl/templates/sessao/blocos_resumo/lista_presenca_ordem_dia.html

@ -4,7 +4,7 @@
<legend>Lista de Presença na Ordem do Dia</legend>
<div class="row">
{% for p in presenca_ordem %}
<div class="col-md-12">{{p.nome_parlamentar}} / {% if p|filiacao_data_filter:object.data_inicio %} {{ p|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}</div>
<div class="col-md-12">{{p.nome_parlamentar}} / {% if p|filiacao_data_filter:object.data_inicio %} {{ p|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %} </div>
{% endfor %}
</div>
</fieldset>

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

@ -1,9 +1,11 @@
{% load common_tags %}
<fieldset>
<legend>Mesa Diretora</legend>
<div class="row">
{% for m in mesa %}
<div class="col-md-12"><b>{{m.cargo}}:
</b>{{m.parlamentar.nome_parlamentar}} / {% if m.parlamentar.filiacao_atual %} {{ m.parlamentar.filiacao_atual }} {% else %} Sem partido {% endif %}
</b>{{m.parlamentar.nome_parlamentar}} / {% if m.parlamentar|filiacao_data_filter:object.data_inicio %} {{ m.parlamentar|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}
</div>
{% endfor %}
</div>

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

@ -1,3 +1,5 @@
{% load common_tags %}
<fieldset>
<legend>Oradores das Explicações Pessoais</legend>
<div class="row">
@ -6,7 +8,7 @@
</div>
<div class="row">
{% for o in oradores_explicacoes %}
<div class="col-md-6"><b>{{o.numero_ordem}}</b> - {{o.parlamentar.nome_parlamentar}} / {{ o.parlamentar.filiacao_atual }}</div>
<div class="col-md-6"><b>{{o.numero_ordem}}</b> - {{o.parlamentar.nome_parlamentar}} / {% if o.parlamentar|filiacao_data_filter:object.data_inicio %} {{ o.parlamentar|filiacao_data_filter:object.data_inicio }} {% else %} Sem partido {% endif %}</div>
<div class="col-md-6">{{o.url_discurso}}</div>
</br>
{% endfor %}

7
sapl/templates/sessao/layouts.yaml

@ -84,13 +84,6 @@ CargoBancada:
{% trans 'Cargo de Bancada' %}:
- nome_cargo:8 cargo_unico
Bloco:
{% trans 'Bloco' %}:
- nome
- data_criacao data_extincao
- partidos
- descricao
TipoJustificativa:
{% trans 'Tipo de Justificativa' %}:
- descricao

98
sapl/templates/sessao/painel.html

@ -116,11 +116,11 @@ $(function() {
}
startTime();
$('#discurso').prop('disabled', true);
$('#aparte').prop('disabled', true);
$('#ordem').prop('disabled', true);
$('#consideracoes').prop('disabled', true);
var audioAlertFinish = document.getElementById("audio");
$('#discurso').prop('disabled', false);
$('#aparte').prop('disabled', false);
$('#ordem').prop('disabled', false);
$('#consideracoes').prop('disabled', false);
$('#discurso').runner({
autostart: false,
@ -130,16 +130,10 @@ $(function() {
milliseconds: false
}).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'discurso', action: 'stop' } );
audioAlertFinish.play();
$('#discursoReset').show();
$('#discurso').runner('stop');
$('#discursoStart').text('Iniciar');
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
});
@ -152,12 +146,6 @@ $(function() {
$('#discursoReset').hide();
$('#discurso').runner('start');
$('#discursoStart').text('Parar');
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else {
@ -166,12 +154,6 @@ $(function() {
$('#discursoReset').show();
$('#discurso').runner('stop');
$('#discursoStart').text('Iniciar');
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}
});
@ -191,16 +173,10 @@ $(function() {
milliseconds: false
}).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } );
audioAlertFinish.play();
$('#aparteReset').show();
$('#aparte').runner('stop');
$('#aparteStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
});
@ -212,12 +188,7 @@ $(function() {
$('#aparteReset').hide();
$('#aparte').runner('start');
$('#aparteStart').text('Parar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else {
$.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } );
@ -225,12 +196,7 @@ $(function() {
$('#aparteReset').show();
$('#aparte').runner('stop');
$('#aparteStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}
});
@ -250,16 +216,11 @@ $(function() {
milliseconds: false
}).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'ordem', action: 'stop' } );
audioAlertFinish.play();
$('#ordemReset').show();
$('#ordem').runner('stop');
$('#ordemStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
});
$('#ordemStart').click(function() {
@ -270,12 +231,7 @@ $(function() {
$('#ordemReset').hide();
$('#ordem').runner('start');
$('#ordemStart').text('Parar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else {
@ -284,12 +240,7 @@ $(function() {
$('#ordemReset').show();
$('#ordem').runner('stop');
$('#ordemStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}
});
@ -309,16 +260,11 @@ $(function() {
milliseconds: false
}).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'consideracoes', action: 'stop' } );
audioAlertFinish.play();
$('#consideracoesReset').show();
$('#consideracoes').runner('stop');
$('#consideracoesStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
});
@ -330,12 +276,7 @@ $(function() {
$('#consideracoesReset').hide();
$('#consideracoes').runner('start');
$('#consideracoesStart').text('Parar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
} else {
$.get('/painel/cronometro', { tipo: 'consideracoes', action: 'stop' } );
@ -343,12 +284,7 @@ $(function() {
$('#consideracoesReset').show();
$('#consideracoes').runner('stop');
$('#consideracoesStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
}
});

160
sapl/templates/sessao/votacao/votacao_bloco_expediente.html

@ -3,94 +3,96 @@
{% block base_content %}
<form method="POST" enctype="application/x-www-form-urlencoded" id="form" action="{% url 'sapl.sessao:votacaoblocosimb' pk %}">
{% csrf_token %}
<br><br>
<table class="table table-striped table-bordered">
<thead class="thead-default">
{% if sessao_iniciada %}
<form method="POST" enctype="application/x-www-form-urlencoded" id="form" action="{% url 'sapl.sessao:votacaoblocosimb' pk %}">
{% csrf_token %}
<br><br>
<table class="table table-striped table-bordered">
<thead class="thead-default">
<tr>
<td><h3>{% trans "Tipo de Votação" %}</h3></td>
</tr>
</thead>
<tr>
<td><h3>{% trans "Tipo de Votação" %}</h3></td>
</tr>
</thead>
<tr>
<td class="col-md-12">
<fieldset id="tipo_votacao" name="tipo">
<input type="radio" name="tipo_votacao" id="tipo_votacao_1" value="1" onchange="alteraTipoVotacao()" checked="checked"> <label for="tipo">Simbólica</label>
</br>
<input type="radio" name="tipo_votacao" id="tipo_votacao_2" value="2" onchange="alteraTipoVotacao()" > <label for="tipo">Nominal</label>
</br>
</fieldset>
</td>
</tr>
</table>
<br>
<h3 id='frase_selecione'>Selecione o(s) expediente(s) desejado(s).</h3>
<table id='tab_ordens' class="table table-striped table-bordered">
<thead class="thead-default">
<tr>
<td><h3>{% trans "Expediente" %}</h3></td>
<td class="col-md-12">
<fieldset id="tipo_votacao" name="tipo">
<input type="radio" name="tipo_votacao" id="tipo_votacao_1" value="1" onchange="alteraTipoVotacao()" checked="checked"> <label for="tipo">Simbólica</label>
</br>
<input type="radio" name="tipo_votacao" id="tipo_votacao_2" value="2" onchange="alteraTipoVotacao()" > <label for="tipo">Nominal</label>
</br>
</fieldset>
</td>
</tr>
</thead>
</table>
<br>
<h3 id='frase_selecione'>Selecione o(s) expediente(s) desejado(s).</h3>
<table id='tab_ordens' class="table table-striped table-bordered">
<thead class="thead-default">
<tr>
<td><h3>{% trans "Expediente" %}</h3></td>
</tr>
</thead>
<div class="checkbox" id="check_all">
<label for="id_check_all">
<input type="checkbox" id="id_check_all" onchange="checkAll(this)" /> Marcar/Desmarcar Todos
</label>
</div>
<div class="checkbox" id="check_all">
<label for="id_check_all">
<input type="checkbox" id="id_check_all" onchange="checkAll(this)" /> Marcar/Desmarcar Todos
</label>
</div>
{% for o in expedientes %}
{% if o.tipo_votacao == 1 or o.tipo_votacao == 2 %}
<tr class="{% if o.tipo_votacao == 1 %}Simbolica{% else %}Nominal{% endif %}" {% if o.tipo_votacao == 2 %} style="display:none;" {% endif %}>
<td>
<input type="checkbox" name="marcadas_{{o.tipo_votacao}}" id="{{o.id}}" value="{{o.id}}" {% if check %} checked {% endif %}>
<strong><a href="{% url 'sapl.materia:materialegislativa_detail' o.id %}">{{o.materia.tipo.sigla}} {{o.materia.numero}}/{{o.materia.ano}} - {{o.materia.tipo}}</strong></a></br>
{% if o.materia.numeracao_set.last %}
<strong>Processo:</strong> &nbsp; {{o.materia.numeracao_set.last}}</br>
{% endif %}
<strong>Autor:</strong>
{% for a in o.materia.autoria_set.all %}
{% if not forloop.first %}
, &nbsp;&nbsp; {{a.autor|default_if_none:""}}
{% else %}
&nbsp;{{a.autor|default_if_none:""}}
{% for o in expedientes %}
{% if o.tipo_votacao == 1 or o.tipo_votacao == 2 %}
<tr class="{% if o.tipo_votacao == 1 %}Simbolica{% else %}Nominal{% endif %}" {% if o.tipo_votacao == 2 %} style="display:none;" {% endif %}>
<td>
<input type="checkbox" name="marcadas_{{o.tipo_votacao}}" id="{{o.id}}" value="{{o.id}}" {% if check %} checked {% endif %}>
<strong><a href="{% url 'sapl.materia:materialegislativa_detail' o.id %}">{{o.materia.tipo.sigla}} {{o.materia.numero}}/{{o.materia.ano}} - {{o.materia.tipo}}</strong></a></br>
{% if o.materia.numeracao_set.last %}
<strong>Processo:</strong> &nbsp; {{o.materia.numeracao_set.last}}</br>
{% endif %}
{% endfor %}
</br>
{% if o.materia.numero_protocolo %}
<strong>Protocolo:</strong> &nbsp; {{o.materia.numero_protocolo}}</br>
{% endif %}
{% if o.materia.tramitacao_set.last %}
{% if o.materia.tramitacao_set.last.turno %}
<strong>Turno:</strong>&nbsp;
{% for t in turno_choices %}
{% if t.0 == o.materia.tramitacao_set.last.turno %}
{{ t.1 }}
{% endif %}
{% endfor %}</br>
<strong>Autor:</strong>
{% for a in o.materia.autoria_set.all %}
{% if not forloop.first %}
, &nbsp;&nbsp; {{a.autor|default_if_none:""}}
{% else %}
&nbsp;{{a.autor|default_if_none:""}}
{% endif %}
{% endfor %}
</br>
{% if o.materia.numero_protocolo %}
<strong>Protocolo:</strong> &nbsp; {{o.materia.numero_protocolo}}</br>
{% endif %}
{% if o.materia.tramitacao_set.last %}
{% if o.materia.tramitacao_set.last.turno %}
<strong>Turno:</strong>&nbsp;
{% for t in turno_choices %}
{% if t.0 == o.materia.tramitacao_set.last.turno %}
{{ t.1 }}
{% endif %}
{% endfor %}</br>
{% endif %}
{% endif %}
{% endif %}
<strong>Ementa:</strong>&nbsp;{{ o.ementa|safe }}</br>
<p></p>
</td>
</tr>
{% endif %}
{% endfor %}
</table>
<strong>Ementa:</strong>&nbsp;{{ o.ementa|safe }}</br>
<p></p>
</td>
</tr>
{% endif %}
{% endfor %}
</table>
<table class="table table-striped table-bordered" style="display:none" id="nenhum_exp">
<tr>
<td>
<h3>Nenhuma matéria do expediente aberta.</h3>
</td>
</tr>
</table>
<table class="table table-striped table-bordered" style="display:none" id="nenhum_exp">
<tr>
<td>
<h3>Nenhuma matéria do expediente aberta.</h3>
</td>
</tr>
</table>
<a href="{% url 'sapl.sessao:expedientemateria_list' pk %}" class="btn btn-warning mb-3" id="but_cancel">Voltar</a>
<input type="submit" value="Registrar votação" class="btn btn-primary mb-3 float-right" id="but_reg">
<a href="{% url 'sapl.sessao:expedientemateria_list' pk %}" class="btn btn-warning mb-3" id="but_cancel">Voltar</a>
<input type="submit" value="Registrar votação" class="btn btn-primary mb-3 float-right" id="but_reg">
<input type="hidden" id="origem" name="origem" value="expediente">
</form>
<input type="hidden" id="origem" name="origem" value="expediente">
</form>
{% endif %}
{% endblock base_content %}

168
sapl/templates/sessao/votacao/votacao_bloco_ordem.html

@ -3,94 +3,96 @@
{% block base_content %}
<form method="POST" enctype="application/x-www-form-urlencoded" id="form" action="{% url 'sapl.sessao:votacaoblocosimb' pk %}">
{% csrf_token %}
<br><br>
<table class="table table-striped table-bordered">
{% if sessao_iniciada %}
<form method="POST" enctype="application/x-www-form-urlencoded" id="form" action="{% url 'sapl.sessao:votacaoblocosimb' pk %}">
{% csrf_token %}
<br><br>
<table class="table table-striped table-bordered">
<thead class="thead-default">
<tr>
<td><h3>{% trans "Tipo de Votação" %}</h3></td>
</tr>
</thead>
<tr>
<td class="col-md-12">
<fieldset id="tipo_votacao" name="tipo">
<input type="radio" name="tipo_votacao" id="tipo_votacao_1" value="1" onchange="alteraTipoVotacao()" checked="checked"> <label for="tipo">Simbólica</label>
</br>
<input type="radio" name="tipo_votacao" id="tipo_votacao_2" value="2" onchange="alteraTipoVotacao()" > <label for="tipo">Nominal</label>
</br>
</fieldset>
</td>
</tr>
</table>
<br>
<h3 id='frase_selecione'>Selecione a(s) ordem(s) do dia desejada(s).</h3>
<table id='tab_ordens' class="table table-striped table-bordered">
<thead class="thead-default">
<tr>
<td><h3>{% trans "Tipo de Votação" %}</h3></td>
</tr>
<tr>
<td><h3>{% trans "Ordem do dia" %}</h3></td>
</tr>
</thead>
<tr>
<td class="col-md-12">
<fieldset id="tipo_votacao" name="tipo">
<input type="radio" name="tipo_votacao" id="tipo_votacao_1" value="1" onchange="alteraTipoVotacao()" checked="checked"> <label for="tipo">Simbólica</label>
</br>
<input type="radio" name="tipo_votacao" id="tipo_votacao_2" value="2" onchange="alteraTipoVotacao()" > <label for="tipo">Nominal</label>
<div class="checkbox" id="check_all">
<label for="id_check_all">
<input type="checkbox" id="id_check_all" onchange="checkAll(this)" /> Marcar/Desmarcar Todos
</label>
</div>
{% for o in ordem_dia %}
{% if o.tipo_votacao == 1 or o.tipo_votacao == 2 %}
<tr class="{% if o.tipo_votacao == 1 %}Simbolica{% else %}Nominal{% endif %}" {% if o.tipo_votacao == 2 %} style="display:none;" {% endif %}>
<td>
<input type="checkbox" name="marcadas_{{o.tipo_votacao}}" id="{{o.id}}" value="{{o.id}}" {% if check %} checked {% endif %}>
<strong><a href="{% url 'sapl.materia:materialegislativa_detail' o.id %}">{{o.materia.tipo.sigla}} {{o.materia.numero}}/{{o.materia.ano}} - {{o.materia.tipo}}</strong></a></br>
{% if o.materia.numeracao_set.last %}
<strong>Processo:</strong> &nbsp; {{o.materia.numeracao_set.last}}</br>
{% endif %}
<strong>Autor:</strong>
{% for a in o.materia.autoria_set.all %}
{% if not forloop.first %}
, &nbsp;&nbsp; {{a.autor|default_if_none:""}}
{% else %}
&nbsp;{{a.autor|default_if_none:""}}
{% endif %}
{% endfor %}
</br>
</fieldset>
</td>
</tr>
</table>
<br>
<h3 id='frase_selecione'>Selecione a(s) ordem(s) do dia desejada(s).</h3>
<table id='tab_ordens' class="table table-striped table-bordered">
<thead class="thead-default">
<tr>
<td><h3>{% trans "Ordem do dia" %}</h3></td>
</tr>
</thead>
<div class="checkbox" id="check_all">
<label for="id_check_all">
<input type="checkbox" id="id_check_all" onchange="checkAll(this)" /> Marcar/Desmarcar Todos
</label>
</div>
{% for o in ordem_dia %}
{% if o.tipo_votacao == 1 or o.tipo_votacao == 2 %}
<tr class="{% if o.tipo_votacao == 1 %}Simbolica{% else %}Nominal{% endif %}" {% if o.tipo_votacao == 2 %} style="display:none;" {% endif %}>
<td>
<input type="checkbox" name="marcadas_{{o.tipo_votacao}}" id="{{o.id}}" value="{{o.id}}" {% if check %} checked {% endif %}>
<strong><a href="{% url 'sapl.materia:materialegislativa_detail' o.id %}">{{o.materia.tipo.sigla}} {{o.materia.numero}}/{{o.materia.ano}} - {{o.materia.tipo}}</strong></a></br>
{% if o.materia.numeracao_set.last %}
<strong>Processo:</strong> &nbsp; {{o.materia.numeracao_set.last}}</br>
{% endif %}
<strong>Autor:</strong>
{% for a in o.materia.autoria_set.all %}
{% if not forloop.first %}
, &nbsp;&nbsp; {{a.autor|default_if_none:""}}
{% else %}
&nbsp;{{a.autor|default_if_none:""}}
{% if o.materia.numero_protocolo %}
<strong>Protocolo:</strong> &nbsp; {{o.materia.numero_protocolo}}</br>
{% endif %}
{% endfor %}
</br>
{% if o.materia.numero_protocolo %}
<strong>Protocolo:</strong> &nbsp; {{o.materia.numero_protocolo}}</br>
{% endif %}
{% if o.materia.tramitacao_set.last %}
{% if o.materia.tramitacao_set.last.turno %}
<strong>Turno:</strong>&nbsp;
{% for t in turno_choices %}
{% if t.0 == o.materia.tramitacao_set.last.turno %}
{{ t.1 }}
{% endif %}
{% endfor %}</br>
{% if o.materia.tramitacao_set.last %}
{% if o.materia.tramitacao_set.last.turno %}
<strong>Turno:</strong>&nbsp;
{% for t in turno_choices %}
{% if t.0 == o.materia.tramitacao_set.last.turno %}
{{ t.1 }}
{% endif %}
{% endfor %}</br>
{% endif %}
{% endif %}
{% endif %}
<strong>Ementa:</strong>&nbsp;{{ o.ementa|safe }}</br>
<p></p>
</td>
</tr>
{% endif %}
{% endfor %}
</table>
<table class="table table-striped table-bordered" style="display:none" id="nenhuma_ordem">
<tr>
<td>
<h3>Nenhuma ordem do dia aberta.</h3>
</td>
</tr>
</table>
<a href="{% url 'sapl.sessao:ordemdia_list' pk %}" class="btn btn-warning mb-3" id="but_cancel">Voltar</a>
<input type="submit" value="Registrar votação" class="btn btn-primary mb-3 float-right" id="but_reg">
<input type="hidden" id="origem" name="origem" value="ordem">
</form>
<strong>Ementa:</strong>&nbsp;{{ o.ementa|safe }}</br>
<p></p>
</td>
</tr>
{% endif %}
{% endfor %}
</table>
<table class="table table-striped table-bordered" style="display:none" id="nenhuma_ordem">
<tr>
<td>
<h3>Nenhuma ordem do dia aberta.</h3>
</td>
</tr>
</table>
<a href="{% url 'sapl.sessao:ordemdia_list' pk %}" class="btn btn-warning mb-3" id="but_cancel">Voltar</a>
<input type="submit" value="Registrar votação" class="btn btn-primary mb-3 float-right" id="but_reg">
<input type="hidden" id="origem" name="origem" value="ordem">
</form>
{% endif %}
{% endblock base_content %}

2
sapl/templates/sistema.html

@ -53,7 +53,7 @@
<div class="col-md-6"><a href="{% url 'sapl.sessao:bancada_list' %}" class="btn btn-link">Bancadas Parlamentares</a></div>
<div class="col-md-6"><a href="{% url 'sapl.sessao:cargobancada_list' %}" class="btn btn-link">Cargo de Bancada Parlamentar</a></div>
<div class="col-md-6"><a href="{% url 'sapl.parlamentares:frente_list' %}" class="btn btn-link">Frente Parlamentar</a></div>
<div class="col-md-6"><a href="{% url 'sapl.sessao:bloco_list' %}" class="btn btn-link">Bloco Parlamentar</a></div>
<div class="col-md-6"><a href="{% url 'sapl.parlamentares:bloco_list' %}" class="btn btn-link">Bloco Parlamentar</a></div>
</div>
<hr />

28
sapl/utils.py

@ -5,7 +5,7 @@ import os
import re
from unicodedata import normalize as unicodedata_normalize
import unicodedata
import logging
from crispy_forms.layout import HTML, Button
from django import forms
from django.apps import apps
@ -940,15 +940,41 @@ def remover_acentos(string):
def mail_service_configured(request=None):
logger = logging.getLogger(__name__)
if settings.EMAIL_RUNNING is None:
result = True
try:
connection = get_connection()
connection.open()
except Exception as e:
logger.error(str(e))
result = False
finally:
connection.close()
settings.EMAIL_RUNNING = result
return settings.EMAIL_RUNNING
def lista_anexados(principal, isMateriaLegislativa=True):
anexados_total = []
if isMateriaLegislativa: #MateriaLegislativa
from sapl.materia.models import Anexada
anexados_iterator = Anexada.objects.filter(materia_principal=principal)
else: #DocAdm
from sapl.protocoloadm.models import Anexado
anexados_iterator = Anexado.objects.filter(documento_principal=principal)
while anexados_iterator:
anexados_tmp = []
for anx in anexados_iterator:
if isMateriaLegislativa:
anexados_total.append(anx.materia_anexada)
anexados_anexado = Anexada.objects.filter(materia_principal=anx.materia_anexada)
else:
anexados_total.append(anx.documento_anexado)
anexados_anexado = Anexado.objects.filter(documento_principal=anx.documento_anexado)
anexados_tmp.extend(anexados_anexado)
anexados_iterator = anexados_tmp
return anexados_total

27
scripts/check_commits.py

@ -0,0 +1,27 @@
import requests
branch_master = requests.get('https://api.github.com/repos/interlegis/sapl/commits?per_page=100&sha=master')
branch_master = branch_master.json()
commits_master = [e['commit']['message'] for e in branch_master]
branch_3_1_x = requests.get('https://api.github.com/repos/interlegis/sapl/commits?per_page=70&sha=3.1.x')
branch_3_1_x = branch_3_1_x.json()
commits_3_1_x = [(e['commit']['message'], {'sha':e['sha'], 'data':e['commit']['author']['date']}) for e in branch_3_1_x]
print("\nCommits que estão em 3.1.x, mas não em master:\n")
# retira os \r para evitar bugs
commits_master = [commit.replace('\r', '') for commit in commits_master]
for c, s in commits_3_1_x:
# retira os \r para evitar bugs
c = c.replace('\r', '')
if c not in commits_master:
print('---------------------------------------------------------------------')
print('Data: ' + s['data'][:10])
print(s['sha'][:7] + '-> ' + c)
print('---------------------------------------------------------------------')

105
scripts/deduplica_parlamentares.py

@ -0,0 +1,105 @@
from django.db.models import Count
from sapl.base.models import Autor
from sapl.comissoes.models import Participacao
from sapl.materia.models import Relatoria, UnidadeTramitacao, Autoria, Proposicao
from sapl.norma.models import AutoriaNorma
from sapl.parlamentares.models import Parlamentar, ComposicaoMesa, Dependente, Filiacao, Mandato, Frente, Votante
from sapl.protocoloadm.models import Protocolo, DocumentoAdministrativo
from sapl.sessao.models import IntegranteMesa, JustificativaAusencia, OradorExpediente, PresencaOrdemDia, \
RetiradaPauta, SessaoPlenariaPresenca, VotoParlamentar, OradorOrdemDia
models = [ComposicaoMesa, Dependente, Filiacao, IntegranteMesa, JustificativaAusencia, Mandato, OradorOrdemDia,
OradorExpediente, Participacao, PresencaOrdemDia, Relatoria, RetiradaPauta, SessaoPlenariaPresenca,
UnidadeTramitacao, VotoParlamentar, Votante]
# Tratar FRENTE pois ela é 1-to-many (campo parlamentares) com Parlamentar
models_autor = [AutoriaNorma, Autoria, Proposicao, Protocolo, DocumentoAdministrativo]
## Verificar se TipoAutor é sempre 1 para parlamentar e ContentType é sempre 26 para parlamentar.
TIPO_PARLAMENTAR = 1
CONTENT_TYPE_PARLAMENTAR = 26
def recupera_parlamentares():
return [[parlamentar for parlamentar in Parlamentar.objects.filter(nome_parlamentar=nome_parlamentar).order_by('id')]
for nome_parlamentar in Parlamentar.objects.values_list('nome_parlamentar', flat=True)
.annotate(qntd=Count('nome_parlamentar')).filter(qntd__gt=1)]
def deduplica_parlamentares(parlamentares):
for parlamentar in parlamentares:
parlamentar_principal = parlamentar[0]
print('Corrigindo parlamentar {}'.format(parlamentar_principal))
for clone in parlamentar[1:]:
if parlamentar_principal.biografia and clone.biografia:
parlamentar_principal.biografia += f'\n\n------------------------\n\n{clone.biografia}'
parlamentar_principal.save()
elif clone.biografia:
parlamentar_principal.biografia = clone.biografia
autor_principal = Autor.objects.filter(tipo_id=TIPO_PARLAMENTAR,
content_type_id=CONTENT_TYPE_PARLAMENTAR,
object_id=parlamentar_principal.id)
for a in Autor.objects.filter(tipo_id=TIPO_PARLAMENTAR, content_type_id=CONTENT_TYPE_PARLAMENTAR, object_id=clone.id):
print(a)
if not autor_principal:
print('Ajustando autor de %s' % parlamentar)
a.object_id = parlamentar_principal.id
a.save()
else:
print('Movendo referencias de autor')
for ma in models_autor:
for ra in ma.objects.filter(autor=a):
ra.autor = autor_principal[0]
ra.save()
a.delete()
# Muda apontamento de models que referenciam parlamentar
for model in models:
for obj in model.objects.filter(parlamentar_id=clone.id):
obj.parlamentar = parlamentar_principal
obj.save()
frentes = Frente.objects.filter(parlamentares=parlamentar_principal)
for frente in Frente.objects.select_related.filter(parlamentares=clone):
if frente not in frentes:
frente.parlamentares.add(parlamentar_principal)
frente.parlamentares.remove(clone)
clone.delete()
def estatisticas(parlamentares):
stats = []
for ps in parlamentares:
for p in ps:
d = {
'id': p.id,
'nome': p.nome_parlamentar,
'stats': {m.__name__: m.objects.filter(parlamentar=p).count() for m in models}
}
for m in models_autor:
d['stats'].update({m.__name__:m.objects.filter(autor__object_id=p.id,
autor__content_type=CONTENT_TYPE_PARLAMENTAR,
autor__tipo_id=TIPO_PARLAMENTAR).count()})
stats.append(d)
for s in stats:
print('---------------------------------------------------')
print(s['id'], s['nome'])
print(s['stats'])
def main():
parlamentares = recupera_parlamentares()
estatisticas(parlamentares)
deduplica_parlamentares(parlamentares)
estatisticas(parlamentares)
if __name__ == '__main__':
main()

84
scripts/remove_multiplos_autores.py

@ -0,0 +1,84 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count
from sapl.base.models import Autor
from sapl.parlamentares.models import Parlamentar
def pega_autores():
return [[autor for autor in Autor.objects.filter(nome=nome)]
for nome in Autor.objects.values_list('nome', flat=True).annotate(qntd=Count('nome')).filter(qntd__gt=1)]
def pega_parlamentares_autores():
parlamentares = [[parlamentar for parlamentar in Parlamentar.objects.filter(nome_parlamentar=nome_parlamentar)]
for nome_parlamentar in Parlamentar.objects.values_list('nome_parlamentar', flat=True)
.annotate(qntd=Count('nome_parlamentar')).filter(qntd__gt=1)]
parlamentares_autores = []
for parlamentar in parlamentares:
parlamentar_autor = []
for clone in parlamentar[1:]:
try:
autor_principal = Autor.objects.get(parlamentar_set=parlamentar[0])
except ObjectDoesNotExist:
try:
autor_clonado = Autor.objects.get(parlamentar_set=clone)
except ObjectDoesNotExist:
pass
else:
autor_clonado.object_id = parlamentar[0].id
autor_clonado.save()
parlamentares_autores.append(autor_clonado)
else:
if len(parlamentar_autor) == 0:
parlamentar_autor.append(autor_principal)
try:
autor_clonado = Autor.objects.get(parlamentar_set=clone)
except ObjectDoesNotExist:
pass
else:
parlamentar_autor.append(autor_clonado)
parlamentares_autores.extend(parlamentar_autor)
return parlamentares_autores
def transfere_valeres(autores):
for autor in autores:
for clone in autor[1:]:
for autoria in clone.autoria_set.all():
autoria.autor_id = autor[0]
autoria.save()
for proposicao in clone.proposicao_set.all():
proposicao.autor_id = autor[0]
proposicao.save()
for autorianorma in clone.autorianorma_set.all():
autorianorma.autor_id = autor[0]
autorianorma.save()
for documentoadministrativo in clone.documentoadministrativo_set.all():
documentoadministrativo.autor_id = autor[0]
documentoadministrativo.save()
for protocolo in clone.protocolo_set.all():
protocolo.autor_id = autor[0]
protocolo.save()
clone.delete()
def main():
autores = pega_autores()
parlamentares_autores = pega_parlamentares_autores()
autores.append(parlamentares_autores)
transfere_valeres(autores)
if __name__ == '__main__':
main()

13
scripts/remove_protocolos_inexistentes_materias.py

@ -0,0 +1,13 @@
from sapl.materia.models import MateriaLegislativa
from sapl.protocoloadm.models import Protocolo
def main():
for materia in MateriaLegislativa.objects.filter(numero_protocolo__isnull=False):
if not Protocolo.objects.filter(ano=materia.ano, numero=materia.numero_protocolo).exists():
materia.numero_protocolo = None
materia.save()
if __name__ == '__main__':
main()

2
setup.py

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

14
start.sh

@ -70,11 +70,19 @@ if [ "${USE_SOLR-False}" == "True" ] || [ "${USE_SOLR-False}" == "true" ]; then
echo "REPLICATION FACTOR: $RF"
echo "MAX SHARDS PER NODE: $MAX_SHARDS_PER_NODE"
echo "========================================="
echo "running solr script"
/bin/bash check_solr.sh $SOLR_URL
CHECK_SOLR_RETURN=$?
if [ $CHECK_SOLR_RETURN == 1 ]; then
echo "Connecting to solr..."
python3 solr_api.py -u $SOLR_URL -c $SOLR_COLLECTION -s $NUM_SHARDS -rf $RF -ms $MAX_SHARDS_PER_NODE &
# python3 manage.py rebuild_index --noinput &
else
echo "Solr is offline, not possible to connect."
fi
python3 solr_api.py -u $SOLR_URL -c $SOLR_COLLECTION -s $NUM_SHARDS -rf $RF -ms $MAX_SHARDS_PER_NODE &
# python3 manage.py rebuild_index --noinput &
else
echo "Suporte a SOLR não inicializado."
fi

Loading…
Cancel
Save