Browse Source

Merge pull request #5 from interlegis/master

atualização 21072017
pull/1374/head
Rogério Frá 8 years ago
committed by GitHub
parent
commit
cf83173367
  1. 11
      Dockerfile
  2. 1
      MANIFEST.in
  3. 2
      README.rst
  4. 24
      codeclimate.yml
  5. 7
      create_admin.py
  6. 2
      docker-compose.yml
  7. 12
      docs/deploy.rst
  8. 29
      docs/importacao_25_31.rst
  9. 39
      docs/instalacao31.rst
  10. 3
      docs/solr.rst
  11. 1
      genkey.py
  12. 2
      pytest.ini
  13. 58
      sapl/api/forms.py
  14. 125
      sapl/api/serializers.py
  15. 68
      sapl/api/views.py
  16. 13
      sapl/base/forms.py
  17. 20
      sapl/base/migrations/0003_auto_20170519_1106.py
  18. 24
      sapl/base/migrations/0004_auto_20170714_1838.py
  19. 6
      sapl/base/models.py
  20. 77
      sapl/base/search_indexes.py
  21. 29
      sapl/base/templatetags/common_tags.py
  22. 5
      sapl/base/urls.py
  23. 32
      sapl/comissoes/views.py
  24. 5
      sapl/compilacao/templatetags/compilacao_filters.py
  25. 25
      sapl/crispy_layout_mixin.py
  26. 9
      sapl/crud/base.py
  27. 2
      sapl/legacy/management/commands/migracao_25_31.py
  28. 2
      sapl/legacy/management/commands/migracao_documentos.py
  29. 62
      sapl/legacy/migracao_documentos.py
  30. 194
      sapl/legacy/migration.py
  31. 146
      sapl/legacy/scripts/street_sweeper.py
  32. 211
      sapl/materia/email_utils.py
  33. 115
      sapl/materia/forms.py
  34. 32
      sapl/materia/migrations/0005_auto_20170522_1051.py
  35. 32
      sapl/materia/migrations/0005_auto_20170522_1904.py
  36. 16
      sapl/materia/migrations/0006_merge.py
  37. 20
      sapl/materia/migrations/0007_auto_20170620_1252.py
  38. 21
      sapl/materia/migrations/0008_auto_20170622_1527.py
  39. 25
      sapl/materia/migrations/0009_auto_20170712_0951.py
  40. 53
      sapl/materia/models.py
  41. 19
      sapl/materia/receivers.py
  42. 8
      sapl/materia/signals.py
  43. 2
      sapl/materia/tests/test_email_templates.py
  44. 2
      sapl/materia/urls.py
  45. 528
      sapl/materia/views.py
  46. 10
      sapl/norma/forms.py
  47. 22
      sapl/norma/migrations/0004_auto_20170522_1051.py
  48. 27
      sapl/norma/migrations/0004_auto_20170522_1115.py
  49. 16
      sapl/norma/migrations/0005_merge.py
  50. 20
      sapl/norma/migrations/0006_normajuridica_data_ultima_atualizacao.py
  51. 12
      sapl/norma/models.py
  52. 5
      sapl/norma/signals.py
  53. 10
      sapl/painel/urls.py
  54. 233
      sapl/painel/views.py
  55. 15
      sapl/parlamentares/forms.py
  56. 1
      sapl/parlamentares/legacy.yaml
  57. 27
      sapl/parlamentares/migrations/0003_auto_20170707_1656.py
  58. 20
      sapl/parlamentares/migrations/0004_auto_20170711_1305.py
  59. 31
      sapl/parlamentares/models.py
  60. 2
      sapl/parlamentares/tests/test_parlamentares.py
  61. 29
      sapl/parlamentares/urls.py
  62. 281
      sapl/parlamentares/views.py
  63. 46
      sapl/protocoloadm/forms.py
  64. 19
      sapl/protocoloadm/urls.py
  65. 179
      sapl/protocoloadm/views.py
  66. 0
      sapl/redireciona_urls/__init__.py
  67. 8
      sapl/redireciona_urls/apps.py
  68. 13
      sapl/redireciona_urls/exceptions.py
  69. 710
      sapl/redireciona_urls/tests.py
  70. 80
      sapl/redireciona_urls/urls.py
  71. 577
      sapl/redireciona_urls/views.py
  72. 30
      sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py
  73. 51
      sapl/relatorios/views.py
  74. 3
      sapl/rules/map_rules.py
  75. 4
      sapl/rules/tests/test_rules.py
  76. 139
      sapl/sessao/forms.py
  77. 35
      sapl/sessao/migrations/0003_resumoordenacao.py
  78. 21
      sapl/sessao/migrations/0004_votonominal_registro_votacao.py
  79. 39
      sapl/sessao/migrations/0005_auto_20170601_1246.py
  80. 26
      sapl/sessao/migrations/0006_auto_20170601_1257.py
  81. 26
      sapl/sessao/migrations/0007_auto_20170606_1238.py
  82. 38
      sapl/sessao/migrations/0008_auto_20170607_1220.py
  83. 20
      sapl/sessao/migrations/0009_auto_20170619_1441.py
  84. 87
      sapl/sessao/models.py
  85. 1
      sapl/sessao/serializers.py
  86. 30
      sapl/sessao/urls.py
  87. 1106
      sapl/sessao/views.py
  88. 8
      sapl/settings.py
  89. BIN
      sapl/static/img/avatar.png
  90. BIN
      sapl/static/img/beta.png
  91. 8
      sapl/static/js/app.js
  92. 2
      sapl/static/styles/app.scss
  93. 2
      sapl/templates/ajuda/sessao_plenaria_materias_ordem_dia.html
  94. 26
      sapl/templates/base.html
  95. 2
      sapl/templates/base/RelatorioMateriasPorTramitacao_filter.html
  96. 2
      sapl/templates/compilacao/textoarticulado_detail.html
  97. 4
      sapl/templates/crud/detail.html
  98. 6
      sapl/templates/crud/list.html
  99. 6
      sapl/templates/email/acompanhar.html
  100. 9
      sapl/templates/email/tramitacao.html

11
Dockerfile

@ -2,7 +2,7 @@ FROM alpine:3.5
ENV BUILD_PACKAGES postgresql-dev graphviz-dev graphviz build-base git pkgconfig \ ENV BUILD_PACKAGES postgresql-dev graphviz-dev graphviz build-base git pkgconfig \
python3-dev libxml2-dev jpeg-dev libressl-dev libffi-dev libxslt-dev nodejs py3-lxml \ python3-dev libxml2-dev jpeg-dev libressl-dev libffi-dev libxslt-dev nodejs py3-lxml \
py3-magic postgresql-client vim py3-magic postgresql-client poppler-utils vim
RUN apk add --no-cache python3 nginx && \ RUN apk add --no-cache python3 nginx && \
python3 -m ensurepip && \ python3 -m ensurepip && \
@ -35,9 +35,12 @@ COPY config/env_dockerfile /var/interlegis/sapl/sapl/.env
# compilescss - Precompile all occurrences of your SASS/SCSS files for the whole project into css files # compilescss - Precompile all occurrences of your SASS/SCSS files for the whole project into css files
RUN python3 manage.py bower_install -- --allow-root --no-input && \ RUN python3 manage.py bower_install -- --allow-root --no-input && \
python3 manage.py compilescss && \ python3 manage.py compilescss
python3 manage.py collectstatic --no-input && \
rm -rf /var/interlegis/sapl/sapl/.env && \ RUN python3 manage.py collectstatic --noinput --clear
# Remove .env(fake) e sapl.db da imagem
RUN rm -rf /var/interlegis/sapl/sapl/.env && \
rm -rf /var/interlegis/sapl/sapl.db rm -rf /var/interlegis/sapl/sapl.db
RUN chmod +x /var/interlegis/sapl/start.sh && \ RUN chmod +x /var/interlegis/sapl/start.sh && \

1
MANIFEST.in

@ -2,5 +2,6 @@ include README.rst LICENSE.txt
recursive-include sapl *.html *.yaml recursive-include sapl *.html *.yaml
recursive-include sapl/static * recursive-include sapl/static *
recursive-include sapl/relatorios/templates *.py recursive-include sapl/relatorios/templates *.py
recursive-include sapl/compilacao *.sql
global-exclude __pycache__ global-exclude __pycache__
global-exclude *.py[co] global-exclude *.py[co]

2
README.rst

@ -17,7 +17,7 @@ atual do sistema (2.5), visite a página do `projeto na Interlegis wiki <https:/
Instalação do Ambiente de Desenvolvimento Instalação do Ambiente de Desenvolvimento
========================================= =========================================
`Instalação do Ambiente de Desenvolvimento <https://github.com/interlegis/sapl/blob/master/docs/instacao31.rst>`_ `Instalação do Ambiente de Desenvolvimento <https://github.com/interlegis/sapl/blob/master/docs/instalacao31.rst>`_
Instruções para Importação da base mysql 2.5 Instruções para Importação da base mysql 2.5

24
codeclimate.yml

@ -0,0 +1,24 @@
engines:
eslint:
enabled: false
csslint:
enabled: false
duplication:
enabled: true
config:
languages:
- python
- javascript
fixme:
enabled: true
radon:
enabled: true
ratings:
paths:
- "**.py"
- "**.js"
exclude_paths:
- sapl/**/migrations/*
- sapl/**/legacy/*
- sapl/relatorios/
- sapl/templates/*

7
create_admin.py

@ -1,13 +1,13 @@
import sys
import os import os
import sys
import django import django
from sapl import settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings")
django.setup()
def create_superuser():
from django.contrib.auth.models import User from django.contrib.auth.models import User
def create_superuser():
username = "admin" username = "admin"
password = os.environ['ADMIN_PASSWORD'] if 'ADMIN_PASSWORD' in os.environ else None password = os.environ['ADMIN_PASSWORD'] if 'ADMIN_PASSWORD' in os.environ else None
email = os.environ['ADMIN_EMAIL'] if 'ADMIN_EMAIL' in os.environ else '' email = os.environ['ADMIN_EMAIL'] if 'ADMIN_EMAIL' in os.environ else ''
@ -30,4 +30,5 @@ def create_superuser():
sys.exit(0) sys.exit(0)
if __name__ == '__main__': if __name__ == '__main__':
django.setup()
create_superuser() create_superuser()

2
docker-compose.yml

@ -10,7 +10,7 @@ sapldb:
ports: ports:
- "5532:5432" - "5532:5432"
sapl: sapl:
image: interlegis/sapl:3.1.2-BETA image: interlegis/sapl:3.1.12-BETA
volumes: volumes:
- sapl_data:/var/interlegis/sapl/data - sapl_data:/var/interlegis/sapl/data
- sapl_media:/var/interlegis/sapl/media - sapl_media:/var/interlegis/sapl/media

12
docs/deploy.rst

@ -17,16 +17,20 @@ alterando o variável DEBUG para false::
DEBUG = False DEBUG = False
Entrar no ambiente virtual::
workon sapl
Arquivos Estáticos Arquivos Estáticos
------------------ ------------------
Com o ambiente em produção, os arquivos estáticos devem ser servidos pelo web service, em nosso caso o `NGINX`, logo para ter acesso aos arquivos primeiro devemos rodar o seguinte comando:: Com o ambiente em produção, os arquivos estáticos devem ser servidos pelo web service, em nosso caso o `NGINX`, logo para ter acesso aos arquivos primeiro devemos rodar o seguinte comando::
python3 manage.py compilescss ./manage.py compilescss
para que os arquivos SASS/SCSS sejam compilados em arquivos .css em ambiente de produção, e em seguida rode:: para que os arquivos SASS/SCSS sejam compilados em arquivos .css em ambiente de produção, e em seguida rode::
pyhton3 manage.py collectstatic --no-input ./manage.py collectstatic --no-input
para coletar todos os arquivos estáticos do projeto e guarda-los no diretório definido em `STATIC_ROOT`, que será também o diretório no qual o `NGINX` irá referenciar para a aplicação. para coletar todos os arquivos estáticos do projeto e guarda-los no diretório definido em `STATIC_ROOT`, que será também o diretório no qual o `NGINX` irá referenciar para a aplicação.
@ -40,7 +44,7 @@ Instalar o NGINX::
Instalar o Gunicorn:: Instalar o Gunicorn::
sudo pip install gunicorn sudo pip3 install gunicorn
Preparando o NGINX Preparando o NGINX
@ -91,7 +95,7 @@ Criar link simbólico para ativar o site::
sudo ln -s /etc/nginx/sites-available/sapl31.conf /etc/nginx/sites-enabled/sapl3 sudo ln -s /etc/nginx/sites-available/sapl31.conf /etc/nginx/sites-enabled/sapl3
Reiniciar o nginx Reiniciar o nginx::
sudo service nginx restart sudo service nginx restart

29
docs/importacao_25_31.rst

@ -80,3 +80,32 @@ Vincular os documentos ao novo banco do sapl 3.1
%run sapl/legacy/migracao_documentos.py %run sapl/legacy/migracao_documentos.py
migrar_documentos() migrar_documentos()
Para indexar os arquivos para pesquisa textual
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. workon sapl
2. ./manage.py rebuild_index
Dependendo da quantidade de arquivos a serem indexados, pode ser listado o seguinte erro 'Too many open files'
Isto está ligado a quantidade máxima de aquivos que podem ser abertos ao mesmo tempo pelo sistema operacional
Para aumentar este limite::
sudo nano /etc/security/limits.conf
* soft nofile 9000
* hard nofile 65000
sudo nano /etc/pam.d/common-session
session required pam_limits.so
Após reiniciar, verificar se foram carregados os novos parâmetros com o comando::
ulimit -a
deve ser apresentado o seguinte::
open files (-n) 9000

39
docs/instacao31.rst → docs/instalacao31.rst

@ -28,7 +28,7 @@ Instalar as seguintes dependências do sistema::
pkg-config postgresql postgresql-contrib pgadmin3 python-psycopg2 \ pkg-config postgresql postgresql-contrib pgadmin3 python-psycopg2 \
software-properties-common build-essential libxml2-dev libjpeg-dev \ software-properties-common build-essential libxml2-dev libjpeg-dev \
libmysqlclient-dev libssl-dev libffi-dev libxslt1-dev python3-setuptools \ libmysqlclient-dev libssl-dev libffi-dev libxslt1-dev python3-setuptools \
python3-pip curl python3-pip curl poppler-utils default-jre
sudo -i sudo -i
curl -sL https://deb.nodesource.com/setup_5.x | bash - curl -sL https://deb.nodesource.com/setup_5.x | bash -
@ -146,12 +146,14 @@ Criação da `SECRET_KEY <https://docs.djangoproject.com/es/1.9/ref/settings/#st
DEFAULT_FROM_EMAIL = [Insira este parâmetro] DEFAULT_FROM_EMAIL = [Insira este parâmetro]
SERVER_EMAIL = [Insira este parâmetro] SERVER_EMAIL = [Insira este parâmetro]
SOLR_URL = '[Insira este parâmetro]'
* Uma configuração mínima para atender os procedimentos acima seria:: * Uma configuração mínima para atender os procedimentos acima seria::
DATABASE_URL = postgresql://sapl:sapl@localhost:5432/sapl DATABASE_URL = postgresql://sapl:sapl@localhost:5432/sapl
SECRET_KEY = 'cole aqui entre as aspas simples a chave gerada pelo comando abaixo' SECRET_KEY = 'cole aqui entre as aspas simples a chave gerada pelo comando abaixo'
DEBUG = True DEBUG = False
EMAIL_USE_TLS = True EMAIL_USE_TLS = True
EMAIL_PORT = 587 EMAIL_PORT = 587
EMAIL_HOST = EMAIL_HOST =
@ -162,7 +164,6 @@ Criação da `SECRET_KEY <https://docs.djangoproject.com/es/1.9/ref/settings/#st
Rodar o comando abaixo, um detalhe importante, esse comando só funciona com o django extensions, mas ele já está presente no arquivo requirements/requirements.txt desse projeto:: Rodar o comando abaixo, um detalhe importante, esse comando só funciona com o django extensions, mas ele já está presente no arquivo requirements/requirements.txt desse projeto::
python manage.py generate_secret_key python manage.py generate_secret_key
@ -177,6 +178,7 @@ Copie a chave que aparecerá, edite o arquivo .env e altere o valor do parâmetr
* Instalar as dependências do ``bower``:: * Instalar as dependências do ``bower``::
sudo chown -R sapl31:sapl31 /home/sapl31/
./manage.py bower install ./manage.py bower install
* Atualizar e/ou criar as tabelas da base de dados para refletir o modelo da versão clonada:: * Atualizar e/ou criar as tabelas da base de dados para refletir o modelo da versão clonada::
@ -187,8 +189,6 @@ Copie a chave que aparecerá, edite o arquivo .env e altere o valor do parâmetr
./manage.py collectstatic --noinput ./manage.py collectstatic --noinput
* Preparar o ambiente para indexação de arquivos::
./manage.py rebuild_index
* Subir o servidor do django:: * Subir o servidor do django::
@ -198,6 +198,35 @@ Copie a chave que aparecerá, edite o arquivo .env e altere o valor do parâmetr
http://localhost:8001/ http://localhost:8001/
================================
Instruções para instalar o Solr
================================
Solr é a ferramenta utilizada pelo SAPL 3.1 para indexar documentos para que possa ser feita
a Pesquisa Textual.
Adicione ao final do arquivo ``.env`` o seguinte atributo:
``SOLR_URL = 'http://127.0.0.1:8983/solr'``
Dentro do diretório principal siga os seguintes passos::
curl -LO https://archive.apache.org/dist/lucene/solr/4.10.2/solr-4.10.2.tgz
tar xvzf solr-4.10.2.tgz
cd solr-4.10.2
cd example
java -jar start.jar
./manage.py build_solr_schema --filename solr-4.10.2/example/solr/collection1/conf/schema.xml
Após isso, deve-se parar o servidor do Solr e restartar com ``java -jar start.jar``
este processo prende o prompt
**OBS: Toda vez que o código da pesquisa textual for modificado, os comandos de build_solr_schema e start.jar devem ser rodados, nessa mesma ordem.**
Instruções para criação do super usuário e de usuários de testes Instruções para criação do super usuário e de usuários de testes
=========================================================================== ===========================================================================

3
docs/solr.rst

@ -5,6 +5,9 @@ Instruções para instalar o Solr
Solr é a ferramenta utilizada pelo SAPL 3.1 para indexar documentos para que possa ser feita Solr é a ferramenta utilizada pelo SAPL 3.1 para indexar documentos para que possa ser feita
a Pesquisa Textual. a Pesquisa Textual.
Adicione ao arquivo ``.env`` o seguinte atributo:
``SOLR_URL = 'http://127.0.0.1:8983/solr'``
Dentro do diretório principal siga os seguintes passos:: Dentro do diretório principal siga os seguintes passos::

1
genkey.py

@ -1,5 +1,6 @@
import random import random
def generate_secret(): def generate_secret():
return ''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) return ''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])

2
pytest.ini

@ -2,6 +2,8 @@
DJANGO_SETTINGS_MODULE=sapl.settings DJANGO_SETTINGS_MODULE=sapl.settings
norecursedirs = legacy crud norecursedirs = legacy crud
python_files = tests.py test_*.py *_tests.py
# REUSING DATABASE BY DEFAULT (as a performance optimization) # REUSING DATABASE BY DEFAULT (as a performance optimization)
# http://pytest-django.readthedocs.org/en/latest/database.html#example-work-flow-with-reuse-db-and-create-db # http://pytest-django.readthedocs.org/en/latest/database.html#example-work-flow-with-reuse-db-and-create-db
# #

58
sapl/api/forms.py

@ -1,5 +1,8 @@
from django.db.models import Q from django.db.models import Q
from django.forms.fields import CharField, MultiValueField
from django.forms.widgets import MultiWidget, TextInput
from django_filters.filters import MethodFilter, ModelChoiceFilter from django_filters.filters import MethodFilter, ModelChoiceFilter
from rest_framework.compat import django_filters
from rest_framework.filters import FilterSet from rest_framework.filters import FilterSet
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
@ -35,10 +38,11 @@ class SaplGenericRelationSearchFilterSet(FilterSet):
item.related_query_name(), item.related_query_name(),
field[0]) field[0])
) )
if len(field) == 3 and field[2](qtext) is not None:
q_fs = q_fs | Q(**{'%s__%s%s' % ( q_fs = q_fs | Q(**{'%s__%s%s' % (
item.related_query_name(), item.related_query_name(),
field[0], field[0],
field[1]): qtext}) field[1]): qtext if len(field) == 2 else field[2](qtext)})
q = q & q_fs q = q & q_fs
@ -48,6 +52,37 @@ class SaplGenericRelationSearchFilterSet(FilterSet):
return queryset return queryset
class SearchForFieldWidget(MultiWidget):
def decompress(self, value):
if value is None:
return [None, None]
return value
def __init__(self, attrs=None):
widgets = (TextInput, TextInput)
MultiWidget.__init__(self, widgets, attrs)
class SearchForFieldField(MultiValueField):
widget = SearchForFieldWidget
def __init__(self, *args, **kwargs):
fields = (
CharField(),
CharField())
super(SearchForFieldField, self).__init__(fields, *args, **kwargs)
def compress(self, parameters):
if parameters:
return parameters
return None
class SearchForFieldFilter(django_filters.filters.MethodFilter):
field_class = SearchForFieldField
class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet): class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet):
q = MethodFilter() q = MethodFilter()
tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all()) tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all())
@ -60,4 +95,23 @@ class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet):
def filter_q(self, queryset, value): def filter_q(self, queryset, value):
return SaplGenericRelationSearchFilterSet.filter_q( return SaplGenericRelationSearchFilterSet.filter_q(
self, queryset, value).order_by('nome') self, queryset, value).distinct('nome').order_by('nome')
class AutorSearchForFieldFilterSet(AutorChoiceFilterSet):
q = SearchForFieldFilter()
class Meta(AutorChoiceFilterSet.Meta):
pass
def filter_q(self, queryset, value):
value[0] = value[0].split(',')
value[1] = value[1].split(',')
params = {}
for key, v in list(zip(value[0], value[1])):
if v in ['True', 'False']:
v = '1' if v == 'True' else '0'
params[key] = v
return queryset.filter(**params).distinct('nome').order_by('nome')

125
sapl/api/serializers.py

@ -1,8 +1,8 @@
from rest_framework import serializers from rest_framework import serializers
from sapl.base.models import Autor from sapl.base.models import Autor, CasaLegislativa
from sapl.materia.models import MateriaLegislativa from sapl.materia.models import MateriaLegislativa
from sapl.sessao.models import SessaoPlenaria from sapl.sessao.models import OrdemDia, SessaoPlenaria
class ChoiceSerializer(serializers.Serializer): class ChoiceSerializer(serializers.Serializer):
@ -46,6 +46,7 @@ class AutorSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Autor model = Autor
fields = '__all__'
class MateriaLegislativaSerializer(serializers.ModelSerializer): class MateriaLegislativaSerializer(serializers.ModelSerializer):
@ -57,21 +58,113 @@ class MateriaLegislativaSerializer(serializers.ModelSerializer):
class SessaoPlenariaSerializer(serializers.ModelSerializer): class SessaoPlenariaSerializer(serializers.ModelSerializer):
tipo = serializers.StringRelatedField(many=False) codReuniao = serializers.SerializerMethodField('get_pk_sessao')
sessao_legislativa = serializers.StringRelatedField(many=False) codReuniaoPrincipal = serializers.SerializerMethodField('get_pk_sessao')
legislatura = serializers.StringRelatedField(many=False) txtTituloReuniao = serializers.SerializerMethodField('get_name')
txtSiglaOrgao = serializers.SerializerMethodField('get_sigla_orgao')
txtApelido = serializers.SerializerMethodField('get_name')
txtNomeOrgao = serializers.SerializerMethodField('get_nome_orgao')
codEstadoReuniao = serializers.SerializerMethodField('get_estadoSessaoPlenaria')
txtTipoReuniao = serializers.SerializerMethodField('get_tipo_sessao')
txtObjeto = serializers.SerializerMethodField('get_assunto_sessao')
txtLocal = serializers.SerializerMethodField('get_endereco_orgao')
bolReuniaoConjunta = serializers.SerializerMethodField('get_reuniao_conjunta')
bolHabilitarEventoInterativo = serializers.SerializerMethodField('get_iterativo')
idYoutube = serializers.SerializerMethodField('get_url')
codEstadoTransmissaoYoutube = serializers.SerializerMethodField('get_estadoTransmissaoYoutube')
datReuniaoString = serializers.SerializerMethodField('get_date')
# Constantes SessaoPlenaria (de 1-9) (apenas 3 serão usados)
SESSAO_FINALIZADA = 4
SESSAO_EM_ANDAMENTO = 3
SESSAO_CONVOCADA = 2
# Constantes EstadoTranmissaoYoutube (de 0 a 2)
TRANSMISSAO_ENCERRADA = 2
TRANSMISSAO_EM_ANDAMENTO = 1
SEM_TRANSMISSAO = 0
class Meta: class Meta:
model = SessaoPlenaria model = SessaoPlenaria
fields = ('pk', fields = (
'tipo', 'codReuniao',
'sessao_legislativa', 'codReuniaoPrincipal',
'legislatura', 'txtTituloReuniao',
'data_inicio', 'txtSiglaOrgao',
'hora_inicio', 'txtApelido',
'hora_fim', 'txtNomeOrgao',
'url_video', 'codEstadoReuniao',
'iniciada', 'txtTipoReuniao',
'finalizada', 'txtObjeto',
'interativa' 'txtLocal',
'bolReuniaoConjunta',
'bolHabilitarEventoInterativo',
'idYoutube',
'codEstadoTransmissaoYoutube',
'datReuniaoString'
) )
def __init__(self, *args, **kwargs):
super(SessaoPlenariaSerializer, self).__init__(args, kwargs)
casa = CasaLegislativa.objects.first()
def get_pk_sessao(self, obj):
return obj.pk
def get_name(self, obj):
return obj.__str__()
def get_estadoSessaoPlenaria(self, obj):
if obj.finalizada:
return self.SESSAO_FINALIZADA
elif obj.iniciada:
return self.SESSAO_EM_ANDAMENTO
else:
return self.SESSAO_CONVOCADA
def get_tipo_sessao(self, obj):
return obj.tipo.__str__()
def get_url(self, obj):
return obj.url_video if obj.url_video else None
def get_iterativo(self, obj):
return obj.interativa if obj.interativa else False
def get_date(self, obj):
return "{} {}{}".format(
obj.data_inicio.strftime("%d/%m/%Y"),
obj.hora_inicio,
":00"
)
def get_estadoTransmissaoYoutube(self, obj):
if obj.url_video:
if obj.finalizada:
return self.TRANSMISSAO_ENCERRADA
else:
return self.TRANSMISSAO_EM_ANDAMENTO
else:
return self.SEM_TRANSMISSAO
def get_assunto_sessao(self, obj):
pauta_sessao = ''
ordem_dia = OrdemDia.objects.filter(sessao_plenaria=obj.pk)
pauta_sessao = ', '.join([i.materia.__str__() for i in ordem_dia])
return str(pauta_sessao)
def get_endereco_orgao(self, obj):
return self.casa().endereco
def get_reuniao_conjunta(self, obj):
return False
def get_sigla_orgao(self, obj):
return self.casa().sigla
def get_nome_orgao(self, obj):
return self.casa().nome
def casa(self):
casa = CasaLegislativa.objects.first()
return casa

68
sapl/api/views.py

@ -5,12 +5,11 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework.filters import DjangoFilterBackend from rest_framework.filters import DjangoFilterBackend
from rest_framework.generics import ListAPIView from rest_framework.generics import ListAPIView
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.permissions import (IsAuthenticated, from rest_framework.permissions import (AllowAny, IsAuthenticated,
IsAuthenticatedOrReadOnly, IsAuthenticatedOrReadOnly)
AllowAny)
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from sapl.api.forms import AutorChoiceFilterSet from sapl.api.forms import AutorChoiceFilterSet, AutorSearchForFieldFilterSet
from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer, from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer,
ChoiceSerializer, ChoiceSerializer,
MateriaLegislativaSerializer, MateriaLegislativaSerializer,
@ -79,7 +78,57 @@ class AutorListView(ListAPIView):
o django-filter é desativado e a busca é feita o django-filter é desativado e a busca é feita
no model do ContentType associado ao tipo. no model do ContentType associado ao tipo.
Outros campos - q_0 / q_1 - q_0 faz o código ignorar "q"...
q_0 -> campos lookup a serem filtrados em qualquer Model
que implemente SaplGenericRelation
q_1 -> o valor que será pesquisado no lookup de q_0
q_0 e q_1 podem ser separados por ","... isso dará a
possibilidade de filtrar mais de um campo.
http://localhost:8000
/api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=False
/api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=True
/api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=False
/api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=True
http://localhost:8000
/api/autor?tr=1
&q_0=parlamentar_set__nome_parlamentar__icontains,
parlamentar_set__ativo
&q_1=Carvalho,False
/api/autor?tr=1
&q_0=parlamentar_set__nome_parlamentar__icontains,
parlamentar_set__ativo
&q_1=Carvalho,True
/api/autor?tr=3
&q_0=parlamentar_set__nome_parlamentar__icontains,
parlamentar_set__ativo
&q_1=Carvalho,False
/api/autor?tr=3
&q_0=parlamentar_set__nome_parlamentar__icontains,
parlamentar_set__ativo
&q_1=Carvalho,True
não importa o campo que vc passe de qualquer dos Models
ligados... é possível ver que models são esses,
na ocasião do commit deste texto, executando:
In [6]: from sapl.utils import models_with_gr_for_model
In [7]: models_with_gr_for_model(Autor)
Out[7]:
[sapl.parlamentares.models.Parlamentar,
sapl.parlamentares.models.Frente,
sapl.comissoes.models.Comissao,
sapl.materia.models.Orgao,
sapl.sessao.models.Bancada,
sapl.sessao.models.Bloco]
qualquer atributo destes models podem ser passados
para busca
""" """
TR_AUTOR_CHOICE_SERIALIZER = 1 TR_AUTOR_CHOICE_SERIALIZER = 1
@ -125,6 +174,9 @@ class AutorListView(ListAPIView):
self.serializer_class = AutorSerializer self.serializer_class = AutorSerializer
self.permission_classes = (IsAuthenticated,) self.permission_classes = (IsAuthenticated,)
if self.filter_class and 'q_0' in request.GET:
self.filter_class = AutorSearchForFieldFilterSet
return ListAPIView.get(self, request, *args, **kwargs) return ListAPIView.get(self, request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
@ -186,9 +238,12 @@ class AutorListView(ListAPIView):
q_filter = q_filter & q_fs q_filter = q_filter & q_fs
qs = qs.filter(q_filter).distinct( qs = qs.filter(q_filter).distinct(
fields[0].fields_search[0][0]).order_by(
fields[0].fields_search[0][0]) fields[0].fields_search[0][0])
else:
qs = qs.order_by(fields[0].fields_search[0][0])
qs = qs.order_by(fields[0].fields_search[0][0]).values_list( qs = qs.values_list(
'id', fields[0].fields_search[0][0]) 'id', fields[0].fields_search[0][0])
r += list(qs) r += list(qs)
@ -207,6 +262,7 @@ class MateriaLegislativaViewSet(ListModelMixin,
filter_backends = (DjangoFilterBackend,) filter_backends = (DjangoFilterBackend,)
filter_fields = ('numero', 'ano', 'tipo', ) filter_fields = ('numero', 'ano', 'tipo', )
class SessaoPlenariaViewSet(ListModelMixin, class SessaoPlenariaViewSet(ListModelMixin,
RetrieveModelMixin, RetrieveModelMixin,
GenericViewSet): GenericViewSet):

13
sapl/base/forms.py

@ -227,6 +227,8 @@ class AutorForm(ModelForm):
return True return True
def clean(self): def clean(self):
super(AutorForm, self).clean()
User = get_user_model() User = get_user_model()
cd = self.cleaned_data cd = self.cleaned_data
@ -505,6 +507,11 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
'widget': RangeWidgetOverride} 'widget': RangeWidgetOverride}
}} }}
@property
def qs(self):
parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs
return parent.distinct().order_by('-ano', 'tipo', 'numero')
class Meta: class Meta:
model = MateriaLegislativa model = MateriaLegislativa
fields = ['tipo', 'tramitacao__unidade_tramitacao_local', fields = ['tipo', 'tramitacao__unidade_tramitacao_local',
@ -534,7 +541,7 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet): class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True, ano = django_filters.ChoiceFilter(required=True,
label=u'Ano da Matéria', label='Ano da Matéria',
choices=RANGE_ANOS) choices=RANGE_ANOS)
class Meta: class Meta:
@ -565,7 +572,7 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet): class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True, ano = django_filters.ChoiceFilter(required=True,
label=u'Ano da Matéria', label='Ano da Matéria',
choices=RANGE_ANOS) choices=RANGE_ANOS)
class Meta: class Meta:
@ -720,6 +727,8 @@ class RecuperarSenhaForm(PasswordResetForm):
super(RecuperarSenhaForm, self).__init__(*args, **kwargs) super(RecuperarSenhaForm, self).__init__(*args, **kwargs)
def clean(self): def clean(self):
super(RecuperarSenhaForm, self).clean()
email_existente = User.objects.filter( email_existente = User.objects.filter(
email=self.data['email']).exists() email=self.data['email']).exists()

20
sapl/base/migrations/0003_auto_20170519_1106.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.12 on 2017-05-19 11:06
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0002_auto_20170331_1900'),
]
operations = [
migrations.AlterField(
model_name='autor',
name='nome',
field=models.CharField(blank=True, max_length=60, verbose_name='Nome do Autor'),
),
]

24
sapl/base/migrations/0004_auto_20170714_1838.py

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.12 on 2017-07-14 18:38
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0003_auto_20170519_1106'),
]
operations = [
migrations.RemoveField(
model_name='problemamigracao',
name='eh_importante',
),
migrations.AddField(
model_name='problemamigracao',
name='critico',
field=models.BooleanField(default=False, verbose_name='Crítico'),
),
]

6
sapl/base/models.py

@ -65,8 +65,8 @@ class ProblemaMigracao(models.Model):
problema = models.CharField(max_length=300, verbose_name=_('Problema')) problema = models.CharField(max_length=300, verbose_name=_('Problema'))
descricao = models.CharField(max_length=300, verbose_name=_('Descrição')) descricao = models.CharField(max_length=300, verbose_name=_('Descrição'))
eh_stub = models.BooleanField(verbose_name=_('É stub?')) eh_stub = models.BooleanField(verbose_name=_('É stub?'))
eh_importante = models.BooleanField( critico = models.BooleanField(
default=False, verbose_name=_('É importante?')) default=False, verbose_name=_('Crítico'))
class Meta: class Meta:
verbose_name = _('Problema na Migração') verbose_name = _('Problema na Migração')
@ -211,7 +211,7 @@ class Autor(models.Model):
autor_related = GenericForeignKey('content_type', 'object_id') autor_related = GenericForeignKey('content_type', 'object_id')
nome = models.CharField( nome = models.CharField(
max_length=50, blank=True, verbose_name=_('Nome do Autor')) max_length=60, blank=True, verbose_name=_('Nome do Autor'))
cargo = models.CharField(max_length=50, blank=True) cargo = models.CharField(max_length=50, blank=True)

77
sapl/base/search_indexes.py

@ -1,11 +1,18 @@
import logging
import os.path import os.path
import re
import string
import textract import textract
from django.template import Context, loader from django.template import loader
from haystack import indexes from haystack import indexes
from textract.exceptions import ExtensionNotSupported
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa
from sapl.norma.models import NormaJuridica from sapl.norma.models import NormaJuridica
from sapl.settings import BASE_DIR, SOLR_URL
logger = logging.getLogger(BASE_DIR.name)
class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable): class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable):
@ -21,6 +28,37 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable):
def index_queryset(self, using=None): def index_queryset(self, using=None):
return self.get_model().objects.all() return self.get_model().objects.all()
def get_updated_field(self):
return 'data_ultima_atualizacao'
def solr_extraction(self, arquivo):
extracted_data = self._get_backend(None).extract_file_contents(
arquivo)['contents']
# Remove as tags xml
extracted_data = re.sub('<[^>]*>', '', extracted_data)
# Remove tags \t e \n
extracted_data = extracted_data.replace(
'\n', ' ').replace('\t', ' ')
# Remove sinais de pontuação
extracted_data = re.sub('[' + string.punctuation + ']',
' ', extracted_data)
# Remove espaços múltiplos
extracted_data = " ".join(extracted_data.split())
return extracted_data
def whoosh_extraction(self, arquivo):
return textract.process(
arquivo.path,
language='pt-br').decode('utf-8').replace('\n', ' ').replace(
'\t', ' ')
def print_error(self, arquivo):
msg = 'Erro inesperado processando arquivo: %s' % (
arquivo.path)
print(msg)
logger.error(msg)
def prepare(self, obj): def prepare(self, obj):
if not self.filename or not self.model or not self.template_name: if not self.filename or not self.model or not self.template_name:
raise Exception raise Exception
@ -30,26 +68,39 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable):
arquivo = getattr(obj, self.filename) arquivo = getattr(obj, self.filename)
if arquivo: if arquivo:
try: if not os.path.exists(arquivo.path):
arquivo.open()
except OSError:
return self.prepared_data return self.prepared_data
if not os.path.splitext(arquivo.path)[1][:1]: if not os.path.splitext(arquivo.path)[1][:1]:
return self.prepared_data return self.prepared_data
extracted_data = textract.process( # Em ambiente de produção utiliza-se o SOLR
arquivo.path).decode( if SOLR_URL:
'utf-8').replace('\n', ' ') try:
extracted_data = self.solr_extraction(arquivo)
except Exception:
self.print_error(arquivo)
return self.prepared_data
extracted_data = extracted_data.replace('\t', ' ') # Em ambiente de DEV utiliza-se o Whoosh
# Como ele não possui extração, faz-se uso do textract
else:
try:
extracted_data = self.whoosh_extraction(arquivo)
except ExtensionNotSupported as e:
print(str(e))
logger.error(str(e))
return self.prepared_data
except Exception:
self.print_error(arquivo)
return self.prepared_data
# Now we'll finally perform the template processing to render the # Now we'll finally perform the template processing to render the
# text field with *all* of our metadata visible for templating: # text field with *all* of our metadata visible for templating:
t = loader.select_template(( t = loader.select_template((
'search/indexes/' + self.template_name, )) 'search/indexes/' + self.template_name, ))
data['text'] = t.render(Context({'object': obj, data['text'] = t.render({'object': obj,
'extracted': extracted_data})) 'extracted': extracted_data})
return data return data
@ -63,6 +114,9 @@ class MateriaLegislativaIndex(DocumentoAcessorioIndex):
model = MateriaLegislativa model = MateriaLegislativa
template_name = 'materia/materialegislativa_text.txt' template_name = 'materia/materialegislativa_text.txt'
def get_updated_field(self):
return 'data_ultima_atualizacao'
class NormaJuridicaIndex(DocumentoAcessorioIndex): class NormaJuridicaIndex(DocumentoAcessorioIndex):
text = indexes.CharField(document=True, use_template=True) text = indexes.CharField(document=True, use_template=True)
@ -70,3 +124,6 @@ class NormaJuridicaIndex(DocumentoAcessorioIndex):
filename = 'texto_integral' filename = 'texto_integral'
model = NormaJuridica model = NormaJuridica
template_name = 'norma/normajuridica_text.txt' template_name = 'norma/normajuridica_text.txt'
def get_updated_field(self):
return 'data_ultima_atualizacao'

29
sapl/base/templatetags/common_tags.py

@ -117,6 +117,23 @@ def str2intabs(value):
except: except:
return '' return ''
@register.filter
def has_iframe(request):
iframe = request.session.get('iframe', False)
if not iframe and 'iframe' in request.GET:
ival = request.GET['iframe']
if ival and int(ival) == 1:
request.session['iframe'] = True
return True
elif 'iframe' in request.GET:
ival = request.GET['iframe']
if ival and int(ival) == 0:
del request.session['iframe']
return False
return iframe
@register.filter @register.filter
def url(value): def url(value):
@ -151,3 +168,15 @@ def search_get_model(object):
return 'n' return 'n'
return None return None
@register.filter
def urldetail_content_type(obj, value):
return '%s:%s_detail' % (
value._meta.app_config.name, obj.content_type.model)
@register.filter
def urldetail(obj):
return '%s:%s_detail' % (
obj._meta.app_config.name, obj._meta.model_name)

5
sapl/base/urls.py

@ -66,7 +66,7 @@ urlpatterns = [
# TODO mover estas telas para a app 'relatorios' # TODO mover estas telas para a app 'relatorios'
url(r'^sistema/relatorios/$', TemplateView.as_view( url(r'^sistema/relatorios/$', TemplateView.as_view(
template_name='base/relatorios_list.html')), template_name='base/relatorios_list.html'), name='relatorios_list'),
url(r'^sistema/relatorios/materia-por-autor$', url(r'^sistema/relatorios/materia-por-autor$',
RelatorioMateriasPorAutorView.as_view(), name='materia_por_autor'), RelatorioMateriasPorAutorView.as_view(), name='materia_por_autor'),
url(r'^sistema/relatorios/materia-por-ano-autor-tipo$', url(r'^sistema/relatorios/materia-por-ano-autor-tipo$',
@ -92,7 +92,8 @@ urlpatterns = [
# todos os sublinks de sistema devem vir acima deste # todos os sublinks de sistema devem vir acima deste
url(r'^sistema/$', permission_required('base.view_tabelas_auxiliares') url(r'^sistema/$', permission_required('base.view_tabelas_auxiliares')
(TemplateView.as_view(template_name='sistema.html'))), (TemplateView.as_view(template_name='sistema.html')),
name='sistema'),
url(r'^login/$', views.login, { url(r'^login/$', views.login, {
'template_name': 'base/login.html', 'authentication_form': LoginForm}, 'template_name': 'base/login.html', 'authentication_form': LoginForm},

32
sapl/comissoes/views.py

@ -1,6 +1,7 @@
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import F from django.db.models import F
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import ListView from django.views.generic import ListView
from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud
@ -54,9 +55,21 @@ class ComposicaoCrud(MasterDetailCrud):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['composicao_pk'] = context['composicao_list'].last( # context['composicao_pk'] = context['composicao_list'].last(
).pk if self.take_composicao_pk( # ).pk if self.take_composicao_pk(
) == 0 else self.take_composicao_pk() # ) == 0 else self.take_composicao_pk()
composicao_pk = self.take_composicao_pk()
if composicao_pk == 0:
ultima_composicao = context['composicao_list'].last()
if ultima_composicao:
context['composicao_pk'] = ultima_composicao.pk
else:
context['composicao_pk'] = 0
else:
context['composicao_pk'] = composicao_pk
context['participacao_set'] = Participacao.objects.filter( context['participacao_set'] = Participacao.objects.filter(
composicao__pk=context['composicao_pk'] composicao__pk=context['composicao_pk']
).order_by('parlamentar') ).order_by('parlamentar')
@ -69,9 +82,20 @@ class ComissaoCrud(Crud):
public = [RP_LIST, RP_DETAIL, ] public = [RP_LIST, RP_DETAIL, ]
class BaseMixin(Crud.BaseMixin): class BaseMixin(Crud.BaseMixin):
list_field_names = ['nome', 'sigla', 'tipo', 'data_criacao', 'ativa'] list_field_names = ['nome', 'sigla', 'tipo', 'data_criacao', 'data_extincao', 'ativa']
ordering = '-ativa', 'sigla' ordering = '-ativa', 'sigla'
class ListView(Crud.ListView):
@xframe_options_exempt
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
class DetailView(Crud.DetailView):
@xframe_options_exempt
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
class MateriasTramitacaoListView(ListView): class MateriasTramitacaoListView(ListView):
template_name = "comissoes/materias_em_tramitacao.html" template_name = "comissoes/materias_em_tramitacao.html"

5
sapl/compilacao/templatetags/compilacao_filters.py

@ -286,11 +286,6 @@ def nomenclatura_heranca(d, ignore_ultimo=0, ignore_primeiro=0):
return result return result
@register.filter
def urldetail_content_type(obj):
return '%s:%s_detail' % (
obj.content_object._meta.app_config.name, obj.content_type.model)
@register.filter @register.filter
def list(obj): def list(obj):

25
sapl/crispy_layout_mixin.py

@ -1,12 +1,13 @@
from math import ceil from math import ceil
import rtyaml
from crispy_forms.bootstrap import FormActions from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Div, Fieldset, Layout, Submit from crispy_forms.layout import HTML, Div, Fieldset, Layout, Submit
from django import template from django import template
from django.core.urlresolvers import reverse
from django.utils import formats from django.utils import formats
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
import rtyaml
def heads_and_tails(list_of_lists): def heads_and_tails(list_of_lists):
@ -77,29 +78,30 @@ def get_field_display(obj, fieldname):
else: else:
value = getattr(obj, fieldname) value = getattr(obj, fieldname)
str_type = str(type(value)) str_type_from_value = str(type(value))
str_type_from_field = str(type(field))
if value is None: if value is None:
display = '' display = ''
elif 'date' in str_type: elif 'date' in str_type_from_value:
display = formats.date_format(value, "SHORT_DATE_FORMAT") display = formats.date_format(value, "SHORT_DATE_FORMAT")
elif 'bool' in str(type(value)): elif 'bool' in str_type_from_value:
display = _('Sim') if value else _('Não') display = _('Sim') if value else _('Não')
elif 'ImageFieldFile' in str(type(value)): elif 'ImageFieldFile' in str(type(value)):
if value: if value:
display = '<img src="{}" />'.format(value.url) display = '<img src="{}" />'.format(value.url)
else: else:
display = '' display = ''
elif 'FieldFile' in str(type(value)): elif 'FieldFile' in str_type_from_value:
if value: if value:
display = '<a href="{}">{}</a>'.format( display = '<a href="{}">{}</a>'.format(
value.url, value.url,
value.name.split('/')[-1:][0]) value.name.split('/')[-1:][0])
else: else:
display = '' display = ''
elif 'ManyRelatedManager' in str(type(value))\ elif 'ManyRelatedManager' in str_type_from_value\
or 'RelatedManager' in str(type(value))\ or 'RelatedManager' in str_type_from_value\
or 'GenericRelatedObjectManager' in str(type(value)): or 'GenericRelatedObjectManager' in str_type_from_value:
display = '<ul>' display = '<ul>'
for v in value.all(): for v in value.all():
display += '<li>%s</li>' % str(v) display += '<li>%s</li>' % str(v)
@ -110,6 +112,13 @@ def get_field_display(obj, fieldname):
field.related_model._meta.verbose_name_plural) field.related_model._meta.verbose_name_plural)
elif hasattr(field, 'model'): elif hasattr(field, 'model'):
verbose_name = str(field.model._meta.verbose_name_plural) verbose_name = str(field.model._meta.verbose_name_plural)
elif 'GenericForeignKey' in str_type_from_field:
display = '<a href="{}">{}</a>'.format(
reverse(
'%s:%s_detail' % (
value._meta.app_config.name, obj.content_type.model),
args=(value.id,)),
value)
else: else:
display = str(value) display = str(value)
return verbose_name, display return verbose_name, display

9
sapl/crud/base.py

@ -9,6 +9,7 @@ from django import forms
from django.conf.urls import url from django.conf.urls import url
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.db.models.fields.related import ForeignKey from django.db.models.fields.related import ForeignKey
@ -1133,7 +1134,11 @@ class MasterDetailCrud(Crud):
parent_field = obj.parent_field.split('__') parent_field = obj.parent_field.split('__')
if not obj.is_m2m or len(parent_field) > 1: if not obj.is_m2m or len(parent_field) > 1:
field = self.model._meta.get_field(parent_field[0]) field = self.model._meta.get_field(parent_field[0])
parent = field.related_model.objects.get(pk=self.kwargs['pk']) try:
parent = field.related_model.objects.get(
pk=self.kwargs['pk'])
except ObjectDoesNotExist:
raise Http404()
setattr(form.instance, parent_field[0], parent) setattr(form.instance, parent_field[0], parent)
return form return form
@ -1155,7 +1160,7 @@ class MasterDetailCrud(Crud):
try: try:
parent_object = parent_model.objects.get(**params) parent_object = parent_model.objects.get(**params)
except: except Exception:
raise Http404() raise Http404()
else: else:
parent_model = self.model parent_model = self.model

2
sapl/legacy/management/commands/migracao_25_31.py

@ -5,7 +5,7 @@ from sapl.legacy import migration
class Command(BaseCommand): class Command(BaseCommand):
help = u'Migração de dados do SAPL 2.5 para o SAPL 3.1' help ='Migração de dados do SAPL 2.5 para o SAPL 3.1'
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument( parser.add_argument(

2
sapl/legacy/management/commands/migracao_documentos.py

@ -5,7 +5,7 @@ from sapl.legacy.migracao_documentos import migrar_documentos
class Command(BaseCommand): class Command(BaseCommand):
help = u'Migração documentos do SAPL 2.5 para o SAPL 3.1' help ='Migração documentos do SAPL 2.5 para o SAPL 3.1'
def handle(self, *args, **options): def handle(self, *args, **options):
migrar_documentos() migrar_documentos()

62
sapl/legacy/migracao_documentos.py

@ -3,19 +3,19 @@ import os
import re import re
import magic import magic
from django.db.models.signals import post_delete, post_save from django.db.models.signals import post_delete, post_save
from sapl.base.models import CasaLegislativa from sapl.base.models import CasaLegislativa
from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa, from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa,
Proposicao) Proposicao)
from sapl.norma.models import NormaJuridica from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Parlamentar from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import DocumentoAdministrativo from sapl.protocoloadm.models import (DocumentoAcessorioAdministrativo,
DocumentoAdministrativo)
from sapl.sessao.models import SessaoPlenaria from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MEDIA_ROOT from sapl.settings import MEDIA_ROOT
from sapl.utils import delete_texto, save_texto from sapl.utils import delete_texto, save_texto
# MIGRAÇÃO DE DOCUMENTOS ################################################### # MIGRAÇÃO DE DOCUMENTOS ###################################################
EXTENSOES = { EXTENSOES = {
'application/msword': '.doc', 'application/msword': '.doc',
@ -29,6 +29,17 @@ EXTENSOES = {
'text/html': '.html', 'text/html': '.html',
'text/rtf': '.rtf', 'text/rtf': '.rtf',
'text/x-python': '.py', 'text/x-python': '.py',
'text/plain': '.ksh',
'text/plain': '.c',
'text/plain': '.h',
'text/plain': '.txt',
'text/plain': '.bat',
'text/plain': '.pl',
'text/plain': '.asc',
'text/plain': '.text',
'text/plain': '.pot',
'text/plain': '.brf',
'text/plain': '.srt',
# sem extensao # sem extensao
'application/octet-stream': '', # binário 'application/octet-stream': '', # binário
@ -43,35 +54,41 @@ DOCS = {
Parlamentar: [( Parlamentar: [(
'fotografia', 'fotografia',
'parlamentar/fotos/{}_foto_parlamentar', 'parlamentar/fotos/{}_foto_parlamentar',
'parlamentar/{0}/{0}_foto_parlamentar{1}')], 'public/parlamentar/{0}/{0}_foto_parlamentar{1}')],
MateriaLegislativa: [( MateriaLegislativa: [(
'texto_original', 'texto_original',
'materia/{}_texto_integral', 'materia/{}_texto_integral',
'materialegislativa/{0}/{0}_texto_integral{1}')], 'public/materialegislativa/{2}/{0}/{0}_texto_integral{1}')],
DocumentoAcessorio: [( DocumentoAcessorio: [(
'arquivo', 'arquivo',
'materia/{}', 'materia/{}',
'documentoacessorio/{0}/{0}{1}')], 'public/documentoacessorio/{2}/{0}/{0}{1}')],
NormaJuridica: [( NormaJuridica: [(
'texto_original', 'texto_integral',
'norma_juridica/{}_texto_integral', 'norma_juridica/{}_texto_integral',
'normajuridica/{0}/{0}_texto_integral{1}')], 'public/normajuridica/{2}/{0}/{0}_texto_integral{1}')],
SessaoPlenaria: [ SessaoPlenaria: [
('upload_ata', ('upload_ata',
'ata_sessao/{}_ata_sessao', 'ata_sessao/{}_ata_sessao',
'sessaoplenaria/{0}/ata/{0}_ata_sessao{1}'), 'public/sessaoplenaria/{0}/ata/{0}_ata_sessao{1}'),
('upload_anexo', ('upload_anexo',
'anexo_sessao/{}_texto_anexado', 'anexo_sessao/{}_texto_anexado',
'sessaoplenaria/{0}/anexo/{0}_texto_anexado{1}') 'public/sessaoplenaria/{0}/anexo/{0}_texto_anexado{1}')
], ],
Proposicao: [( Proposicao: [(
'texto_original', 'texto_original',
'proposicao/{}', 'proposicao/{}',
'proposicao/{0}/{0}{1}')], 'private/proposicao/{0}/{0}{1}')],
DocumentoAdministrativo: [( DocumentoAdministrativo: [(
'texto_integral', 'texto_integral',
'administrativo/{}_texto_integral', 'administrativo/{}_texto_integral',
'documentoadministrativo/{0}/{0}_texto_integral{1}')], 'private/documentoadministrativo/{0}/{0}_texto_integral{1}')
],
DocumentoAcessorioAdministrativo: [(
'arquivo',
'administrativo/{}',
'private/documentoacessorioadministrativo/{0}/{0}_acessorio_administrativo{1}')
],
} }
DOCS = {tipo: [(campo, DOCS = {tipo: [(campo,
@ -105,9 +122,14 @@ def migrar_docs_logo():
print('#### Migrando logotipo da casa ####') print('#### Migrando logotipo da casa ####')
[(_, origem, destino)] = DOCS[CasaLegislativa] [(_, origem, destino)] = DOCS[CasaLegislativa]
props_sapl = os.path.dirname(origem) props_sapl = os.path.dirname(origem)
# a pasta props_sapl deve conter apenas o origem e metadatas! # a pasta props_sapl deve conter apenas o origem e metadatas!
# Edit: Aparentemente há diretório que contém properties ao invés de
# metadata. O assert foi modificado para essa situação.
assert set(os.listdir(em_media(props_sapl))) < { assert set(os.listdir(em_media(props_sapl))) < {
'logo_casa.gif', '.metadata', 'logo_casa.gif.metadata'} 'logo_casa.gif', '.metadata', 'logo_casa.gif.metadata',
'.properties', 'logo_casa.gif.properties', '.objects'}
mover_documento(origem, destino) mover_documento(origem, destino)
casa = get_casa_legislativa() casa = get_casa_legislativa()
casa.logotipo = destino casa.logotipo = destino
@ -146,15 +168,22 @@ def migrar_docs_por_ids(tipo):
for arq in os.listdir(dir_origem): for arq in os.listdir(dir_origem):
match = pat.match(arq) match = pat.match(arq)
if match: if match:
# associa documento ao objeto
try:
origem = os.path.join(dir_origem, match.group(0)) origem = os.path.join(dir_origem, match.group(0))
id = match.group(1) id = match.group(1)
obj = tipo.objects.get(pk=id)
extensao = get_extensao(origem) extensao = get_extensao(origem)
if hasattr(obj, "ano"):
destino = base_destino.format(id, extensao, obj.ano)
elif isinstance(obj, DocumentoAcessorio):
destino = base_destino.format(
id, extensao, obj.materia.ano)
else:
destino = base_destino.format(id, extensao) destino = base_destino.format(id, extensao)
mover_documento(origem, destino) mover_documento(origem, destino)
# associa documento ao objeto
try:
obj = tipo.objects.get(pk=id)
setattr(obj, campo, destino) setattr(obj, campo, destino)
obj.save() obj.save()
except tipo.DoesNotExist: except tipo.DoesNotExist:
@ -199,6 +228,7 @@ def migrar_documentos():
SessaoPlenaria, SessaoPlenaria,
Proposicao, Proposicao,
DocumentoAdministrativo, DocumentoAdministrativo,
DocumentoAcessorioAdministrativo,
]: ]:
migrar_docs_por_ids(tipo) migrar_docs_por_ids(tipo)

194
sapl/legacy/migration.py

@ -8,10 +8,11 @@ import yaml
from django.apps import apps from django.apps import apps
from django.apps.config import AppConfig from django.apps.config import AppConfig
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import OperationalError, ProgrammingError, connections, models from django.db import OperationalError, ProgrammingError, connections, models
from django.db.models import CharField, Max, ProtectedError, TextField from django.db.models import CharField, Max, ProtectedError, TextField, Count
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from django.db.models.signals import post_delete, post_save from django.db.models.signals import post_delete, post_save
from model_mommy import mommy from model_mommy import mommy
@ -20,17 +21,20 @@ from model_mommy.mommy import foreign_key_required, make
from sapl.base.models import Argumento, Autor, Constraint, ProblemaMigracao from sapl.base.models import Argumento, Autor, Constraint, ProblemaMigracao
from sapl.comissoes.models import Comissao, Composicao, Participacao from sapl.comissoes.models import Comissao, Composicao, Participacao
from sapl.legacy.models import Protocolo as ProtocoloLegado from sapl.legacy.models import Protocolo as ProtocoloLegado
from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa, from sapl.materia.models import (AcompanhamentoMateria, DocumentoAcessorio,
MateriaLegislativa, Proposicao,
StatusTramitacao, TipoDocumento, StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao, TipoMateriaLegislativa, TipoProposicao,
Tramitacao) Tramitacao)
from sapl.norma.models import (AssuntoNorma, NormaJuridica, from sapl.norma.models import (AssuntoNorma, NormaJuridica,
TipoVinculoNormaJuridica, NormaRelacionada) TipoVinculoNormaJuridica, NormaRelacionada)
from sapl.parlamentares.models import Parlamentar from sapl.parlamentares.models import (Legislatura,Mandato, Parlamentar,
from sapl.protocoloadm.models import Protocolo, StatusTramitacaoAdministrativo TipoAfastamento)
from sapl.protocoloadm.models import (DocumentoAdministrativo,Protocolo,
StatusTramitacaoAdministrativo)
from sapl.sessao.models import ExpedienteMateria, OrdemDia, RegistroVotacao from sapl.sessao.models import ExpedienteMateria, OrdemDia, RegistroVotacao
from sapl.settings import PROJECT_DIR from sapl.settings import PROJECT_DIR
from sapl.utils import delete_texto, normalize, save_texto from sapl.utils import normalize
# BASE ###################################################################### # BASE ######################################################################
# apps to be migrated, in app dependency order (very important) # apps to be migrated, in app dependency order (very important)
@ -157,6 +161,8 @@ def get_fk_related(field, value, label=None):
ct = ContentType.objects.get(pk=13) ct = ContentType.objects.get(pk=13)
value = TipoProposicao.objects.create( value = TipoProposicao.objects.create(
id=value, descricao='Erro', content_type=ct) id=value, descricao='Erro', content_type=ct)
ultimo_valor = get_last_value(type(value))
alter_sequence(type(value), ultimo_valor+1)
else: else:
value = tipo[0] value = tipo[0]
else: else:
@ -240,6 +246,25 @@ def delete_constraints(model):
(table, r[0])) (table, r[0]))
def problema_duplicatas(model, lista_duplicatas, argumentos):
for obj in lista_duplicatas:
pks = []
string_pks = ""
problema = "%s de PK %s não é único." % (model.__name__, obj.pk)
args_dict = {k: obj.__dict__[k]
for k in set(argumentos) & set(obj.__dict__.keys())}
for dup in model.objects.filter(**args_dict):
pks.append(dup.pk)
string_pks = "(" + ", ".join(map(str, pks)) + ")"
descricao = "As entradas de PK %s são idênticas, mas " \
"apenas uma deve existir" % string_pks
with reversion.create_revision():
warn(problema + ' => ' + descricao)
save_relation(obj=obj, problema=problema,
descricao=descricao, eh_stub=False, critico=True)
reversion.set_comment('%s não é único.' % model.__name__)
def recria_constraints(): def recria_constraints():
constraints = Constraint.objects.all() constraints = Constraint.objects.all()
for con in constraints: for con in constraints:
@ -249,6 +274,8 @@ def recria_constraints():
args = [a.argumento for a in con.argumento_set.all()] args = [a.argumento for a in con.argumento_set.all()]
args_string = '' args_string = ''
args_string = "(" + "_".join(map(str, args[2:-1])) + ")" args_string = "(" + "_".join(map(str, args[2:-1])) + ")"
model = ContentType.objects.filter(
model=con.nome_model.lower())[0].model_class()
try: try:
exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" % exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" %
(nome_tabela, nome_constraint, args_string)) (nome_tabela, nome_constraint, args_string))
@ -268,6 +295,18 @@ def recria_constraints():
args[i] = args[i] + '_id' args[i] = args[i] + '_id'
args_string = '' args_string = ''
args_string += "(" + ', '.join(map(str, args)) + ")" args_string += "(" + ', '.join(map(str, args)) + ")"
distintos = model.objects.distinct(*args)
todos = model.objects.all()
if hasattr(model, "content_type"):
distintos = distintos.exclude(content_type_id=None,
object_id=None)
todos = todos.exclude(content_type_id=None, object_id=None)
lista_duplicatas = list(set(todos).difference(set(distintos)))
if lista_duplicatas:
problema_duplicatas(model, lista_duplicatas, args)
else:
try: try:
exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" % exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" %
(nome_tabela, nome_constraint, args_string)) (nome_tabela, nome_constraint, args_string))
@ -275,8 +314,8 @@ def recria_constraints():
info('A constraint %s já foi recriada!' % nome_constraint) info('A constraint %s já foi recriada!' % nome_constraint)
except Exception as err: except Exception as err:
problema = re.findall('\(.*?\)', err.args[0]) problema = re.findall('\(.*?\)', err.args[0])
erro('A constraint [%s] da tabela [%s] não pode ser recriada' % erro('A constraint [%s] da tabela [%s] não pode ser" \
(nome_constraint, nome_tabela)) recriada' % (nome_constraint, nome_tabela))
erro('Os dados %s = %s estão duplicados. ' erro('Os dados %s = %s estão duplicados. '
'Arrume antes de recriar as constraints!' % 'Arrume antes de recriar as constraints!' %
(problema[0], problema[1])) (problema[0], problema[1]))
@ -311,10 +350,10 @@ def save_with_id(new, id):
def save_relation(obj, nome_campo='', problema='', descricao='', def save_relation(obj, nome_campo='', problema='', descricao='',
eh_stub=False): eh_stub=False, critico=False):
link = ProblemaMigracao( link = ProblemaMigracao(
content_object=obj, nome_campo=nome_campo, problema=problema, content_object=obj, nome_campo=nome_campo, problema=problema,
descricao=descricao, eh_stub=eh_stub,) descricao=descricao, eh_stub=eh_stub, critico=critico)
link.save() link.save()
@ -369,6 +408,34 @@ def fill_vinculo_norma_juridica():
TipoVinculoNormaJuridica.objects.bulk_create(lista_objs) TipoVinculoNormaJuridica.objects.bulk_create(lista_objs)
# Uma anomalia no sapl 2.5 causa a duplicação de registros de votação.
# Essa duplicação deve ser eliminada para que não haja erro no sapl 3.1
def excluir_registrovotacao_duplicados():
duplicatas_ids = RegistroVotacao.objects.values(
'materia', 'ordem', 'expediente').annotate(
Count('id')).order_by().filter(id__count__gt=1)
duplicatas_queryset = RegistroVotacao.objects.filter(
materia__in=[item['materia'] for item in duplicatas_ids])
for dup in duplicatas_queryset:
lista_dups = duplicatas_queryset.filter(
materia=dup.materia, expediente=dup.expediente, ordem=dup.ordem)
primeiro_registro = lista_dups[0]
lista_dups = lista_dups.exclude(pk=primeiro_registro.pk)
for objeto in lista_dups:
if (objeto.pk > primeiro_registro.pk):
try:
objeto.delete()
except:
assert 0
else:
try:
primeiro_registro.delete()
primeiro_registro = objeto
except:
assert 0
class DataMigrator: class DataMigrator:
def __init__(self): def __init__(self):
@ -445,8 +512,6 @@ class DataMigrator:
call([PROJECT_DIR.child('manage.py'), 'flush', call([PROJECT_DIR.child('manage.py'), 'flush',
'--database=default', '--no-input'], stdout=PIPE) '--database=default', '--no-input'], stdout=PIPE)
desconecta_sinais_indexacao()
fill_vinculo_norma_juridica() fill_vinculo_norma_juridica()
info('Começando migração: %s...' % obj) info('Começando migração: %s...' % obj)
self._do_migrate(obj) self._do_migrate(obj)
@ -464,11 +529,15 @@ class DataMigrator:
save_relation(obj=obj, problema=msg, save_relation(obj=obj, problema=msg,
descricao=descricao, eh_stub=False) descricao=descricao, eh_stub=False)
info('Excluindo possíveis duplicações em RegistroVotacao...')
excluir_registrovotacao_duplicados()
info('Deletando stubs desnecessários...') info('Deletando stubs desnecessários...')
while self.delete_stubs(): while self.delete_stubs():
pass pass
conecta_sinais_indexacao() info('Recriando constraints...')
recria_constraints()
def _do_migrate(self, obj): def _do_migrate(self, obj):
if isinstance(obj, AppConfig): if isinstance(obj, AppConfig):
@ -580,11 +649,46 @@ def migrate(obj=appconfs, interativo=True):
# MIGRATION_ADJUSTMENTS ##################################################### # MIGRATION_ADJUSTMENTS #####################################################
def adjust_ordemdia(new, old): def adjust_acompanhamentomateria(new, old):
# Prestar atenção new.confirmado = True
def adjust_documentoadministrativo(new, old):
if new.numero_protocolo:
protocolo = Protocolo.objects.get(numero=new.numero_protocolo,
ano=new.ano)
new.protocolo = protocolo
def adjust_mandato(new, old):
if not new.data_fim_mandato:
legislatura = Legislatura.objects.latest('data_fim')
new.data_fim_mandato = legislatura.data_fim
new.data_expedicao_diploma = legislatura.data_inicio
def adjust_ordemdia_antes_salvar(new, old):
new.votacao_aberta = False
if not old.tip_votacao: if not old.tip_votacao:
new.tipo_votacao = 1 new.tipo_votacao = 1
if old.num_ordem is None:
new.numero_ordem = 999999999
def adjust_ordemdia_depois_salvar(new, old):
if old.num_ordem is None and new.numero_ordem == 999999999:
with reversion.create_revision():
problema = 'OrdemDia de PK %s tinha seu valor de numero ordem'\
' nulo.' % old.pk
descricao = 'O valor %s foi colocado no lugar.' % new.numero_ordem
warn(problema + ' => ' + descricao)
save_relation(obj=new, problema=problema,
descricao=descricao, eh_stub=False)
reversion.set_comment('OrdemDia sem número da ordem.')
pass
def adjust_parlamentar(new, old): def adjust_parlamentar(new, old):
if old.ind_unid_deliberativa: if old.ind_unid_deliberativa:
@ -616,6 +720,25 @@ def adjust_participacao(new, old):
new.composicao = composicao new.composicao = composicao
def adjust_proposicao_antes_salvar(new, old):
if new.data_envio:
new.ano = new.data_envio.year
def adjust_proposicao_depois_salvar(new, old):
if not hasattr(old.dat_envio, 'year') or old.dat_envio.year == 1800:
msg = "O valor do campo data_envio (DateField) da model Proposicao"
"era inválido"
descricao = 'A data 1111-11-11 foi colocada no lugar'
problema = 'O valor da data era nulo ou inválido'
warn(msg + ' => ' + descricao)
new.data_envio = date(1111, 11, 11)
with reversion.create_revision():
save_relation(obj=new, problema=problema,
descricao=descricao, eh_stub=False)
reversion.set_comment('Ajuste de data pela migração')
def adjust_normarelacionada(new, old): def adjust_normarelacionada(new, old):
tipo = TipoVinculoNormaJuridica.objects.filter(sigla=old.tip_vinculo) tipo = TipoVinculoNormaJuridica.objects.filter(sigla=old.tip_vinculo)
assert len(tipo) == 1 assert len(tipo) == 1
@ -660,6 +783,12 @@ def adjust_registrovotacao_depois_salvar(new, old):
reversion.set_comment('RegistroVotacao sem ordem ou expediente') reversion.set_comment('RegistroVotacao sem ordem ou expediente')
def adjust_tipoafastamento(new, old):
if old.ind_afastamento == 1:
new.indicador = 'A'
def adjust_tipoproposicao(new, old): def adjust_tipoproposicao(new, old):
if old.ind_mat_ou_doc == 'M': if old.ind_mat_ou_doc == 'M':
new.tipo_conteudo_related = TipoMateriaLegislativa.objects.get( new.tipo_conteudo_related = TipoMateriaLegislativa.objects.get(
@ -723,6 +852,7 @@ def adjust_autor(new, old):
new.nome = new.autor_related.nome_parlamentar new.nome = new.autor_related.nome_parlamentar
elif old.cod_comissao: elif old.cod_comissao:
new.autor_related = Comissao.objects.get(pk=old.cod_comissao) new.autor_related = Comissao.objects.get(pk=old.cod_comissao)
new.nome = new.autor_related.nome
if old.col_username: if old.col_username:
if not get_user_model().objects.filter( if not get_user_model().objects.filter(
@ -732,6 +862,13 @@ def adjust_autor(new, old):
with reversion.create_revision(): with reversion.create_revision():
user.save() user.save()
reversion.set_comment('Objeto criado pela migração') reversion.set_comment('Objeto criado pela migração')
grupo_autor = Group.objects.get(name="Autor")
user.groups.add(grupo_autor)
if old.cod_parlamentar:
grupo_parlamentar = Group.objects.get(name="Parlamentar")
user.groups.add(grupo_parlamentar)
new.user = user new.user = user
else: else:
new.user = get_user_model().objects.filter( new.user = get_user_model().objects.filter(
@ -750,14 +887,19 @@ def adjust_comissao(new, old):
AJUSTE_ANTES_SALVAR = { AJUSTE_ANTES_SALVAR = {
Autor: adjust_autor, Autor: adjust_autor,
AcompanhamentoMateria: adjust_acompanhamentomateria,
Comissao: adjust_comissao, Comissao: adjust_comissao,
DocumentoAdministrativo: adjust_documentoadministrativo,
Mandato: adjust_mandato,
NormaJuridica: adjust_normajuridica_antes_salvar, NormaJuridica: adjust_normajuridica_antes_salvar,
NormaRelacionada: adjust_normarelacionada, NormaRelacionada: adjust_normarelacionada,
OrdemDia: adjust_ordemdia, OrdemDia: adjust_ordemdia_antes_salvar,
Parlamentar: adjust_parlamentar, Parlamentar: adjust_parlamentar,
Participacao: adjust_participacao, Participacao: adjust_participacao,
Proposicao: adjust_proposicao_antes_salvar,
Protocolo: adjust_protocolo, Protocolo: adjust_protocolo,
RegistroVotacao: adjust_registrovotacao_antes_salvar, RegistroVotacao: adjust_registrovotacao_antes_salvar,
TipoAfastamento: adjust_tipoafastamento,
TipoProposicao: adjust_tipoproposicao, TipoProposicao: adjust_tipoproposicao,
StatusTramitacao: adjust_statustramitacao, StatusTramitacao: adjust_statustramitacao,
StatusTramitacaoAdministrativo: adjust_statustramitacaoadm, StatusTramitacaoAdministrativo: adjust_statustramitacaoadm,
@ -766,6 +908,8 @@ AJUSTE_ANTES_SALVAR = {
AJUSTE_DEPOIS_SALVAR = { AJUSTE_DEPOIS_SALVAR = {
NormaJuridica: adjust_normajuridica_depois_salvar, NormaJuridica: adjust_normajuridica_depois_salvar,
OrdemDia: adjust_ordemdia_depois_salvar,
Proposicao: adjust_proposicao_depois_salvar,
Protocolo: adjust_protocolo_depois_salvar, Protocolo: adjust_protocolo_depois_salvar,
RegistroVotacao: adjust_registrovotacao_depois_salvar, RegistroVotacao: adjust_registrovotacao_depois_salvar,
} }
@ -801,23 +945,3 @@ def make_with_log(model, _quantity=None, make_m2m=False, **attrs):
return stub return stub
make_with_log.required = foreign_key_required make_with_log.required = foreign_key_required
# DISCONNECT SIGNAL ########################################################
def desconecta_sinais_indexacao():
post_save.disconnect(save_texto, NormaJuridica)
post_save.disconnect(save_texto, DocumentoAcessorio)
post_save.disconnect(save_texto, MateriaLegislativa)
post_delete.disconnect(delete_texto, NormaJuridica)
post_delete.disconnect(delete_texto, DocumentoAcessorio)
post_delete.disconnect(delete_texto, MateriaLegislativa)
def conecta_sinais_indexacao():
post_save.connect(save_texto, NormaJuridica)
post_save.connect(save_texto, DocumentoAcessorio)
post_save.connect(save_texto, MateriaLegislativa)
post_delete.connect(delete_texto, NormaJuridica)
post_delete.connect(delete_texto, DocumentoAcessorio)
post_delete.connect(delete_texto, MateriaLegislativa)

146
sapl/legacy/scripts/street_sweeper.py

@ -0,0 +1,146 @@
#!/usr/bin/python
# requisito: pip install PyMySQL
import pymysql.cursors
HOST = 'localhost'
USER = 'root'
PASSWORD = ''
DB = ''
SELECT_EXCLUIDOS = "SELECT %s FROM %s WHERE ind_excluido = 1 ORDER BY %s"
REGISTROS_INCONSISTENTES = "DELETE FROM %s WHERE %s in (%s) AND ind_excluido = 0 "
EXCLUI_REGISTRO = "DELETE FROM %s WHERE ind_excluido=1"
NORMA_DEP = "DELETE FROM vinculo_norma_juridica WHERE cod_norma_referente in (%s) OR \
cod_norma_referida in (%s) AND ind_excluido = 0 "
mapa = {} # mapa com tabela principal -> tabelas dependentes
mapa['tipo_autor'] = ['autor']
mapa['materia_legislativa'] = ['acomp_materia', 'autoria', 'despacho_inicial',
'documento_acessorio', 'expediente_materia',
'legislacao_citada', 'materia_assunto',
'numeracao', 'ordem_dia', 'parecer',
'proposicao', 'registro_votacao',
'relatoria', 'tramitacao']
mapa['norma_juridica'] = ['vinculo_norma_juridica']
mapa['comissao'] = ['composicao_comissao']
mapa['sessao_legislativa'] = ['composicao_mesa']
mapa['tipo_expediente'] = ['expediente_sessao_plenaria']
"""
mapa['autor'] = ['tipo_autor', 'partido', 'comissao', 'parlamentar']
mapa['parlamentar'] = ['autor', 'autoria', 'composicao_comissao',
'composicao_mesa', 'dependente', 'filiacao',
'mandato', 'mesa_sessao_plenaria', 'oradores',
'oradores_expediente', 'ordem_dia_presenca',
'registro_votacao_parlamentar', 'relatoria',
'sessao_plenaria_presenca', 'unidade_tramitacao']
"""
def get_ids_excluidos(cursor, query):
"""
recupera as PKs de registros com ind_excluido = 1 da tabela principal
"""
cursor.execute(query)
excluidos = cursor.fetchall()
# flat tuple of tuples with map transformation into string
excluidos = [str(val) for sublist in excluidos for val in sublist]
return excluidos
def remove_tabelas(cursor, tabela_principal, pk, query_dependentes=None):
QUERY = SELECT_EXCLUIDOS % (pk, tabela_principal, pk)
ids_excluidos = get_ids_excluidos(cursor, QUERY)
print("\nRegistros da tabela '%s' com ind_excluido = 1: %s" % (tabela_principal.upper(), len(ids_excluidos)))
"""
Remove registros de tabelas que dependem da tabela principal,
e que se encontram com ind_excluido = 0 (nao excluidas), se
tais registros existirem.
"""
if ids_excluidos:
print("Dependencias inconsistentes")
for tabela in mapa[tabela_principal]:
QUERY_DEP = REGISTROS_INCONSISTENTES % (tabela, pk, ','.join(ids_excluidos))
# Trata caso especifico de norma_juridica
if query_dependentes:
QUERY_DEP = query_dependentes % (','.join(ids_excluidos),
','.join(ids_excluidos))
print(tabela.upper(), cursor.execute(QUERY_DEP))
"""
Remove todos os registros com ind_excluido = 1 das tabelas
dependentes e da tabela principal, nesta ordem.
"""
print("\n\nRegistros com ind_excluido = 1")
for tabela in mapa[tabela_principal] + [tabela_principal]:
QUERY = EXCLUI_REGISTRO % tabela
print(tabela.upper(), cursor.execute(QUERY))
def remove_excluidas(cursor):
cursor.execute("SHOW_TABLES")
for row in cursor.fetchall():
print(row)
def remove_proposicao_invalida(cursor):
return cursor.execute(
"DELETE FROM proposicao WHERE cod_mat_ou_doc is null")
def remove_materia_assunto_invalida(cursor):
return cursor.execute(
"DELETE FROM materia_assunto WHERE cod_assunto = 0")
def shotgun_remove(cursor):
for tabela in get_ids_excluidos(cursor, "SHOW TABLES"):
try:
cursor.execute("DELETE FROM %s WHERE ind_excluido = 1" % tabela)
except:
pass
if __name__ == '__main__':
connection = pymysql.connect(host=HOST,
user=USER,
password=PASSWORD,
db=DB)
cursor = connection.cursor()
# TIPO AUTOR
remove_tabelas(cursor, 'tipo_autor', 'tip_autor')
# MATERIA LEGISLATIVA
remove_tabelas(cursor, 'materia_legislativa', 'cod_materia')
# NORMA JURIDICA
remove_tabelas(cursor, 'norma_juridica', 'cod_norma', NORMA_DEP)
# COMISSAO
remove_tabelas(cursor, 'comissao', 'cod_comissao')
# SESSAO LEGISLATIVA
remove_tabelas(cursor, 'sessao_legislativa', 'cod_sessao_leg')
# EXPEDIENTE SESSAO
remove_tabelas(cursor, 'tipo_expediente', 'cod_expediente')
# AUTOR
remove_tabelas(cursor, 'autor', 'cod_autor')
# PARLAMENTAR
remove_tabelas(cursor, 'parlamentar', 'cod_parlamentar')
# PROPOSICAO
remove_proposicao_invalida(cursor)
# MATERIA_ASSUNTO
remove_materia_assunto_invalida(cursor)
# shotgun_remove(cursor)
cursor.close()

211
sapl/materia/email_utils.py

@ -0,0 +1,211 @@
from datetime import datetime
from django.core.mail import EmailMultiAlternatives, get_connection, send_mail
from django.core.urlresolvers import reverse
from django.template import Context, loader
from sapl.base.models import CasaLegislativa
from sapl.settings import EMAIL_SEND_USER
from .models import AcompanhamentoMateria
def load_email_templates(templates, context={}):
emails = []
for t in templates:
tpl = loader.get_template(t)
email = tpl.render(Context(context))
if t.endswith(".html"):
email = email.replace('\n', '').replace('\r', '')
emails.append(email)
return emails
def enviar_emails(sender, recipients, messages):
'''
Recipients is a string list of email addresses
Messages is an array of dicts of the form:
{'recipient': 'address', # useless????
'subject': 'subject text',
'txt_message': 'text message',
'html_message': 'html message'
}
'''
if len(messages) == 1:
# sends an email simultaneously to all recipients
send_mail(messages[0]['subject'],
messages[0]['txt_message'],
sender,
recipients,
html_message=messages[0]['html_message'],
fail_silently=False)
elif len(recipients) > len(messages):
raise ValueError("Message list should have size 1 \
or equal recipient list size. \
recipients: %s, messages: %s" % (recipients, messages)
)
else:
# sends an email simultaneously to all reciepients
for (d, m) in zip(recipients, messages):
send_mail(m['subject'],
m['txt_message'],
sender,
[d],
html_message=m['html_message'],
fail_silently=False)
def criar_email_confirmacao(base_url, casa_legislativa, materia, hash_txt=''):
if not casa_legislativa:
raise ValueError("Casa Legislativa é obrigatória")
if not materia:
raise ValueError("Matéria é obrigatória")
# FIXME i18n
casa_nome = (casa_legislativa.nome + ' de ' +
casa_legislativa.municipio + '-' +
casa_legislativa.uf)
materia_url = reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': materia.id})
confirmacao_url = reverse('sapl.materia:acompanhar_confirmar',
kwargs={'pk': materia.id})
autores = []
for autoria in materia.autoria_set.all():
autores.append(autoria.autor.nome)
templates = load_email_templates(['email/acompanhar.txt',
'email/acompanhar.html'],
{"casa_legislativa": casa_nome,
"logotipo": casa_legislativa.logotipo,
"descricao_materia": materia.ementa,
"autoria": autores,
"hash_txt": hash_txt,
"base_url": base_url,
"materia": str(materia),
"materia_url": materia_url,
"confirmacao_url": confirmacao_url, })
return templates
def do_envia_email_confirmacao(base_url, casa, materia, destinatario):
#
# Envia email de confirmacao para atualizações de tramitação
#
sender = EMAIL_SEND_USER
# FIXME i18n
subject = "[SAPL] " + str(materia) + " - Ative o Acompanhamento da Materia"
messages = []
recipients = []
email_texts = criar_email_confirmacao(base_url,
casa,
materia,
destinatario.hash,)
recipients.append(destinatario.email)
messages.append({
'recipient': destinatario.email,
'subject': subject,
'txt_message': email_texts[0],
'html_message': email_texts[1]
})
enviar_emails(sender, recipients, messages)
def criar_email_tramitacao(base_url, casa_legislativa, materia, status,
unidade_destino, hash_txt=''):
if not casa_legislativa:
raise ValueError("Casa Legislativa é obrigatória")
if not materia:
raise ValueError("Matéria é obrigatória")
# FIXME i18n
casa_nome = (casa_legislativa.nome + ' de ' +
casa_legislativa.municipio + '-' +
casa_legislativa.uf)
url_materia = reverse('sapl.materia:tramitacao_list',
kwargs={'pk': materia.id})
url_excluir = reverse('sapl.materia:acompanhar_excluir',
kwargs={'pk': materia.id})
autores = []
for autoria in materia.autoria_set.all():
autores.append(autoria.autor.nome)
tramitacao = materia.tramitacao_set.last()
templates = load_email_templates(['email/tramitacao.txt',
'email/tramitacao.html'],
{"casa_legislativa": casa_nome,
"data_registro": datetime.now().strftime(
"%d/%m/%Y"),
"cod_materia": materia.id,
"logotipo": casa_legislativa.logotipo,
"descricao_materia": materia.ementa,
"autoria": autores,
"data": tramitacao.data_tramitacao,
"status": status,
"localizacao": unidade_destino,
"texto_acao": tramitacao.texto,
"hash_txt": hash_txt,
"materia": str(materia),
"base_url": base_url,
"materia_url": url_materia,
"excluir_url": url_excluir})
return templates
def do_envia_email_tramitacao(base_url, materia, status, unidade_destino):
#
# Envia email de tramitacao para usuarios cadastrados
#
destinatarios = AcompanhamentoMateria.objects.filter(materia=materia,
confirmado=True)
casa = CasaLegislativa.objects.first()
sender = EMAIL_SEND_USER
# FIXME i18n
subject = "[SAPL] " + str(materia) + \
" - Acompanhamento de Materia Legislativa"
connection = get_connection()
connection.open()
for destinatario in destinatarios:
try:
email_texts = criar_email_tramitacao(base_url,
casa,
materia,
status,
unidade_destino,
destinatario.hash,)
email = EmailMultiAlternatives(
subject,
email_texts[0],
sender,
[destinatario.email],
connection=connection)
email.attach_alternative(email_texts[1], "text/html")
email.send()
# Garantia de que, mesmo com o lançamento de qualquer exceção,
# a conexão será fechada
except Exception:
connection.close()
raise Exception('Erro ao enviar e-mail de acompanhamento de matéria.')
connection.close()

115
sapl/materia/forms.py

@ -1,8 +1,7 @@
import os
from datetime import date, datetime from datetime import date, datetime
import os
import django_filters
from crispy_forms.bootstrap import Alert, FormActions, InlineRadios from crispy_forms.bootstrap import Alert, FormActions, InlineRadios
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset, from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset,
@ -21,8 +20,8 @@ from django.utils.encoding import force_text
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django_filters
import sapl
from sapl.base.models import Autor from sapl.base.models import Autor
from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC, from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC,
@ -40,6 +39,7 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
ChoiceWithoutValidationField, ChoiceWithoutValidationField,
MateriaPesquisaOrderingFilter, RangeWidgetOverride, MateriaPesquisaOrderingFilter, RangeWidgetOverride,
autor_label, autor_modal, models_with_gr_for_model) autor_label, autor_modal, models_with_gr_for_model)
import sapl
from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial, from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial,
DocumentoAcessorio, Numeracao, Proposicao, Relatoria, DocumentoAcessorio, Numeracao, Proposicao, Relatoria,
@ -52,8 +52,8 @@ def ANO_CHOICES():
def em_tramitacao(): def em_tramitacao():
return [('', 'Tanto Faz'), return [('', 'Tanto Faz'),
(True, 'Sim'), (1, 'Sim'),
(False, 'Não')] (0, 'Não')]
class AdicionarVariasAutoriasFilterSet(django_filters.FilterSet): class AdicionarVariasAutoriasFilterSet(django_filters.FilterSet):
@ -124,6 +124,8 @@ class UnidadeTramitacaoForm(ModelForm):
fields = ['comissao', 'orgao', 'parlamentar'] fields = ['comissao', 'orgao', 'parlamentar']
def clean(self): def clean(self):
super(UnidadeTramitacaoForm, self).clean()
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
for key in list(cleaned_data.keys()): for key in list(cleaned_data.keys()):
@ -164,17 +166,6 @@ class DocumentoAcessorioForm(ModelForm):
class Meta: class Meta:
model = DocumentoAcessorio model = DocumentoAcessorio
fields = ['tipo', 'nome', 'data', 'autor', 'ementa', 'arquivo'] fields = ['tipo', 'nome', 'data', 'autor', 'ementa', 'arquivo']
widgets = {'autor': forms.HiddenInput()}
def clean_autor(self):
autor_field = self.cleaned_data['autor']
try:
int(autor_field)
except ValueError:
return autor_field
else:
if autor_field:
return str(Autor.objects.get(id=autor_field))
class RelatoriaForm(ModelForm): class RelatoriaForm(ModelForm):
@ -190,6 +181,8 @@ class RelatoriaForm(ModelForm):
super(RelatoriaForm, self).__init__(*args, **kwargs) super(RelatoriaForm, self).__init__(*args, **kwargs)
def clean(self): def clean(self):
super(RelatoriaForm, self).clean()
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
try: try:
@ -222,6 +215,7 @@ class TramitacaoForm(ModelForm):
self.fields['data_tramitacao'].initial = datetime.now() self.fields['data_tramitacao'].initial = datetime.now()
def clean(self): def clean(self):
super(TramitacaoForm, self).clean()
if 'data_encaminhamento' in self.data: if 'data_encaminhamento' in self.data:
data_enc_form = self.cleaned_data['data_encaminhamento'] data_enc_form = self.cleaned_data['data_encaminhamento']
@ -299,6 +293,8 @@ class TramitacaoUpdateForm(TramitacaoForm):
} }
def clean(self): def clean(self):
super(TramitacaoUpdateForm, self).clean()
local = self.instance.unidade_tramitacao_local local = self.instance.unidade_tramitacao_local
data_tram = self.instance.data_tramitacao data_tram = self.instance.data_tramitacao
@ -339,6 +335,8 @@ class LegislacaoCitadaForm(ModelForm):
'item'] 'item']
def clean(self): def clean(self):
super(LegislacaoCitadaForm, self).clean()
if self.errors: if self.errors:
return self.errors return self.errors
@ -400,6 +398,8 @@ class NumeracaoForm(ModelForm):
'data_materia'] 'data_materia']
def clean(self): def clean(self):
super(NumeracaoForm, self).clean()
if self.errors: if self.errors:
return self.errors return self.errors
@ -443,6 +443,8 @@ class AnexadaForm(ModelForm):
return super(AnexadaForm, self).__init__(*args, **kwargs) return super(AnexadaForm, self).__init__(*args, **kwargs)
def clean(self): def clean(self):
super(AnexadaForm, self).clean()
if self.errors: if self.errors:
return self.errors return self.errors
@ -483,32 +485,42 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
}} }}
ano = django_filters.ChoiceFilter(required=False, ano = django_filters.ChoiceFilter(required=False,
label=u'Ano da Matéria', label='Ano da Matéria',
choices=ANO_CHOICES) choices=ANO_CHOICES)
autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput()) autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
autoria__primeiro_autor = django_filters.BooleanFilter(required=False,
label='Primeiro Autor',
widget=forms.HiddenInput())
ementa = django_filters.CharFilter(lookup_expr='icontains') ementa = django_filters.CharFilter(lookup_expr='icontains')
em_tramitacao = django_filters.ChoiceFilter(required=False, em_tramitacao = django_filters.ChoiceFilter(required=False,
label=u'Em tramitação', label='Em tramitação',
choices=em_tramitacao) choices=em_tramitacao)
materiaassunto__assunto = django_filters.ModelChoiceFilter( materiaassunto__assunto = django_filters.ModelChoiceFilter(
queryset=AssuntoMateria.objects.all(), queryset=AssuntoMateria.objects.all(),
label=_('Assunto da Matéria')) label=_('Assunto da Matéria'))
numeracao__numero_materia = django_filters.NumberFilter(
required=False,
label=_('Número do Processo'))
o = MateriaPesquisaOrderingFilter() o = MateriaPesquisaOrderingFilter()
class Meta: class Meta:
model = MateriaLegislativa model = MateriaLegislativa
fields = ['numero', fields = ['numero',
'numero_protocolo', 'numero_protocolo',
'numeracao__numero_materia',
'ano', 'ano',
'tipo', 'tipo',
'data_apresentacao', 'data_apresentacao',
'data_publicacao', 'data_publicacao',
'autoria__autor__tipo', 'autoria__autor__tipo',
'autoria__primeiro_autor',
# FIXME 'autoria__autor__partido', # FIXME 'autoria__autor__partido',
'relatoria__parlamentar_id', 'relatoria__parlamentar_id',
'local_origem_externa', 'local_origem_externa',
@ -529,14 +541,16 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
row1 = to_row( row1 = to_row(
[('tipo', 12)]) [('tipo', 12)])
row2 = to_row( row2 = to_row(
[('numero', 4), [('numero', 3),
('ano', 4), ('numeracao__numero_materia', 3),
('numero_protocolo', 4)]) ('numero_protocolo', 3),
('ano', 3)])
row3 = to_row( row3 = to_row(
[('data_apresentacao', 6), [('data_apresentacao', 6),
('data_publicacao', 6)]) ('data_publicacao', 6)])
row4 = to_row( row4 = to_row(
[('autoria__autor', 0), [('autoria__autor', 0),
('autoria__primeiro_autor', 0),
(Button('pesquisar', (Button('pesquisar',
'Pesquisar Autor', 'Pesquisar Autor',
css_class='btn btn-primary btn-sm'), 2), css_class='btn btn-primary btn-sm'), 2),
@ -617,6 +631,8 @@ class DespachoInicialForm(ModelForm):
fields = ['comissao'] fields = ['comissao']
def clean(self): def clean(self):
super(DespachoInicialForm, self).clean()
if self.errors: if self.errors:
return self.errors return self.errors
@ -637,6 +653,8 @@ class AutoriaForm(ModelForm):
fields = ['autor', 'primeiro_autor'] fields = ['autor', 'primeiro_autor']
def clean(self): def clean(self):
super(AutoriaForm, self).clean()
if self.errors: if self.errors:
return self.errors return self.errors
@ -808,6 +826,8 @@ class TipoProposicaoForm(ModelForm):
'tipo_conteudo_related'].initial = self.instance.object_id 'tipo_conteudo_related'].initial = self.instance.object_id
def clean(self): def clean(self):
super(TipoProposicaoForm, self).clean()
cd = self.cleaned_data cd = self.cleaned_data
content_type = cd['content_type'] content_type = cd['content_type']
@ -821,9 +841,25 @@ class TipoProposicaoForm(ModelForm):
pk=cd['tipo_conteudo_related']).exists(): pk=cd['tipo_conteudo_related']).exists():
raise ValidationError( raise ValidationError(
_('O Registro definido (%s) não está na base de %s.' _('O Registro definido (%s) não está na base de %s.'
) % (cd['tipo_conteudo_related'], cd['q'], content_type)) ) % (cd['tipo_conteudo_related'], content_type))
return self.cleaned_data unique_value = self._meta.model.objects.filter(
content_type=content_type, object_id=cd['tipo_conteudo_related'])
if self.instance.pk:
unique_value = unique_value.exclude(pk=self.instance.pk)
unique_value = unique_value.first()
if unique_value:
raise ValidationError(
_('Já existe um Tipo de Proposição (%s) '
'que foi defindo como (%s) para (%s)'
) % (unique_value,
content_type,
unique_value.tipo_conteudo_related))
return super().clean()
@transaction.atomic @transaction.atomic
def save(self, commit=False): def save(self, commit=False):
@ -986,10 +1022,14 @@ class ProposicaoForm(forms.ModelForm):
texto_original = self.cleaned_data.get('texto_original', False) texto_original = self.cleaned_data.get('texto_original', False)
if texto_original: if texto_original:
if texto_original.size > MAX_DOC_UPLOAD_SIZE: if texto_original.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("Arquivo muito grande. ( > 5mb )") max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))
return texto_original return texto_original
def clean(self): def clean(self):
super(ProposicaoForm, self).clean()
cd = self.cleaned_data cd = self.cleaned_data
tm, am, nm = (cd.get('tipo_materia', ''), tm, am, nm = (cd.get('tipo_materia', ''),
@ -1106,6 +1146,18 @@ class ConfirmarProposicaoForm(ProposicaoForm):
if 'numero_de_paginas' not in self._meta.fields: if 'numero_de_paginas' not in self._meta.fields:
self._meta.fields.append('numero_de_paginas') self._meta.fields.append('numero_de_paginas')
self.instance = kwargs.get('instance', None)
if not self.instance:
raise ValueError(_('Erro na Busca por proposição a incorporar'))
if self.instance.tipo.content_type.model_class() == TipoDocumento:
if 'numero_de_paginas' in self._meta.fields:
self._meta.fields.remove('numero_de_paginas')
if 'gerar_protocolo' in self._meta.fields:
self._meta.fields.remove('gerar_protocolo')
if 'regime_tramitacao' in self._meta.fields:
self._meta.fields.remove('regime_tramitacao')
# esta chamada isola o __init__ de ProposicaoForm # esta chamada isola o __init__ de ProposicaoForm
super(ProposicaoForm, self).__init__(*args, **kwargs) super(ProposicaoForm, self).__init__(*args, **kwargs)
@ -1137,7 +1189,11 @@ class ConfirmarProposicaoForm(ProposicaoForm):
css_class="ementa_materia hidden alert-info", css_class="ementa_materia hidden alert-info",
dismiss=False), 12)))) dismiss=False), 12))))
itens_incorporacao = []
if self.instance.tipo.content_type.model_class() == \
TipoMateriaLegislativa:
itens_incorporacao = [to_column(('regime_tramitacao', 4))] itens_incorporacao = [to_column(('regime_tramitacao', 4))]
if self.proposicao_incorporacao_obrigatoria == 'C': if self.proposicao_incorporacao_obrigatoria == 'C':
itens_incorporacao.append(to_column((InlineRadios( itens_incorporacao.append(to_column((InlineRadios(
'gerar_protocolo'), 4))) 'gerar_protocolo'), 4)))
@ -1180,6 +1236,14 @@ class ConfirmarProposicaoForm(ProposicaoForm):
self.fields['gerar_protocolo'].initial = True self.fields['gerar_protocolo'].initial = True
def clean(self): def clean(self):
super(ConfirmarProposicaoForm, self).clean()
numeracao = sapl.base.models.AppConfig.attr('sequencia_numeracao')
if not numeracao:
raise ValidationError("A sequência de numeração (por ano ou geral)"
" não foi configurada para a aplicação em "
"tabelas auxiliares")
if 'incorporar' in self.data: if 'incorporar' in self.data:
cd = ProposicaoForm.clean(self) cd = ProposicaoForm.clean(self)
@ -1367,6 +1431,9 @@ class ConfirmarProposicaoForm(ProposicaoForm):
proposicao.conteudo_gerado_related = conteudo_gerado proposicao.conteudo_gerado_related = conteudo_gerado
proposicao.save() proposicao.save()
if self.instance.tipo.content_type.model_class() == TipoDocumento:
return self.instance
# Nunca gerar protocolo # Nunca gerar protocolo
if self.proposicao_incorporacao_obrigatoria == 'N': if self.proposicao_incorporacao_obrigatoria == 'N':
return self.instance return self.instance

32
sapl/materia/migrations/0005_auto_20170522_1051.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-05-22 10:51
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.materia.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('materia', '0004_auto_20170504_1751'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorio',
name='arquivo',
field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.anexo_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'),
),
migrations.AlterField(
model_name='materialegislativa',
name='texto_original',
field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'),
),
migrations.AlterField(
model_name='proposicao',
name='texto_original',
field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'),
),
]

32
sapl/materia/migrations/0005_auto_20170522_1904.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.12 on 2017-05-22 19:04
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.materia.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('materia', '0004_auto_20170504_1751'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorio',
name='arquivo',
field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.anexo_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'),
),
migrations.AlterField(
model_name='materialegislativa',
name='texto_original',
field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'),
),
migrations.AlterField(
model_name='proposicao',
name='texto_original',
field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'),
),
]

16
sapl/materia/migrations/0006_merge.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-05-23 18:20
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0005_auto_20170522_1051'),
('materia', '0005_auto_20170522_1904'),
]
operations = [
]

20
sapl/materia/migrations/0007_auto_20170620_1252.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2017-06-20 12:52
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0006_merge'),
]
operations = [
migrations.AlterField(
model_name='tipoproposicao',
name='descricao',
field=models.CharField(error_messages={'unique': 'Já existe um Tipo de Proposição com esta descrição.'}, max_length=50, unique=True, verbose_name='Descrição'),
),
]

21
sapl/materia/migrations/0008_auto_20170622_1527.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2017-06-22 15:27
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('materia', '0007_auto_20170620_1252'),
]
operations = [
migrations.AlterField(
model_name='acompanhamentomateria',
name='materia',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='materia.MateriaLegislativa'),
),
]

25
sapl/materia/migrations/0009_auto_20170712_0951.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2017-07-12 09:51
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0008_auto_20170622_1527'),
]
operations = [
migrations.AddField(
model_name='documentoacessorio',
name='data_ultima_atualizacao',
field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data'),
),
migrations.AddField(
model_name='materialegislativa',
name='data_ultima_atualizacao',
field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data'),
),
]

53
sapl/materia/models.py

@ -1,6 +1,5 @@
from datetime import datetime from datetime import datetime
import reversion
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -9,6 +8,7 @@ from django.db import models
from django.utils import formats from django.utils import formats
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from model_utils import Choices from model_utils import Choices
import reversion
from sapl.base.models import Autor from sapl.base.models import Autor
from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao
@ -19,6 +19,7 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey,
SaplGenericRelation, restringe_tipos_de_arquivo_txt, SaplGenericRelation, restringe_tipos_de_arquivo_txt,
texto_upload_path) texto_upload_path)
EM_TRAMITACAO = [(1, 'Sim'), EM_TRAMITACAO = [(1, 'Sim'),
(0, 'Não')] (0, 'Não')]
@ -33,7 +34,13 @@ def grupo_autor():
@reversion.register() @reversion.register()
class TipoProposicao(models.Model): class TipoProposicao(models.Model):
descricao = models.CharField(max_length=50, verbose_name=_('Descrição')) descricao = models.CharField(
max_length=50,
verbose_name=_('Descrição'),
unique=True,
error_messages={
'unique': _('Já existe um Tipo de Proposição com esta descrição.')
})
# FIXME - para a rotina de migração - estes campos mudaram # FIXME - para a rotina de migração - estes campos mudaram
# retire o comentário quando resolver # retire o comentário quando resolver
@ -121,6 +128,14 @@ TIPO_APRESENTACAO_CHOICES = Choices(('O', 'oral', _('Oral')),
('E', 'escrita', _('Escrita'))) ('E', 'escrita', _('Escrita')))
def materia_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath=instance.ano)
def anexo_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath=instance.materia.ano)
@reversion.register() @reversion.register()
class MateriaLegislativa(models.Model): class MateriaLegislativa(models.Model):
@ -194,19 +209,27 @@ class MateriaLegislativa(models.Model):
texto_original = models.FileField( texto_original = models.FileField(
blank=True, blank=True,
null=True, null=True,
upload_to=texto_upload_path, upload_to=materia_upload_path,
verbose_name=_('Texto Original'), verbose_name=_('Texto Original'),
validators=[restringe_tipos_de_arquivo_txt]) validators=[restringe_tipos_de_arquivo_txt])
texto_articulado = GenericRelation( texto_articulado = GenericRelation(
TextoArticulado, related_query_name='texto_articulado') TextoArticulado, related_query_name='texto_articulado')
proposicao = GenericRelation(
'Proposicao', related_query_name='proposicao')
autores = models.ManyToManyField( autores = models.ManyToManyField(
Autor, Autor,
through='Autoria', through='Autoria',
through_fields=('materia', 'autor'), through_fields=('materia', 'autor'),
symmetrical=False,) symmetrical=False,)
data_ultima_atualizacao = models.DateTimeField(
blank=True, null=True,
auto_now=True,
verbose_name=_('Data'))
class Meta: class Meta:
verbose_name = _('Matéria Legislativa') verbose_name = _('Matéria Legislativa')
verbose_name_plural = _('Matérias Legislativas') verbose_name_plural = _('Matérias Legislativas')
@ -236,6 +259,10 @@ class MateriaLegislativa(models.Model):
if self.texto_original: if self.texto_original:
self.texto_original.delete() self.texto_original.delete()
for p in self.proposicao.all():
p.conteudo_gerado_related = None
p.save()
return models.Model.delete( return models.Model.delete(
self, using=using, keep_parents=keep_parents) self, using=using, keep_parents=keep_parents)
@ -281,7 +308,7 @@ class Autoria(models.Model):
@reversion.register() @reversion.register()
class AcompanhamentoMateria(models.Model): class AcompanhamentoMateria(models.Model):
usuario = models.CharField(max_length=50) usuario = models.CharField(max_length=50)
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.PROTECT) materia = models.ForeignKey(MateriaLegislativa)
email = models.EmailField( email = models.EmailField(
max_length=100, verbose_name=_('E-mail')) max_length=100, verbose_name=_('E-mail'))
data_cadastro = models.DateField(auto_now_add=True) data_cadastro = models.DateField(auto_now_add=True)
@ -392,10 +419,18 @@ class DocumentoAcessorio(models.Model):
arquivo = models.FileField( arquivo = models.FileField(
blank=True, blank=True,
null=True, null=True,
upload_to=texto_upload_path, upload_to=anexo_upload_path,
verbose_name=_('Texto Integral'), verbose_name=_('Texto Integral'),
validators=[restringe_tipos_de_arquivo_txt]) validators=[restringe_tipos_de_arquivo_txt])
proposicao = GenericRelation(
'Proposicao', related_query_name='proposicao')
data_ultima_atualizacao = models.DateTimeField(
blank=True, null=True,
auto_now=True,
verbose_name=_('Data'))
class Meta: class Meta:
verbose_name = _('Documento Acessório') verbose_name = _('Documento Acessório')
verbose_name_plural = _('Documentos Acessórios') verbose_name_plural = _('Documentos Acessórios')
@ -411,6 +446,10 @@ class DocumentoAcessorio(models.Model):
if self.arquivo: if self.arquivo:
self.arquivo.delete() self.arquivo.delete()
for p in self.proposicao.all():
p.conteudo_gerado_related = None
p.save()
return models.Model.delete( return models.Model.delete(
self, using=using, keep_parents=keep_parents) self, using=using, keep_parents=keep_parents)
@ -625,7 +664,7 @@ class Proposicao(models.Model):
('I', 'Incorporada')), ('I', 'Incorporada')),
verbose_name=_('Status Proposição')) verbose_name=_('Status Proposição'))
texto_original = models.FileField( texto_original = models.FileField(
upload_to=texto_upload_path, upload_to=materia_upload_path,
blank=True, blank=True,
null=True, null=True,
verbose_name=_('Texto Original'), verbose_name=_('Texto Original'),
@ -834,4 +873,4 @@ class Tramitacao(models.Model):
return _('%(materia)s | %(status)s | %(data)s') % { return _('%(materia)s | %(status)s | %(data)s') % {
'materia': self.materia, 'materia': self.materia,
'status': self.status, 'status': self.status,
'data': self.data_tramitacao} 'data': self.data_tramitacao.strftime("%d/%m/%Y")}

19
sapl/materia/receivers.py

@ -0,0 +1,19 @@
from django.dispatch import receiver
from sapl.materia.signals import tramitacao_signal
from sapl.utils import get_base_url
from .email_utils import do_envia_email_tramitacao
@receiver(tramitacao_signal)
def handle_tramitacao_signal(sender, **kwargs):
tramitacao = kwargs.get("post")
request = kwargs.get("request")
materia = tramitacao.materia
do_envia_email_tramitacao(
get_base_url(request),
materia,
tramitacao.status,
tramitacao.unidade_tramitacao_destino)

8
sapl/materia/signals.py

@ -1,10 +1,8 @@
from django.db.models.signals import post_delete, post_save from django.db.models.signals import post_delete, post_save
from sapl.utils import save_texto, delete_texto
import django.dispatch
from .models import DocumentoAcessorio, MateriaLegislativa from .models import DocumentoAcessorio, MateriaLegislativa
post_save.connect(save_texto, sender=MateriaLegislativa) tramitacao_signal = django.dispatch.Signal(providing_args=['post', 'request'])
post_save.connect(save_texto, sender=DocumentoAcessorio)
post_delete.connect(delete_texto, sender=MateriaLegislativa)
post_delete.connect(delete_texto, sender=DocumentoAcessorio)

2
sapl/materia/tests/test_email_templates.py

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

2
sapl/materia/urls.py

@ -25,6 +25,8 @@ from sapl.materia.views import (AcompanhamentoConfirmarView,
from .apps import AppConfig from .apps import AppConfig
from . import receivers
app_name = AppConfig.name app_name = AppConfig.name
urlpatterns_materia = [ urlpatterns_materia = [

528
sapl/materia/views.py

@ -1,28 +1,47 @@
from datetime import datetime from datetime import datetime, date
from random import choice from random import choice
from string import ascii_letters, digits from string import ascii_letters, digits
from .email_utils import do_envia_email_confirmacao
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
AdicionarVariasAutoriasFilterSet, DespachoInicialForm,
DocumentoAcessorioForm, MateriaAssuntoForm,
MateriaLegislativaFilterSet, MateriaSimplificadaForm,
PrimeiraTramitacaoEmLoteFilterSet, ReceberProposicaoForm,
RelatoriaForm, TramitacaoEmLoteFilterSet,
filtra_tramitacao_destino,
filtra_tramitacao_destino_and_status,
filtra_tramitacao_status)
from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria,
DespachoInicial, DocumentoAcessorio, MateriaAssunto,
MateriaLegislativa, Numeracao, Orgao, Origem, Proposicao,
RegimeTramitacao, Relatoria, StatusTramitacao,
TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa,
TipoProposicao, Tramitacao, UnidadeTramitacao)
from .signals import tramitacao_signal
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML from crispy_forms.layout import HTML
from django import forms
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist from django.contrib.contenttypes.models import ContentType
from django.core.mail import send_mail from django.core.exceptions import (ObjectDoesNotExist,
MultipleObjectsReturned)
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import Q
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from django.http.response import Http404, HttpResponseRedirect from django.http.response import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.template import Context, loader
from django.utils import formats from django.utils import formats
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic import CreateView, ListView, TemplateView, UpdateView
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django_filters.views import FilterView from django_filters.views import FilterView
import sapl import sapl
from sapl.base.models import Autor, CasaLegislativa from sapl.base.models import Autor, CasaLegislativa
from sapl.comissoes.models import Comissao
from sapl.comissoes.models import Comissao, Participacao from sapl.comissoes.models import Comissao, Participacao
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT, from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT,
STATUS_TA_PRIVATE) STATUS_TA_PRIVATE)
@ -33,29 +52,18 @@ from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL,
Crud, CrudAux, MasterDetailCrud, Crud, CrudAux, MasterDetailCrud,
PermissionRequiredForAppCrudMixin, make_pagination) PermissionRequiredForAppCrudMixin, make_pagination)
from sapl.materia.forms import (AnexadaForm, ConfirmarProposicaoForm, from sapl.materia.forms import (AnexadaForm, ConfirmarProposicaoForm,
LegislacaoCitadaForm, ProposicaoForm, LegislacaoCitadaForm, AutoriaForm, ProposicaoForm,
TipoProposicaoForm) TipoProposicaoForm, TramitacaoForm,
TramitacaoUpdateForm)
from sapl.materia.models import Autor
from sapl.norma.models import LegislacaoCitada from sapl.norma.models import LegislacaoCitada
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import Protocolo from sapl.protocoloadm.models import Protocolo
from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label,
autor_modal, gerar_hash_arquivo, get_base_url, autor_modal, gerar_hash_arquivo, get_base_url,
montar_row_autor) montar_row_autor)
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
AdicionarVariasAutoriasFilterSet, DespachoInicialForm,
DocumentoAcessorioForm, MateriaAssuntoForm,
MateriaLegislativaFilterSet, MateriaSimplificadaForm,
PrimeiraTramitacaoEmLoteFilterSet, ReceberProposicaoForm,
RelatoriaForm, TramitacaoEmLoteFilterSet,
filtra_tramitacao_destino,
filtra_tramitacao_destino_and_status,
filtra_tramitacao_status)
from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria,
DespachoInicial, DocumentoAcessorio, MateriaAssunto,
MateriaLegislativa, Numeracao, Orgao, Origem, Proposicao,
RegimeTramitacao, Relatoria, StatusTramitacao,
TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa,
TipoProposicao, Tramitacao, UnidadeTramitacao)
AssuntoMateriaCrud = Crud.build(AssuntoMateria, 'assunto_materia') AssuntoMateriaCrud = Crud.build(AssuntoMateria, 'assunto_materia')
@ -84,8 +92,8 @@ def proposicao_texto(request, pk):
proposicao = Proposicao.objects.get(pk=pk) proposicao = Proposicao.objects.get(pk=pk)
if proposicao.texto_original: if proposicao.texto_original:
if not proposicao.data_recebimento: if (not proposicao.data_recebimento and
if proposicao.autor.user_id != request.user.id: proposicao.autor.user_id != request.user.id):
raise Http404 raise Http404
arquivo = proposicao.texto_original arquivo = proposicao.texto_original
@ -162,19 +170,43 @@ class CriarProtocoloMateriaView(CreateView):
context = super( context = super(
CriarProtocoloMateriaView, self).get_context_data(**kwargs) CriarProtocoloMateriaView, self).get_context_data(**kwargs)
try:
protocolo = Protocolo.objects.get(pk=self.kwargs['pk']) protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
except ObjectDoesNotExist:
raise Http404()
materias_ano = MateriaLegislativa.objects.filter(
ano=protocolo.ano,
tipo=protocolo.tipo_materia).order_by('-numero')
if materias_ano:
numero = materias_ano.first().numero + 1
else:
numero = 1
context['form'].fields['tipo'].initial = protocolo.tipo_materia context['form'].fields['tipo'].initial = protocolo.tipo_materia
context['form'].fields['numero'].initial = protocolo.numero context['form'].fields['numero'].initial = numero
context['form'].fields['ano'].initial = protocolo.ano context['form'].fields['ano'].initial = protocolo.ano
context['form'].fields['data_apresentacao'].initial = protocolo.data context['form'].fields['data_apresentacao'].initial = protocolo.data
context['form'].fields['numero_protocolo'].initial = protocolo.numero context['form'].fields['numero_protocolo'].initial = protocolo.numero
context['form'].fields['ementa'].initial = protocolo.observacao context['form'].fields['ementa'].initial = protocolo.assunto_ementa
return context return context
def form_valid(self, form): def form_valid(self, form):
materia = form.save() materia = form.save()
try:
protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
except ObjectDoesNotExist:
raise Http404()
if protocolo.autor:
Autoria.objects.create(
materia=materia,
autor=protocolo.autor,
primeiro_autor=True)
return redirect(self.get_success_url(materia)) return redirect(self.get_success_url(materia))
@ -524,7 +556,7 @@ class ProposicaoCrud(Crud):
class BaseMixin(Crud.BaseMixin): class BaseMixin(Crud.BaseMixin):
list_field_names = ['data_envio', 'data_recebimento', 'descricao', list_field_names = ['data_envio', 'data_recebimento', 'descricao',
'tipo'] 'tipo', 'conteudo_gerado_related']
class BaseLocalMixin: class BaseLocalMixin:
form_class = ProposicaoForm form_class = ProposicaoForm
@ -826,6 +858,7 @@ class RelatoriaCrud(MasterDetailCrud):
composicao=composicao) composicao=composicao)
parlamentares = [] parlamentares = []
parlamentares.append(['', '---------'])
for p in participacao: for p in participacao:
if p.titular: if p.titular:
parlamentares.append( parlamentares.append(
@ -854,6 +887,28 @@ class RelatoriaCrud(MasterDetailCrud):
class UpdateView(MasterDetailCrud.UpdateView): class UpdateView(MasterDetailCrud.UpdateView):
form_class = RelatoriaForm form_class = RelatoriaForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
comissao = Comissao.objects.get(
pk=context['form'].initial['comissao'])
except ObjectDoesNotExist:
pass
else:
composicao = comissao.composicao_set.last()
participacao = Participacao.objects.filter(
composicao=composicao)
parlamentares = []
for p in participacao:
if p.titular:
parlamentares.append(
[p.parlamentar.id, p.parlamentar.nome_parlamentar])
context['form'].fields['parlamentar'].choices = parlamentares
return context
class TramitacaoCrud(MasterDetailCrud): class TramitacaoCrud(MasterDetailCrud):
model = Tramitacao model = Tramitacao
@ -868,33 +923,77 @@ class TramitacaoCrud(MasterDetailCrud):
ordering = '-data_tramitacao', ordering = '-data_tramitacao',
class CreateView(MasterDetailCrud.CreateView): class CreateView(MasterDetailCrud.CreateView):
form_class = TramitacaoForm
def get_success_url(self):
return reverse('sapl.materia:tramitacao_list', kwargs={
'pk': self.kwargs['pk']})
def get_initial(self): def get_initial(self):
local = MateriaLegislativa.objects.get( local = MateriaLegislativa.objects.get(
pk=self.kwargs['pk']).tramitacao_set.last() pk=self.kwargs['pk']).tramitacao_set.order_by(
'-data_tramitacao').first()
if local: if local:
self.initial['unidade_tramitacao_local' self.initial['unidade_tramitacao_local'
] = local.unidade_tramitacao_destino.pk ] = local.unidade_tramitacao_destino.pk
else:
self.initial['unidade_tramitacao_local'] = ''
self.initial['data_tramitacao'] = datetime.now() self.initial['data_tramitacao'] = datetime.now()
return self.initial return self.initial
def post(self, request, *args, **kwargs): def get_context_data(self, **kwargs):
materia = MateriaLegislativa.objects.get(id=kwargs['pk']) context = super().get_context_data(**kwargs)
do_envia_email_tramitacao(request, materia)
return super(CreateView, self).post(request, *args, **kwargs)
class UpdateView(MasterDetailCrud.UpdateView): primeira_tramitacao = not(Tramitacao.objects.filter(
materia_id=int(kwargs['root_pk'])).exists())
def post(self, request, *args, **kwargs): # Se não for a primeira tramitação daquela matéria, o campo
materia = MateriaLegislativa.objects.get( # não pode ser modificado
tramitacao__id=kwargs['pk']) if not primeira_tramitacao:
do_envia_email_tramitacao(request, materia) context['form'].fields[
return super(UpdateView, self).post(request, *args, **kwargs) 'unidade_tramitacao_local'].widget.attrs['disabled'] = True
return context
def form_valid(self, form):
self.object = form.save()
try:
tramitacao_signal.send(sender=Tramitacao,
post=self.object,
request=self.request)
except Exception:
# 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.')
messages.add_message(self.request, messages.ERROR, msg)
return HttpResponseRedirect(self.get_success_url())
return super().form_valid(form)
class UpdateView(MasterDetailCrud.UpdateView):
form_class = TramitacaoUpdateForm
@property @property
def layout_key(self): def layout_key(self):
return 'TramitacaoUpdate' return 'TramitacaoUpdate'
def form_valid(self, form):
self.object = form.save()
try:
tramitacao_signal.send(sender=Tramitacao,
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.')
messages.add_message(self.request, messages.ERROR, msg)
return HttpResponseRedirect(self.get_success_url())
return super().form_valid(form)
class ListView(MasterDetailCrud.ListView): class ListView(MasterDetailCrud.ListView):
def get_queryset(self): def get_queryset(self):
@ -951,25 +1050,21 @@ class DocumentoAcessorioCrud(MasterDetailCrud):
form_class = DocumentoAcessorioForm form_class = DocumentoAcessorioForm
def __init__(self, **kwargs): def __init__(self, **kwargs):
montar_helper_documento_acessorio(self)
super(MasterDetailCrud.CreateView, self).__init__(**kwargs) super(MasterDetailCrud.CreateView, self).__init__(**kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super( context = super(
MasterDetailCrud.CreateView, self).get_context_data(**kwargs) MasterDetailCrud.CreateView, self).get_context_data(**kwargs)
context['helper'] = self.helper
return context return context
class UpdateView(MasterDetailCrud.UpdateView): class UpdateView(MasterDetailCrud.UpdateView):
form_class = DocumentoAcessorioForm form_class = DocumentoAcessorioForm
def __init__(self, **kwargs): def __init__(self, **kwargs):
montar_helper_documento_acessorio(self)
super(MasterDetailCrud.UpdateView, self).__init__(**kwargs) super(MasterDetailCrud.UpdateView, self).__init__(**kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs) context = super(UpdateView, self).get_context_data(**kwargs)
context['helper'] = self.helper
return context return context
@ -979,6 +1074,32 @@ class AutoriaCrud(MasterDetailCrud):
help_path = '' help_path = ''
public = [RP_LIST, RP_DETAIL] public = [RP_LIST, RP_DETAIL]
class CreateView(MasterDetailCrud.CreateView):
form_class = AutoriaForm
def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs)
autores_ativos = self.autores_ativos()
autores = []
for a in autores_ativos:
autores.append([a.id, a.__str__()])
context['form'].fields['autor'].choices = autores
return context
def autores_ativos(self):
lista_parlamentares = Parlamentar.objects.filter(ativo=True).values_list('id', flat=True)
model_parlamentar = ContentType.objects.get_for_model(Parlamentar)
autor_parlamentar = Autor.objects.filter(content_type=model_parlamentar, object_id__in=lista_parlamentares)
lista_comissoes = Comissao.objects.filter(Q(data_extincao__isnull=True)|Q(data_extincao__gt=date.today())).values_list('id', flat=True)
model_comissao = ContentType.objects.get_for_model(Comissao)
autor_comissoes = Autor.objects.filter(content_type=model_comissao, object_id__in=lista_comissoes)
autores_outros = Autor.objects.exclude(content_type__in=[model_parlamentar, model_comissao])
q = autor_parlamentar | autor_comissoes | autores_outros
return q
class DespachoInicialCrud(MasterDetailCrud): class DespachoInicialCrud(MasterDetailCrud):
model = DespachoInicial model = DespachoInicial
@ -1222,25 +1343,41 @@ class DocumentoAcessorioView(PermissionRequiredMixin, CreateView):
class AcompanhamentoConfirmarView(TemplateView): class AcompanhamentoConfirmarView(TemplateView):
def get_redirect_url(self): def get_redirect_url(self, email):
return reverse('sapl.sessao:list_pauta_sessao') msg = _('Esta matéria está sendo acompanhada pelo e-mail: %s') % (
email)
messages.add_message(self.request, messages.SUCCESS, msg)
return reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': self.kwargs['pk']})
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
materia_id = kwargs['pk'] materia_id = kwargs['pk']
hash_txt = request.GET.get('hash_txt', '') hash_txt = request.GET.get('hash_txt', '')
acompanhar = AcompanhamentoMateria.objects.get(materia_id=materia_id, try:
acompanhar = AcompanhamentoMateria.objects.get(
materia_id=materia_id,
hash=hash_txt) hash=hash_txt)
except ObjectDoesNotExist:
raise Http404()
# except MultipleObjectsReturned:
# A melhor solução deve ser permitir que a exceção
# (MultipleObjectsReturned) seja lançada e vá para o log,
# pois só poderá ser causada por um erro de desenvolvimente
acompanhar.confirmado = True acompanhar.confirmado = True
acompanhar.save() acompanhar.save()
return HttpResponseRedirect(self.get_redirect_url()) return HttpResponseRedirect(self.get_redirect_url(acompanhar.email))
class AcompanhamentoExcluirView(TemplateView): class AcompanhamentoExcluirView(TemplateView):
def get_redirect_url(self): def get_success_url(self):
return reverse('sapl.sessao:list_pauta_sessao') msg = _('Você parou de acompanhar esta matéria.')
messages.add_message(self.request, messages.INFO, msg)
return reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': self.kwargs['pk']})
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
materia_id = kwargs['pk'] materia_id = kwargs['pk']
@ -1252,7 +1389,7 @@ class AcompanhamentoExcluirView(TemplateView):
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
return HttpResponseRedirect(self.get_redirect_url()) return HttpResponseRedirect(self.get_success_url())
class MateriaLegislativaPesquisaView(FilterView): class MateriaLegislativaPesquisaView(FilterView):
@ -1270,7 +1407,7 @@ class MateriaLegislativaPesquisaView(FilterView):
unidade_destino = self.request.GET.get( unidade_destino = self.request.GET.get(
'tramitacao__unidade_tramitacao_destino') 'tramitacao__unidade_tramitacao_destino')
qs = self.get_queryset() qs = self.get_queryset().distinct()
if status_tramitacao and unidade_destino: if status_tramitacao and unidade_destino:
lista = filtra_tramitacao_destino_and_status(status_tramitacao, lista = filtra_tramitacao_destino_and_status(status_tramitacao,
@ -1337,237 +1474,62 @@ class AcompanhamentoMateriaView(CreateView):
materia = MateriaLegislativa.objects.get(id=pk) materia = MateriaLegislativa.objects.get(id=pk)
if form.is_valid(): if form.is_valid():
email = form.cleaned_data['email'] email = form.cleaned_data['email']
usuario = request.user usuario = request.user
hash_txt = self.get_random_chars() hash_txt = self.get_random_chars()
try: acompanhar = AcompanhamentoMateria.objects.get_or_create(
AcompanhamentoMateria.objects.get(
email=email,
materia=materia, materia=materia,
hash=hash_txt) email=form.data['email'])
except ObjectDoesNotExist:
acompanhar = form.save(commit=False) # Se o segundo elemento do retorno do get_or_create for True
# quer dizer que o elemento não existia
if acompanhar[1]:
acompanhar = acompanhar[0]
acompanhar.hash = hash_txt acompanhar.hash = hash_txt
acompanhar.materia = materia
acompanhar.usuario = usuario.username acompanhar.usuario = usuario.username
acompanhar.confirmado = False acompanhar.confirmado = False
acompanhar.save() acompanhar.save()
do_envia_email_confirmacao(request, materia, email) base_url = get_base_url(request)
destinatario = AcompanhamentoMateria.objects.get(
materia=materia,
email=email,
confirmado=False)
casa = CasaLegislativa.objects.first()
do_envia_email_confirmacao(base_url,
casa,
materia,
destinatario)
msg = _('Foi enviado um e-mail de confirmação. Confira sua caixa \
de mensagens e clique no link que nós enviamos para \
confirmar o acompanhamento desta matéria.')
messages.add_message(request, messages.SUCCESS, msg)
# Caso esse Acompanhamento já exista
# avisa ao usuário que essa matéria já está sendo acompanhada
else: else:
msg = _('Este e-mail já está acompanhando essa matéria.')
messages.add_message(request, messages.INFO, msg)
return self.render_to_response( return self.render_to_response(
{'form': form, {'form': form,
'materia': materia, 'materia': materia,
'error': _('Essa matéria já está\ 'error': _('Essa matéria já está\
sendo acompanhada por este e-mail.')}) sendo acompanhada por este e-mail.')})
return self.form_valid(form) return HttpResponseRedirect(self.get_success_url())
else: else:
return self.render_to_response( return self.render_to_response(
{'form': form, {'form': form,
'materia': materia}) 'materia': materia})
def get_success_url(self): def get_success_url(self):
return reverse('sapl.sessao:list_pauta_sessao') return reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': self.kwargs['pk']})
def load_email_templates(templates, context={}):
emails = []
for t in templates:
tpl = loader.get_template(t)
email = tpl.render(Context(context))
if t.endswith(".html"):
email = email.replace('\n', '').replace('\r', '')
emails.append(email)
return emails
def criar_email_confirmacao(request, casa_legislativa, materia, hash_txt=''):
if not casa_legislativa:
raise ValueError("Casa Legislativa é obrigatória")
if not materia:
raise ValueError("Matéria é obrigatória")
# FIXME i18n
casa_nome = (casa_legislativa.nome + ' de ' +
casa_legislativa.municipio + '-' +
casa_legislativa.uf)
base_url = get_base_url(request)
materia_url = reverse('sapl.materia:acompanhar_materia',
kwargs={'pk': materia.id})
confirmacao_url = reverse('sapl.materia:acompanhar_confirmar',
kwargs={'pk': materia.id})
autores = []
for autoria in materia.autoria_set.all():
autores.append(autoria.autor.nome)
templates = load_email_templates(['email/acompanhar.txt',
'email/acompanhar.html'],
{"casa_legislativa": casa_nome,
"logotipo": casa_legislativa.logotipo,
"descricao_materia": materia.ementa,
"autoria": autores,
"hash_txt": hash_txt,
"base_url": base_url,
"materia": str(materia),
"materia_url": materia_url,
"confirmacao_url": confirmacao_url, })
return templates
def criar_email_tramitacao(request, casa_legislativa, materia, hash_txt=''):
if not casa_legislativa:
raise ValueError("Casa Legislativa é obrigatória")
if not materia:
raise ValueError("Matéria é obrigatória")
# FIXME i18n
casa_nome = (casa_legislativa.nome + ' de ' +
casa_legislativa.municipio + '-' +
casa_legislativa.uf)
base_url = get_base_url(request)
url_materia = reverse('sapl.materia:acompanhar_materia',
kwargs={'pk': materia.id})
url_excluir = reverse('sapl.materia:acompanhar_excluir',
kwargs={'pk': materia.id})
autores = []
for autoria in materia.autoria_set.all():
autores.append(autoria.autor.nome)
templates = load_email_templates(['email/tramitacao.txt',
'email/tramitacao.html'],
{"casa_legislativa": casa_nome,
"data_registro": datetime.now().strftime(
"%d/%m/%Y"),
"cod_materia": materia.id,
"logotipo": casa_legislativa.logotipo,
"descricao_materia": materia.ementa,
"autoria": autores,
"data": materia.tramitacao_set.last(
).data_tramitacao,
"status": materia.tramitacao_set.last(
).status,
"texto_acao":
materia.tramitacao_set.last().texto,
"hash_txt": hash_txt,
"materia": str(materia),
"base_url": base_url,
"materia_url": url_materia,
"excluir_url": url_excluir})
return templates
def enviar_emails(sender, recipients, messages):
'''
Recipients is a string list of email addresses
Messages is an array of dicts of the form:
{'recipient': 'address', # useless????
'subject': 'subject text',
'txt_message': 'text message',
'html_message': 'html message'
}
'''
if len(messages) == 1:
# sends an email simultaneously to all recipients
send_mail(messages[0]['subject'],
messages[0]['txt_message'],
sender,
recipients,
html_message=messages[0]['html_message'],
fail_silently=False)
elif len(recipients) > len(messages):
raise ValueError("Message list should have size 1 \
or equal recipient list size. \
recipients: %s, messages: %s" % (recipients, messages)
)
else:
# sends an email simultaneously to all reciepients
for (d, m) in zip(recipients, messages):
send_mail(m['subject'],
m['txt_message'],
sender,
[d],
html_message=m['html_message'],
fail_silently=False)
return None
def do_envia_email_confirmacao(request, materia, email):
#
# Envia email de confirmacao para atualizações de tramitação
#
destinatario = AcompanhamentoMateria.objects.get(materia=materia,
email=email,
confirmado=False)
casa = CasaLegislativa.objects.first()
sender = 'sapl-test@interlegis.leg.br'
# FIXME i18n
subject = "[SAPL] " + str(materia) + " - Ative o Acompanhamento da Materia"
messages = []
recipients = []
email_texts = criar_email_confirmacao(request,
casa,
materia,
destinatario.hash,)
recipients.append(destinatario.email)
messages.append({
'recipient': destinatario.email,
'subject': subject,
'txt_message': email_texts[0],
'html_message': email_texts[1]
})
enviar_emails(sender, recipients, messages)
return None
def do_envia_email_tramitacao(request, materia):
#
# Envia email de tramitacao para usuarios cadastrados
#
destinatarios = AcompanhamentoMateria.objects.filter(materia=materia,
confirmado=True)
casa = CasaLegislativa.objects.first()
sender = 'sapl-test@interlegis.leg.br'
# FIXME i18n
subject = "[SAPL] " + str(materia) + \
" - Acompanhamento de Materia Legislativa"
messages = []
recipients = []
for destinatario in destinatarios:
email_texts = criar_email_tramitacao(request,
casa,
materia,
destinatario.hash,)
recipients.append(destinatario.email)
messages.append({
'recipient': destinatario.email,
'subject': subject,
'txt_message': email_texts[0],
'html_message': email_texts[1]
})
enviar_emails(sender, recipients, messages)
return None
class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView): class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
@ -1586,6 +1548,8 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
qr = self.request.GET.copy() qr = self.request.GET.copy()
context['tipos_docs'] = TipoDocumento.objects.all() context['tipos_docs'] = TipoDocumento.objects.all()
context['object_list'] = context['object_list'].order_by(
'ano', 'numero')
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
return context return context
@ -1600,15 +1564,15 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
tipo = TipoDocumento.objects.get(descricao=request.POST['tipo']) tipo = TipoDocumento.objects.get(descricao=request.POST['tipo'])
for materia_id in marcadas: for materia_id in marcadas:
DocumentoAcessorio.objects.create( doc = DocumentoAcessorio()
materia_id=materia_id, doc.materia_id = materia_id
tipo=tipo, doc.tipo = tipo
arquivo=request.POST['arquivo'], doc.arquivo = request.FILES['arquivo']
nome=request.POST['nome'], doc.nome = request.POST['nome']
data=datetime.strptime(request.POST['data'], "%d/%m/%Y"), doc.data = datetime.strptime(request.POST['data'], "%d/%m/%Y")
autor=Autor.objects.get(id=request.POST['autor']), doc.autor = request.POST['autor']
ementa=request.POST['ementa'] doc.ementa = request.POST['ementa']
) doc.save()
msg = _('Documento(s) criado(s).') msg = _('Documento(s) criado(s).')
messages.add_message(request, messages.SUCCESS, msg) messages.add_message(request, messages.SUCCESS, msg)
return self.get(request, self.kwargs) return self.get(request, self.kwargs)
@ -1638,6 +1602,8 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
context['urgente_tramitacao'] = YES_NO_CHOICES context['urgente_tramitacao'] = YES_NO_CHOICES
context['unidade_local'] = UnidadeTramitacao.objects.all() context['unidade_local'] = UnidadeTramitacao.objects.all()
context['primeira_tramitacao'] = True
# Pega somente matéria que não possuem tramitação # Pega somente matéria que não possuem tramitação
if (type(self.__dict__['filterset']).__name__ == if (type(self.__dict__['filterset']).__name__ ==
'PrimeiraTramitacaoEmLoteFilterSet'): 'PrimeiraTramitacaoEmLoteFilterSet'):
@ -1674,8 +1640,12 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
else: else:
data_fim_prazo = None data_fim_prazo = None
# issue https://github.com/interlegis/sapl/issues/1123
# TODO: usar Form
urgente = request.POST['urgente'] == 'True'
for materia_id in marcadas: for materia_id in marcadas:
Tramitacao.objects.create( t = Tramitacao(
materia_id=materia_id, materia_id=materia_id,
data_tramitacao=datetime.strptime( data_tramitacao=datetime.strptime(
request.POST['data_tramitacao'], "%d/%m/%Y"), request.POST['data_tramitacao'], "%d/%m/%Y"),
@ -1685,11 +1655,12 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
'unidade_tramitacao_local'], 'unidade_tramitacao_local'],
unidade_tramitacao_destino_id=request.POST[ unidade_tramitacao_destino_id=request.POST[
'unidade_tramitacao_destino'], 'unidade_tramitacao_destino'],
urgente=request.POST['urgente'], urgente=urgente,
status_id=request.POST['status'], status_id=request.POST['status'],
turno=request.POST['turno'], turno=request.POST['turno'],
texto=request.POST['texto'] texto=request.POST['texto']
) )
t.save()
msg = _('Tramitação completa.') msg = _('Tramitação completa.')
messages.add_message(request, messages.SUCCESS, msg) messages.add_message(request, messages.SUCCESS, msg)
return self.get(request, self.kwargs) return self.get(request, self.kwargs)
@ -1697,3 +1668,24 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
class TramitacaoEmLoteView(PrimeiraTramitacaoEmLoteView): class TramitacaoEmLoteView(PrimeiraTramitacaoEmLoteView):
filterset_class = TramitacaoEmLoteFilterSet filterset_class = TramitacaoEmLoteFilterSet
def get_context_data(self, **kwargs):
context = super(TramitacaoEmLoteView,
self).get_context_data(**kwargs)
qr = self.request.GET.copy()
context['primeira_tramitacao'] = False
if ('tramitacao__status' in qr and
'tramitacao__unidade_tramitacao_destino' in qr and
qr['tramitacao__status'] and
qr['tramitacao__unidade_tramitacao_destino']
):
lista = filtra_tramitacao_destino_and_status(
qr['tramitacao__status'],
qr['tramitacao__unidade_tramitacao_destino'])
context['object_list'] = context['object_list'].filter(
id__in=lista).distinct()
return context

10
sapl/norma/forms.py

@ -47,7 +47,7 @@ class NormaFilterSet(django_filters.FilterSet):
}} }}
ano = django_filters.ChoiceFilter(required=False, ano = django_filters.ChoiceFilter(required=False,
label=u'Ano', label='Ano',
choices=ANO_CHOICES) choices=ANO_CHOICES)
ementa = django_filters.CharFilter(lookup_expr='icontains') ementa = django_filters.CharFilter(lookup_expr='icontains')
@ -118,6 +118,8 @@ class NormaJuridicaForm(ModelForm):
widgets = {'assuntos': widgets.CheckboxSelectMultiple} widgets = {'assuntos': widgets.CheckboxSelectMultiple}
def clean(self): def clean(self):
super(NormaJuridicaForm, self).clean()
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
if (cleaned_data['tipo_materia'] and if (cleaned_data['tipo_materia'] and
@ -142,7 +144,9 @@ class NormaJuridicaForm(ModelForm):
texto_integral = self.cleaned_data.get('texto_integral', False) texto_integral = self.cleaned_data.get('texto_integral', False)
if texto_integral: if texto_integral:
if texto_integral.size > MAX_DOC_UPLOAD_SIZE: if texto_integral.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("Arquivo muito grande. ( > 5mb )") max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))
return texto_integral return texto_integral
def save(self, commit=False): def save(self, commit=False):
@ -175,6 +179,8 @@ class NormaRelacionadaForm(ModelForm):
super(NormaRelacionadaForm, self).__init__(*args, **kwargs) super(NormaRelacionadaForm, self).__init__(*args, **kwargs)
def clean(self): def clean(self):
super(NormaRelacionadaForm, self).clean()
if self.errors: if self.errors:
return self.errors return self.errors
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data

22
sapl/norma/migrations/0004_auto_20170522_1051.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-05-22 10:51
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.norma.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('norma', '0003_auto_20170510_1549'),
]
operations = [
migrations.AlterField(
model_name='normajuridica',
name='texto_integral',
field=models.FileField(blank=True, null=True, upload_to=sapl.norma.models.norma_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'),
),
]

27
sapl/norma/migrations/0004_auto_20170522_1115.py

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.12 on 2017-05-22 11:15
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.norma.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('norma', '0003_auto_20170510_1549'),
]
operations = [
migrations.AlterField(
model_name='normajuridica',
name='texto_integral',
field=models.FileField(blank=True, null=True, upload_to=sapl.norma.models.norma_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'),
),
migrations.AlterField(
model_name='normajuridica',
name='timestamp',
field=models.DateTimeField(null=True),
),
]

16
sapl/norma/migrations/0005_merge.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-05-23 18:20
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('norma', '0004_auto_20170522_1115'),
('norma', '0004_auto_20170522_1051'),
]
operations = [
]

20
sapl/norma/migrations/0006_normajuridica_data_ultima_atualizacao.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2017-07-12 09:51
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0005_merge'),
]
operations = [
migrations.AddField(
model_name='normajuridica',
name='data_ultima_atualizacao',
field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data'),
),
]

12
sapl/norma/models.py

@ -59,6 +59,8 @@ class TipoNormaJuridica(models.Model):
def __str__(self): def __str__(self):
return self.descricao return self.descricao
def norma_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath=instance.ano)
@reversion.register() @reversion.register()
class NormaJuridica(models.Model): class NormaJuridica(models.Model):
@ -67,10 +69,11 @@ class NormaJuridica(models.Model):
('F', 'federal', _('Federal')), ('F', 'federal', _('Federal')),
('M', 'municipal', _('Municipal')), ('M', 'municipal', _('Municipal')),
) )
texto_integral = models.FileField( texto_integral = models.FileField(
blank=True, blank=True,
null=True, null=True,
upload_to=texto_upload_path, upload_to=norma_upload_path,
verbose_name=_('Texto Integral'), verbose_name=_('Texto Integral'),
validators=[restringe_tipos_de_arquivo_txt]) validators=[restringe_tipos_de_arquivo_txt])
tipo = models.ForeignKey( tipo = models.ForeignKey(
@ -113,11 +116,16 @@ class NormaJuridica(models.Model):
AssuntoNorma, blank=True, AssuntoNorma, blank=True,
verbose_name=_('Assuntos')) verbose_name=_('Assuntos'))
data_vigencia = models.DateField(blank=True, null=True) data_vigencia = models.DateField(blank=True, null=True)
timestamp = models.DateTimeField() timestamp = models.DateTimeField(null=True)
texto_articulado = GenericRelation( texto_articulado = GenericRelation(
TextoArticulado, related_query_name='texto_articulado') TextoArticulado, related_query_name='texto_articulado')
data_ultima_atualizacao = models.DateTimeField(
blank=True, null=True,
auto_now=True,
verbose_name=_('Data'))
class Meta: class Meta:
verbose_name = _('Norma Jurídica') verbose_name = _('Norma Jurídica')
verbose_name_plural = _('Normas Jurídicas') verbose_name_plural = _('Normas Jurídicas')

5
sapl/norma/signals.py

@ -1,8 +1,3 @@
from django.db.models.signals import post_delete, post_save from django.db.models.signals import post_delete, post_save
from sapl.utils import save_texto, delete_texto
from .models import NormaJuridica from .models import NormaJuridica
post_save.connect(save_texto, sender=NormaJuridica)
post_delete.connect(delete_texto, sender=NormaJuridica)

10
sapl/painel/urls.py

@ -1,8 +1,8 @@
from django.conf.urls import url from django.conf.urls import url
from .apps import AppConfig from .apps import AppConfig
from .views import (controlador_painel, cronometro_painel, get_dados_painel, from .views import (cronometro_painel, get_dados_painel, painel_mensagem_view,
painel_mensagem_view, painel_parlamentar_view, painel_view, painel_parlamentar_view, painel_view,
painel_votacao_view, votante_view) painel_votacao_view, votante_view)
app_name = AppConfig.name app_name = AppConfig.name
@ -11,8 +11,6 @@ urlpatterns = [
url(r'^painel-principal/(?P<pk>\d+)$', painel_view, url(r'^painel-principal/(?P<pk>\d+)$', painel_view,
name="painel_principal"), name="painel_principal"),
url(r'^painel/(?P<pk>\d+)/dados$', get_dados_painel, name='dados_painel'), url(r'^painel/(?P<pk>\d+)/dados$', get_dados_painel, name='dados_painel'),
url(r'^painel/controlador$',
controlador_painel, name='painel_controlador'),
url(r'^painel/mensagem$', painel_mensagem_view, name="painel_mensagem"), url(r'^painel/mensagem$', painel_mensagem_view, name="painel_mensagem"),
url(r'^painel/parlamentar$', painel_parlamentar_view, url(r'^painel/parlamentar$', painel_parlamentar_view,
name='painel_parlamentar'), name='painel_parlamentar'),
@ -20,6 +18,6 @@ urlpatterns = [
url(r'^painel/cronometro$', cronometro_painel, name='cronometro_painel'), url(r'^painel/cronometro$', cronometro_painel, name='cronometro_painel'),
# url(r'^painel/cronometro$', include(CronometroPainelCrud.get_urls())), # url(r'^painel/cronometro$', include(CronometroPainelCrud.get_urls())),
url(r'^voto-individual/(?P<pk>\d+)$', votante_view, url(r'^voto-individual/$', votante_view,
name="voto_individual"), name='voto_individual'),
] ]

233
sapl/painel/views.py

@ -1,7 +1,9 @@
from datetime import date from datetime import date
from django.contrib import messages
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.db.models import Q
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from django.http.response import Http404, HttpResponseRedirect from django.http.response import Http404, HttpResponseRedirect
@ -14,8 +16,7 @@ from sapl.painel.models import Painel
from sapl.parlamentares.models import Filiacao, Votante from sapl.parlamentares.models import Filiacao, Votante
from sapl.sessao.models import (ExpedienteMateria, OrdemDia, PresencaOrdemDia, from sapl.sessao.models import (ExpedienteMateria, OrdemDia, PresencaOrdemDia,
RegistroVotacao, SessaoPlenaria, RegistroVotacao, SessaoPlenaria,
SessaoPlenariaPresenca, VotoNominal, SessaoPlenariaPresenca, VotoParlamentar)
VotoParlamentar)
from sapl.utils import get_client_ip from sapl.utils import get_client_ip
from .models import Cronometro from .models import Cronometro
@ -31,14 +32,71 @@ def check_permission(user):
return user.has_module_perms(AppConfig.label) return user.has_module_perms(AppConfig.label)
def votante_view(request, pk): def votacao_aberta(request):
if not Votante.objects.filter(user=request.user).exists(): '''
raise Http404('Você não tem permissão para votar') Função que verifica se somente 1 uma matéria aberta ou
nenhuma. É utilizada como uma função auxiliar para a view
votante_view.
'''
votacoes_abertas = SessaoPlenaria.objects.filter(
Q(ordemdia__votacao_aberta=True) |
Q(expedientemateria__votacao_aberta=True)).distinct()
if len(votacoes_abertas) > 1:
msg_abertas = []
for v in votacoes_abertas:
msg_abertas.append('''<li><a href="%s">%s</a></li>''' % (
reverse('sapl.sessao:sessaoplenaria_detail',
kwargs={'pk': v.id}),
v.__str__()))
msg = _('Existe mais de uma votações aberta. Elas se encontram '
'nas seguintes Sessões: ' + ', '.join(msg_abertas) + '. '
'Para votar, peça para que o Operador feche-as.')
messages.add_message(request, messages.INFO, msg)
return None, msg
elif len(votacoes_abertas) == 1:
ordens = OrdemDia.objects.filter(
sessao_plenaria=votacoes_abertas.first(),
votacao_aberta=True)
expedientes = ExpedienteMateria.objects.filter(
sessao_plenaria=votacoes_abertas.first(),
votacao_aberta=True)
numero_materias_abertas = len(ordens) + len(expedientes)
if numero_materias_abertas > 1:
msg = _('Existe mais de uma votação aberta na Sessão: ' +
('''<li><a href="%s">%s</a></li>''' % (
reverse('sapl.sessao:sessaoplenaria_detail',
kwargs={'pk': votacoes_abertas.first().id}),
votacoes_abertas.first().__str__())) +
'Para votar, peça para que o Operador as feche.')
messages.add_message(request, messages.INFO, msg)
return None, msg
return votacoes_abertas.first(), None
def votante_view(request):
# Pega o votante relacionado ao usuário
try:
votante = Votante.objects.get(user=request.user)
except ObjectDoesNotExist:
raise Http404()
context = {'head_title': str(_('Votação Individual'))}
context = {'head_title': str(_('Votação Individual')), 'sessao_id': pk} # Verifica se usuário possui permissão para votar
if 'parlamentares.can_vote' in request.user.get_all_permissions():
context.update({'permissao': True})
# Pega sessão # Pega sessão
sessao = SessaoPlenaria.objects.get(pk=pk) sessao, msg = votacao_aberta(request)
if sessao and not msg:
pk = sessao.pk
context.update({'sessao_id': pk})
context.update({'sessao': sessao, context.update({'sessao': sessao,
'data': sessao.data_inicio, 'data': sessao.data_inicio,
'hora': sessao.hora_inicio}) 'hora': sessao.hora_inicio})
@ -50,110 +108,117 @@ def votante_view(request, pk):
# Se aberta, verifica se é nominal. ID nominal == 2 # Se aberta, verifica se é nominal. ID nominal == 2
ordem_dia = get_materia_aberta(pk) ordem_dia = get_materia_aberta(pk)
expediente = get_materia_expediente_aberta(pk) expediente = get_materia_expediente_aberta(pk)
materia = None
materia_aberta = None
if ordem_dia: if ordem_dia:
materia = ordem_dia.materia materia_aberta = ordem_dia
if ordem_dia.tipo_votacao == VOTACAO_NOMINAL: presentes = PresencaOrdemDia.objects.filter(
context.update({'materia': materia, 'ementa': materia.ementa}) sessao_plenaria_id=pk).values_list(
presentes = PresencaOrdemDia.objects.filter(sessao_plenaria_id=pk) 'parlamentar_id', flat=True).distinct()
else:
context.update(
{'materia': 'A matéria aberta não é votação nominal.'})
elif expediente: elif expediente:
materia = expediente.materia materia_aberta = expediente
if expediente.tipo_votacao == VOTACAO_NOMINAL:
context.update({'materia': materia, 'ementa': materia.ementa})
presentes = SessaoPlenariaPresenca.objects.filter( presentes = SessaoPlenariaPresenca.objects.filter(
sessao_plenaria_id=pk) sessao_plenaria_id=pk).values_list(
else: 'parlamentar_id', flat=True).distinct()
context.update(
{'materia': 'A matéria aberta não é votação nominal.'})
else:
context.update(
{'materia': 'Nenhuma matéria com votação nominal aberta.'})
# Verifica se usuário possui permissão para votar if materia_aberta:
if 'parlamentares.can_vote' in request.user.get_all_permissions(): if materia_aberta.tipo_votacao == VOTACAO_NOMINAL:
context.update({'permissao': True}) context.update({'materia': materia_aberta.materia,
'ementa': materia_aberta.materia.ementa})
parlamentar = votante.parlamentar
parlamentar_presente = False
if parlamentar.id in presentes:
parlamentar_presente = True
else: else:
context.update({'permissao': False}) context.update({'error_message':
'Não há presentes na Sessão com a '
'matéria em votação.'})
if parlamentar_presente:
voto = []
if ordem_dia:
voto = VotoParlamentar.objects.filter(
ordem=ordem_dia)
elif expediente:
voto = VotoParlamentar.objects.filter(
expediente=expediente)
# Verifica se usuário está presente na sessão if voto:
try: try:
votante = Votante.objects.get(user=request.user) voto = voto.get(parlamentar=parlamentar)
context.update({'voto_parlamentar': voto.voto})
except ObjectDoesNotExist: except ObjectDoesNotExist:
context.update(
{'voto_parlamentar': 'Voto não '
'computado.'})
else:
context.update({'error_message': context.update({'error_message':
'Erro ao recuperar parlamentar ligado ao usuário'}) 'Você não está presente na '
'Ordem do Dia/Expediente em votação.'})
else: else:
parlamentar = votante.parlamentar context.update(
context.update({'presente': False}) {'error_message': 'A matéria aberta não é do tipo '
if len(presentes) > 0: 'votação nominal.'})
for p in presentes:
if p.parlamentar.id == parlamentar.id:
context.update({'presente': True})
break
else: else:
context.update({'error_message': context.update(
'Nenhuma matéria com votação nominal aberta.'}) {'error_message': 'Não há nenhuma matéria aberta.'})
# Recupera o voto do parlamentar logado elif not sessao and msg:
return HttpResponseRedirect('/')
else:
context.update(
{'error_message': 'Não há nenhuma sessão com matéria aberta.'})
else:
context.update({'permissao': False,
'error_message': 'Usuário sem permissão para votar.'})
# Salva o voto
if request.method == 'POST':
if ordem_dia:
try: try:
voto = VotoNominal.objects.get( voto = VotoParlamentar.objects.get(
sessao=sessao,
parlamentar=parlamentar, parlamentar=parlamentar,
materia=materia) ordem=ordem_dia)
except ObjectDoesNotExist: except ObjectDoesNotExist:
context.update({'voto_parlamentar': 'Voto não computado.'}) voto = VotoParlamentar.objects.create(
parlamentar=parlamentar,
voto=request.POST['voto'],
user=request.user,
ip=get_client_ip(request),
ordem=ordem_dia)
else: else:
context.update({'voto_parlamentar': voto.voto}) voto.voto = request.POST['voto']
voto.ip = get_client_ip(request)
voto.user = request.user
voto.save()
# Salva o voto elif expediente:
if request.method == 'POST':
try: try:
voto = VotoNominal.objects.get( voto = VotoParlamentar.objects.get(
sessao=sessao,
parlamentar=parlamentar, parlamentar=parlamentar,
materia=materia) expediente=expediente)
except ObjectDoesNotExist: except ObjectDoesNotExist:
voto = VotoNominal.objects.create( voto = VotoParlamentar.objects.create(
sessao=sessao,
parlamentar=parlamentar, parlamentar=parlamentar,
materia=materia,
voto=request.POST['voto'], voto=request.POST['voto'],
user=request.user,
ip=get_client_ip(request), ip=get_client_ip(request),
user=request.user) expediente=expediente)
else: else:
voto.voto = request.POST['voto'] voto.voto = request.POST['voto']
voto.ip = get_client_ip(request) voto.ip = get_client_ip(request)
voto.user = request.user
voto.save() voto.save()
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('sapl.painel:voto_individual', kwargs={'pk': pk})) reverse('sapl.painel:voto_individual'))
return render(request, 'painel/voto_nominal.html', context) return render(request, 'painel/voto_nominal.html', context)
@user_passes_test(check_permission)
def controlador_painel(request):
painel_created = Painel.objects.get_or_create(data_painel=date.today())
painel = painel_created[0]
if request.method == 'POST':
if 'start-painel' in request.POST:
painel.aberto = True
painel.save()
elif 'stop-painel' in request.POST:
painel.aberto = False
painel.save()
elif 'save-painel' in request.POST:
painel.mostrar = request.POST['tipo_painel']
painel.save()
context = {'painel': painel, 'PAINEL_TYPES': Painel.PAINEL_TYPES}
return render(request, 'painel/controlador.html', context)
@user_passes_test(check_permission) @user_passes_test(check_permission)
def painel_view(request, pk): def painel_view(request, pk):
context = {'head_title': str(_('Painel Plenário')), 'sessao_id': pk} context = {'head_title': str(_('Painel Plenário')), 'sessao_id': pk}
@ -249,7 +314,8 @@ def get_presentes(pk, response, materia):
'num_presentes_sessao_plenaria': num_presentes_sessao_plen, 'num_presentes_sessao_plenaria': num_presentes_sessao_plen,
'status_painel': 'ABERTO', 'status_painel': 'ABERTO',
'msg_painel': str(_('Votação aberta!')), 'msg_painel': str(_('Votação aberta!')),
'tipo_resultado': tipo_votacao, 'tipo_resultado': materia.resultado,
'tipo_votacao': tipo_votacao,
'observacao_materia': materia.observacao, 'observacao_materia': materia.observacao,
'materia_legislativa_texto': str(materia.materia)}) 'materia_legislativa_texto': str(materia.materia)})
@ -502,12 +568,12 @@ def get_dados_painel(request, pk):
else: else:
if ultimo_expediente_votado.tipo_votacao in [1, 3]: if ultimo_expediente_votado.tipo_votacao in [1, 3]:
return JsonResponse( return JsonResponse(
get_votos(get_presentes( get_votos(get_presentes_expediente(
pk, response, ultimo_expediente_votado), pk, response, ultimo_expediente_votado),
ultimo_expediente_votado)) ultimo_expediente_votado))
elif ultimo_expediente_votado.tipo_votacao == 2: elif ultimo_expediente_votado.tipo_votacao == 2:
return JsonResponse( return JsonResponse(
get_votos_nominal(get_presentes( get_votos_nominal(get_presentes_expediente(
pk, response, pk, response,
ultimo_expediente_votado), ultimo_expediente_votado),
ultimo_expediente_votado)) ultimo_expediente_votado))
@ -518,7 +584,8 @@ def get_dados_painel(request, pk):
pk, response, ultima_ordem_votada)) pk, response, ultima_ordem_votada))
# Caso a Ordem do dia não tenha resultado, mostra o último expediente # Caso a Ordem do dia não tenha resultado, mostra o último expediente
if last_expediente_voto: if last_expediente_voto:
return JsonResponse(get_presentes(pk, response, return JsonResponse(get_presentes_expediente(
pk, response,
ultimo_expediente_votado)) ultimo_expediente_votado))
# Retorna que não há nenhuma matéria já votada ou aberta # Retorna que não há nenhuma matéria já votada ou aberta

15
sapl/parlamentares/forms.py

@ -54,11 +54,14 @@ class MandatoForm(ModelForm):
class Meta: class Meta:
model = Mandato model = Mandato
fields = ['legislatura', 'coligacao', 'votos_recebidos', fields = ['legislatura', 'coligacao', 'votos_recebidos',
'data_fim_mandato', 'data_expedicao_diploma', 'titular', 'data_inicio_mandato', 'data_fim_mandato',
'data_expedicao_diploma', 'titular',
'tipo_afastamento', 'observacao', 'parlamentar'] 'tipo_afastamento', 'observacao', 'parlamentar']
widgets = {'parlamentar': forms.HiddenInput()} widgets = {'parlamentar': forms.HiddenInput()}
def clean(self): def clean(self):
super(MandatoForm, self).clean()
data = self.cleaned_data data = self.cleaned_data
try: try:
if 'legislatura' in data and 'parlamentar' in data: if 'legislatura' in data and 'parlamentar' in data:
@ -82,6 +85,8 @@ class LegislaturaForm(ModelForm):
class LegislaturaCreateForm(LegislaturaForm): class LegislaturaCreateForm(LegislaturaForm):
def clean(self): def clean(self):
super(LegislaturaCreateForm, self).clean()
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
eleicao = cleaned_data['data_eleicao'] eleicao = cleaned_data['data_eleicao']
inicio = cleaned_data['data_inicio'] inicio = cleaned_data['data_inicio']
@ -96,6 +101,8 @@ class LegislaturaCreateForm(LegislaturaForm):
class LegislaturaUpdateForm(LegislaturaCreateForm): class LegislaturaUpdateForm(LegislaturaCreateForm):
def clean(self): def clean(self):
super(LegislaturaUpdateForm, self).clean()
cleaned_data = super(LegislaturaCreateForm, self).clean() cleaned_data = super(LegislaturaCreateForm, self).clean()
eleicao = cleaned_data['data_eleicao'] eleicao = cleaned_data['data_eleicao']
inicio = cleaned_data['data_inicio'] inicio = cleaned_data['data_inicio']
@ -214,6 +221,8 @@ class FiliacaoForm(ModelForm):
'data_desfiliacao'] 'data_desfiliacao']
def clean(self): def clean(self):
super(FiliacaoForm, self).clean()
if self.errors: if self.errors:
return self.errors return self.errors
@ -236,6 +245,8 @@ class ComposicaoColigacaoForm(ModelForm):
fields = ['partido'] fields = ['partido']
def clean(self): def clean(self):
super(ComposicaoColigacaoForm, self).clean()
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
pk = self.initial['coligacao_id'] pk = self.initial['coligacao_id']
if (ComposicaoColigacao.objects.filter( if (ComposicaoColigacao.objects.filter(
@ -311,6 +322,8 @@ class VotanteForm(ModelForm):
return True return True
def clean(self): def clean(self):
super(VotanteForm, self).clean()
cd = self.cleaned_data cd = self.cleaned_data
username = cd['username'] username = cd['username']

1
sapl/parlamentares/legacy.yaml

@ -94,7 +94,6 @@ TipoAfastamento:
Mandato: Mandato:
coligacao: cod_coligacao coligacao: cod_coligacao
data_expedicao_diploma: dat_expedicao_diploma data_expedicao_diploma: dat_expedicao_diploma
data_fim_mandato: dat_fim_mandato
legislatura: num_legislatura legislatura: num_legislatura
observacao: txt_observacao observacao: txt_observacao
parlamentar: cod_parlamentar parlamentar: cod_parlamentar

27
sapl/parlamentares/migrations/0003_auto_20170707_1656.py

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-07-07 16:56
from __future__ import unicode_literals
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('parlamentares', '0002_auto_20170504_1751'),
]
operations = [
migrations.AddField(
model_name='mandato',
name='data_inicio_mandato',
field=models.DateField(default=datetime.datetime(2017, 7, 7, 16, 56, 58, 525896), verbose_name='Início do Mandato'),
preserve_default=False,
),
migrations.AlterField(
model_name='mandato',
name='data_fim_mandato',
field=models.DateField(blank=True, null=True, verbose_name='Fim do Mandato'),
),
]

20
sapl/parlamentares/migrations/0004_auto_20170711_1305.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-07-11 13:05
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('parlamentares', '0003_auto_20170707_1656'),
]
operations = [
migrations.AlterField(
model_name='mandato',
name='data_inicio_mandato',
field=models.DateField(blank=True, null=True, verbose_name='Início do Mandato'),
),
]

31
sapl/parlamentares/models.py

@ -1,9 +1,9 @@
from datetime import datetime from datetime import datetime
import reversion
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from model_utils import Choices from model_utils import Choices
import reversion
from sapl.base.models import Autor from sapl.base.models import Autor
from sapl.utils import (INDICADOR_AFASTAMENTO, UF, YES_NO_CHOICES, from sapl.utils import (INDICADOR_AFASTAMENTO, UF, YES_NO_CHOICES,
@ -26,17 +26,10 @@ class Legislatura(models.Model):
def atual(self): def atual(self):
current_year = datetime.now().year current_year = datetime.now().year
if(self.data_inicio.year <= current_year and return self.data_inicio.year <= current_year <= self.data_fim.year
self.data_fim.year >= current_year):
return True
else:
return False
def __str__(self): def __str__(self):
if self.atual(): current = ' (%s)' % _('Atual') if self.atual() else ''
current = ' (%s)' % _('Atual')
else:
current = ''
return _('%(numero)sª (%(start)s - %(end)s)%(current)s') % { return _('%(numero)sª (%(start)s - %(end)s)%(current)s') % {
'numero': self.numero, 'numero': self.numero,
@ -206,6 +199,15 @@ def foto_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='') return texto_upload_path(instance, filename, subpath='')
def true_false_none(x):
if x == 'True':
return True
elif x == 'False':
return False
else:
return None
@reversion.register() @reversion.register()
class Parlamentar(models.Model): class Parlamentar(models.Model):
FEMININO = 'F' FEMININO = 'F'
@ -311,7 +313,7 @@ class Parlamentar(models.Model):
ordering = ['nome_parlamentar'] ordering = ['nome_parlamentar']
def __str__(self): def __str__(self):
return self.nome_completo return self.nome_parlamentar
@property @property
def filiacao_atual(self): def filiacao_atual(self):
@ -447,7 +449,12 @@ class Mandato(models.Model):
on_delete=models.PROTECT, verbose_name=_('Coligação')) on_delete=models.PROTECT, verbose_name=_('Coligação'))
# TODO what is this field?????? # TODO what is this field??????
tipo_causa_fim_mandato = models.PositiveIntegerField(blank=True, null=True) tipo_causa_fim_mandato = models.PositiveIntegerField(blank=True, null=True)
data_fim_mandato = models.DateField(verbose_name=_('Fim do Mandato')) data_inicio_mandato = models.DateField(verbose_name=_('Início do Mandato'),
blank=True,
null=True)
data_fim_mandato = models.DateField(verbose_name=_('Fim do Mandato'),
blank=True,
null=True)
votos_recebidos = models.PositiveIntegerField( votos_recebidos = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Votos Recebidos (Mandato)')) blank=True, null=True, verbose_name=_('Votos Recebidos (Mandato)'))
data_expedicao_diploma = models.DateField( data_expedicao_diploma = models.DateField(

2
sapl/parlamentares/tests/test_parlamentares.py

@ -147,7 +147,5 @@ def test_form_errors_mandato(admin_client):
assert (response.context_data['form'].errors['legislatura'] == assert (response.context_data['form'].errors['legislatura'] ==
['Este campo é obrigatório.']) ['Este campo é obrigatório.'])
assert (response.context_data['form'].errors['data_fim_mandato'] ==
['Este campo é obrigatório.'])
assert (response.context_data['form'].errors['data_expedicao_diploma'] == assert (response.context_data['form'].errors['data_expedicao_diploma'] ==
['Este campo é obrigatório.']) ['Este campo é obrigatório.'])

29
sapl/parlamentares/urls.py

@ -1,22 +1,24 @@
from django.conf.urls import include, url from django.conf.urls import include, url
from sapl.parlamentares.views import (altera_field_mesa, from sapl.parlamentares.views import (CargoMesaCrud, ColigacaoCrud,
altera_field_mesa_public_view,
CargoMesaCrud, ColigacaoCrud,
ComposicaoColigacaoCrud, DependenteCrud, ComposicaoColigacaoCrud, DependenteCrud,
FiliacaoCrud, FrenteCrud, FrenteList, FiliacaoCrud, FrenteCrud, FrenteList,
LegislaturaCrud, LegislaturaCrud, MandatoCrud,
insere_parlamentar_composicao,
MandatoCrud,
MesaDiretoraView, NivelInstrucaoCrud, MesaDiretoraView, NivelInstrucaoCrud,
ParlamentarCrud, ParlamentarCrud,
ParlamentarMateriasView,
ParticipacaoParlamentarCrud, PartidoCrud, ParticipacaoParlamentarCrud, PartidoCrud,
ProposicaoParlamentarCrud, ProposicaoParlamentarCrud,
RelatoriaParlamentarCrud, RelatoriaParlamentarCrud,
remove_parlamentar_composicao,
SessaoLegislativaCrud, SessaoLegislativaCrud,
TipoAfastamentoCrud, TipoDependenteCrud, TipoAfastamentoCrud, TipoDependenteCrud,
TipoMilitarCrud, VotanteView) TipoMilitarCrud, VotanteView,
altera_field_mesa,
altera_field_mesa_public_view,
frente_atualiza_lista_parlamentares,
insere_parlamentar_composicao,
parlamentares_frente_selected,
remove_parlamentar_composicao)
from .apps import AppConfig from .apps import AppConfig
@ -32,11 +34,22 @@ urlpatterns = [
VotanteView.get_urls() VotanteView.get_urls()
)), )),
url(r'^parlamentar/(?P<pk>\d+)/materias$',
ParlamentarMateriasView.as_view(), name='parlamentar_materias'),
url(r'^sistema/coligacao/', url(r'^sistema/coligacao/',
include(ColigacaoCrud.get_urls() + include(ColigacaoCrud.get_urls() +
ComposicaoColigacaoCrud.get_urls())), ComposicaoColigacaoCrud.get_urls())),
url(r'^sistema/frente/', url(r'^sistema/frente/',
include(FrenteCrud.get_urls())), include(FrenteCrud.get_urls())),
url(r'^sistema/frente/atualiza-lista-parlamentares',
frente_atualiza_lista_parlamentares,
name='atualiza_lista_parlamentares'),
url(r'^sistema/frente/parlamentares-frente-selected',
parlamentares_frente_selected,
name='parlamentares_frente_selected'),
url(r'^sistema/parlamentar/legislatura/', url(r'^sistema/parlamentar/legislatura/',
include(LegislaturaCrud.get_urls())), include(LegislaturaCrud.get_urls())),
url(r'^sistema/parlamentar/tipo-dependente/', url(r'^sistema/parlamentar/tipo-dependente/',

281
sapl/parlamentares/views.py

@ -1,11 +1,13 @@
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
from django.db.models import F, Q from django.db.models import F, Q
from django.http import JsonResponse from django.http import JsonResponse
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.templatetags.static import static
from django.utils.datastructures import MultiValueDictKeyError from django.utils.datastructures import MultiValueDictKeyError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import FormView from django.views.generic import FormView
from sapl.comissoes.models import Participacao from sapl.comissoes.models import Participacao
@ -23,6 +25,15 @@ from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa,
NivelInstrucao, Parlamentar, Partido, SessaoLegislativa, NivelInstrucao, Parlamentar, Partido, SessaoLegislativa,
SituacaoMilitar, TipoAfastamento, TipoDependente, Votante) SituacaoMilitar, TipoAfastamento, TipoDependente, Votante)
from sapl.base.models import Autor
from sapl.materia.models import Autoria
from django.contrib.contenttypes.models import ContentType
from django.db.models.aggregates import Count
import datetime
import json
CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa') CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa')
PartidoCrud = CrudAux.build(Partido, 'partidos') PartidoCrud = CrudAux.build(Partido, 'partidos')
SessaoLegislativaCrud = CrudAux.build(SessaoLegislativa, 'sessao_legislativa') SessaoLegislativaCrud = CrudAux.build(SessaoLegislativa, 'sessao_legislativa')
@ -31,9 +42,6 @@ NivelInstrucaoCrud = CrudAux.build(NivelInstrucao, 'nivel_instrucao')
TipoAfastamentoCrud = CrudAux.build(TipoAfastamento, 'tipo_afastamento') TipoAfastamentoCrud = CrudAux.build(TipoAfastamento, 'tipo_afastamento')
TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar') TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar')
FrenteCrud = CrudAux.build(Frente, 'tipo_situa_militar', list_field_names=[
'nome', 'data_criacao', 'parlamentares'])
DependenteCrud = MasterDetailCrud.build( DependenteCrud = MasterDetailCrud.build(
Dependente, 'parlamentar', 'dependente') Dependente, 'parlamentar', 'dependente')
@ -110,8 +118,6 @@ class ProposicaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = CrudBaseForListAndDetailExternalAppView\ context = CrudBaseForListAndDetailExternalAppView\
.ListView.get_context_data(self, **kwargs) .ListView.get_context_data(self, **kwargs)
context['title'] = context['title'].replace(
'Proposições', 'Matérias')
return context return context
def get_queryset(self): def get_queryset(self):
@ -182,10 +188,133 @@ class ColigacaoCrud(CrudAux):
class ListView(CrudAux.ListView): class ListView(CrudAux.ListView):
ordering = ('-numero_votos', 'nome') ordering = ('-numero_votos', 'nome')
def get_context_data(self, **kwargs):
context = super(ColigacaoCrud.ListView, self).get_context_data(kwargs=kwargs)
rows = context['rows']
coluna_votos_recebidos = 2
for row in rows:
if not row[coluna_votos_recebidos][0]:
row[coluna_votos_recebidos] = ('0', None)
return context
class DetailView(CrudAux.DetailView):
def get_context_data(self, **kwargs):
context = super(ColigacaoCrud.DetailView, self).get_context_data(kwargs=kwargs)
coligacao = context['coligacao']
if not coligacao.numero_votos:
coligacao.numero_votos = '0'
return context
class BaseMixin(CrudAux.BaseMixin): class BaseMixin(CrudAux.BaseMixin):
subnav_template_name = 'parlamentares/subnav_coligacao.yaml' subnav_template_name = 'parlamentares/subnav_coligacao.yaml'
def json_date_convert(date):
'''
:param date: recebe a data de uma chamada ajax no formato de
string "dd/mm/yyyy"
:return:
'''
dia, mes, ano = date.split('/')
return datetime.date(day=int(dia),
month=int(mes),
year=int(ano))
def parlamentares_ativos(data_inicio, data_fim=None):
'''
:param data_inicio: define a data de inicial do período desejado
:param data_fim: define a data final do período desejado
:return: queryset dos parlamentares ativos naquele período
'''
mandatos_ativos = Mandato.objects.filter(Q(
data_inicio_mandato__lte=data_inicio,
data_fim_mandato__isnull=True) | Q(
data_inicio_mandato__lte=data_inicio,
data_fim_mandato__gte=data_inicio))
if data_fim:
mandatos_ativos = mandatos_ativos | Mandato.objects.filter(
data_inicio_mandato__gte=data_inicio,
data_inicio_mandato__lte=data_fim)
else:
mandatos_ativos = mandatos_ativos | Mandato.objects.filter(
data_inicio_mandato__gte=data_inicio)
parlamentares_id = mandatos_ativos.values_list(
'parlamentar_id',
flat=True).distinct('parlamentar_id')
return Parlamentar.objects.filter(id__in=parlamentares_id)
def frente_atualiza_lista_parlamentares(request):
'''
:param request: recebe os parâmetros do GET da chamada Ajax
:return: retorna a lista atualizada dos parlamentares
'''
ativos = json.loads(request.GET['ativos'])
parlamentares = Parlamentar.objects.all()
if ativos:
if 'data_criacao' in request.GET and request.GET['data_criacao']:
data_criacao = json_date_convert(request.GET['data_criacao'])
if 'data_extincao' in request.GET and request.GET['data_extincao']:
data_extincao = json_date_convert(request.GET['data_extincao'])
parlamentares = parlamentares_ativos(data_criacao,
data_extincao)
else:
parlamentares = parlamentares_ativos(data_criacao)
parlamentares_list = [(p.id, p.__str__()) for p in parlamentares]
return JsonResponse({'parlamentares_list': parlamentares_list})
def parlamentares_frente_selected(request):
'''
:return: Lista com o id dos parlamentares em uma frente
'''
try:
frente = Frente.objects.get(id=int(request.GET['frente_id']))
except ObjectDoesNotExist:
lista_parlamentar_id = []
else:
lista_parlamentar_id = frente.parlamentares.all().values_list(
'id', flat=True)
return JsonResponse({'id_list': list(lista_parlamentar_id)})
class FrenteCrud(CrudAux):
model = Frente
help_path = 'tabelas_auxiliares#tipo_situa_militar'
list_field_names = ['nome', 'data_criacao', 'parlamentares']
class CreateView(CrudAux.CreateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Update view é um indicador para o javascript
# de que esta não é uma tela de edição de frente
context['update_view'] = 0
return context
class UpdateView(CrudAux.UpdateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Update view é um indicador para o javascript
# de que esta não é uma tela de edição de frente
context['update_view'] = 1
return context
class MandatoCrud(MasterDetailCrud): class MandatoCrud(MasterDetailCrud):
model = Mandato model = Mandato
parent_field = 'parlamentar' parent_field = 'parlamentar'
@ -199,6 +328,22 @@ class MandatoCrud(MasterDetailCrud):
class ListView(MasterDetailCrud.ListView): class ListView(MasterDetailCrud.ListView):
ordering = ('-legislatura__numero') ordering = ('-legislatura__numero')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
rows = context['rows']
coluna_coligacao = 2
coluna_votos_recebidos = 3
for row in rows:
if not row[coluna_coligacao][0]:
row[coluna_coligacao] = (' ', None)
if not row[coluna_votos_recebidos][0]:
row[coluna_votos_recebidos] = (' ', None)
return context
class CreateView(MasterDetailCrud.CreateView): class CreateView(MasterDetailCrud.CreateView):
form_class = MandatoForm form_class = MandatoForm
@ -242,6 +387,22 @@ class LegislaturaCrud(CrudAux):
class UpdateView(CrudAux.UpdateView): class UpdateView(CrudAux.UpdateView):
form_class = LegislaturaUpdateForm form_class = LegislaturaUpdateForm
class DetailView(CrudAux.DetailView):
def has_permission(self):
return True
@xframe_options_exempt
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
class ListView(CrudAux.ListView):
def has_permission(self):
return True
@xframe_options_exempt
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
class FiliacaoCrud(MasterDetailCrud): class FiliacaoCrud(MasterDetailCrud):
model = Filiacao model = Filiacao
@ -275,9 +436,18 @@ class ParlamentarCrud(Crud):
class DetailView(Crud.DetailView): class DetailView(Crud.DetailView):
def get_template_names(self): def get_template_names(self):
return ['crud/detail.html']\ if self.request.user.has_perm(self.permission(RP_CHANGE)):
if self.request.user.has_perm(self.permission(RP_CHANGE))\ if 'iframe' not in self.request.GET:
else ['parlamentares/parlamentar_perfil_publico.html'] if not self.request.session.get('iframe'):
return ['crud/detail.html']
elif self.request.GET['iframe'] == '0':
return ['crud/detail.html']
return ['parlamentares/parlamentar_perfil_publico.html']
@xframe_options_exempt
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
class UpdateView(Crud.UpdateView): class UpdateView(Crud.UpdateView):
form_class = ParlamentarForm form_class = ParlamentarForm
@ -302,6 +472,10 @@ class ParlamentarCrud(Crud):
template_name = "parlamentares/parlamentares_list.html" template_name = "parlamentares/parlamentares_list.html"
paginate_by = None paginate_by = None
@xframe_options_exempt
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
def take_legislatura_id(self): def take_legislatura_id(self):
try: try:
return int(self.request.GET['pk']) return int(self.request.GET['pk'])
@ -345,6 +519,14 @@ class ParlamentarCrud(Crud):
# Tira Link do avatar_html e coloca no nome # Tira Link do avatar_html e coloca no nome
for row in context['rows']: for row in context['rows']:
# preenche coluna foto, se vazia
if not row[0][0]:
img = "<center><img width='50px' \
height='50px' src='%s'/></center>" \
% static('img/avatar.png')
row[0] = (img, row[0][1])
# Coloca a filiação atual ao invés da última # Coloca a filiação atual ao invés da última
if row[0][1]: if row[0][1]:
# Pega o Parlamentar por meio da pk # Pega o Parlamentar por meio da pk
@ -387,15 +569,87 @@ class ParlamentarCrud(Crud):
return context return context
class ParlamentarMateriasView(FormView):
template_name = "parlamentares/materias.html"
success_url = reverse_lazy('sapl.parlamentares:parlamentar_materia')
def get_autoria(self, resultset):
autoria = {}
total_autoria = 0
for i in resultset:
row = autoria.get(i['materia__ano'], [])
columns = (i['materia__tipo__pk'],
i['materia__tipo__sigla'],
i['materia__tipo__descricao'],
int(i['total']))
row.append(columns)
autoria[i['materia__ano']] = row
total_autoria += columns[3]
autoria = sorted(autoria.items(), reverse=True)
return autoria, total_autoria
@xframe_options_exempt
def get(self, request, *args, **kwargs):
parlamentar_pk = kwargs['pk']
try:
autor = Autor.objects.get(
content_type=ContentType.objects.get_for_model(Parlamentar),
object_id=parlamentar_pk)
except ObjectDoesNotExist:
mensagem = _('Este Parlamentar não é autor de matéria.')
messages.add_message(request, messages.ERROR, mensagem)
return HttpResponseRedirect(
reverse(
'sapl.parlamentares:parlamentar_detail',
kwargs={'pk': parlamentar_pk}))
autoria = Autoria.objects.filter(
autor=autor, primeiro_autor=True).values(
'materia__ano',
'materia__tipo__pk',
'materia__tipo__sigla',
'materia__tipo__descricao').annotate(
total=Count('materia__tipo__pk')).order_by(
'-materia__ano', 'materia__tipo')
coautoria = Autoria.objects.filter(
autor=autor, primeiro_autor=False).values(
'materia__ano',
'materia__tipo__pk',
'materia__tipo__sigla',
'materia__tipo__descricao').annotate(
total=Count('materia__tipo__pk')).order_by(
'-materia__ano', 'materia__tipo')
autor_list = self.get_autoria(autoria)
coautor_list = self.get_autoria(coautoria)
parlamentar_pk = autor.autor_related.pk
nome_parlamentar = autor.autor_related.nome_parlamentar
return self.render_to_response({'autor_pk': autor.pk,
'root_pk': parlamentar_pk,
'autoria': autor_list,
'coautoria': coautor_list,
'nome_parlamentar': nome_parlamentar
})
class MesaDiretoraView(FormView): class MesaDiretoraView(FormView):
template_name = 'parlamentares/composicaomesa_form.html' template_name = 'parlamentares/composicaomesa_form.html'
success_url = reverse_lazy('sapl.parlamentares:mesa_diretora') success_url = reverse_lazy('sapl.parlamentares:mesa_diretora')
def get_template_names(self): def get_template_names(self):
return ['parlamentares/composicaomesa_form.html']\ if self.request.user.has_perm('parlamentares.change_composicaomesa'):
if self.request.user.has_perm( if 'iframe' not in self.request.GET:
'parlamentares.change_composicaomesa')\ if not self.request.session.get('iframe'):
else ['parlamentares/public_composicaomesa_form.html'] return 'parlamentares/composicaomesa_form.html'
elif self.request.GET['iframe'] == '0':
return 'parlamentares/composicaomesa_form.html'
return 'parlamentares/public_composicaomesa_form.html'
# Essa função avisa quando se pode compor uma Mesa Legislativa # Essa função avisa quando se pode compor uma Mesa Legislativa
def validation(self, request): def validation(self, request):
@ -410,6 +664,7 @@ class MesaDiretoraView(FormView):
'legislatura_selecionada': Legislatura.objects.last(), 'legislatura_selecionada': Legislatura.objects.last(),
'cargos_vagos': CargoMesa.objects.all()}) 'cargos_vagos': CargoMesa.objects.all()})
@xframe_options_exempt
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if (not Legislatura.objects.exists() or if (not Legislatura.objects.exists() or

46
sapl/protocoloadm/forms.py

@ -20,8 +20,8 @@ from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo, Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo) TramitacaoAdministrativo)
TIPOS_PROTOCOLO = [('0', 'Enviado'), ('1', 'Recebido'), ('', 'Ambos')] TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'), ('', 'Ambos')]
TIPOS_PROTOCOLO_CREATE = [('0', 'Enviado'), ('1', 'Recebido')] TIPOS_PROTOCOLO_CREATE = [('0', 'Recebido'), ('1', 'Enviado')]
NATUREZA_PROCESSO = [('', 'Ambos'), NATUREZA_PROCESSO = [('', 'Ambos'),
('0', 'Administrativo'), ('0', 'Administrativo'),
@ -47,7 +47,7 @@ class ProtocoloFilterSet(django_filters.FilterSet):
}} }}
ano = django_filters.ChoiceFilter(required=False, ano = django_filters.ChoiceFilter(required=False,
label=u'Ano', label='Ano',
choices=ANO_CHOICES) choices=ANO_CHOICES)
assunto_ementa = django_filters.CharFilter(lookup_expr='icontains') assunto_ementa = django_filters.CharFilter(lookup_expr='icontains')
@ -135,11 +135,11 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
}} }}
ano = django_filters.ChoiceFilter(required=False, ano = django_filters.ChoiceFilter(required=False,
label=u'Ano', label='Ano',
choices=ANO_CHOICES) choices=ANO_CHOICES)
tramitacao = django_filters.ChoiceFilter(required=False, tramitacao = django_filters.ChoiceFilter(required=False,
label=u'Em Tramitação?', label='Em Tramitação?',
choices=EM_TRAMITACAO) choices=EM_TRAMITACAO)
assunto = django_filters.CharFilter(lookup_expr='icontains') assunto = django_filters.CharFilter(lookup_expr='icontains')
@ -213,6 +213,8 @@ class AnularProcoloAdmForm(ModelForm):
widget=forms.Textarea) widget=forms.Textarea)
def clean(self): def clean(self):
super(AnularProcoloAdmForm, self).clean()
cleaned_data = super(AnularProcoloAdmForm, self).clean() cleaned_data = super(AnularProcoloAdmForm, self).clean()
numero = cleaned_data.get("numero") numero = cleaned_data.get("numero")
@ -409,34 +411,6 @@ class DocumentoAcessorioAdministrativoForm(ModelForm):
'data': forms.DateInput(format='%d/%m/%Y') 'data': forms.DateInput(format='%d/%m/%Y')
} }
def __init__(self, excluir=False, *args, **kwargs):
row1 = to_row(
[('tipo', 4),
('nome', 4),
('data', 4)])
row2 = to_row(
[('autor', 12)])
row3 = to_row(
[('arquivo', 12)])
row4 = to_row(
[('assunto', 12)])
more = []
if excluir:
more = [Submit('Excluir', 'Excluir')]
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
_('Incluir Documento Acessório'),
row1, row2, row3, row4,
form_actions(more=more)
)
)
super(DocumentoAcessorioAdministrativoForm, self).__init__(
*args, **kwargs)
class TramitacaoAdmForm(ModelForm): class TramitacaoAdmForm(ModelForm):
@ -458,6 +432,8 @@ class TramitacaoAdmForm(ModelForm):
} }
def clean(self): def clean(self):
super(TramitacaoAdmForm, self).clean()
data_enc_form = self.cleaned_data['data_encaminhamento'] data_enc_form = self.cleaned_data['data_encaminhamento']
data_prazo_form = self.cleaned_data['data_fim_prazo'] data_prazo_form = self.cleaned_data['data_fim_prazo']
data_tram_form = self.cleaned_data['data_tramitacao'] data_tram_form = self.cleaned_data['data_tramitacao']
@ -530,6 +506,8 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm):
} }
def clean(self): def clean(self):
super(TramitacaoAdmEditForm, self).clean()
local = self.instance.unidade_tramitacao_local local = self.instance.unidade_tramitacao_local
data_tram = self.instance.data_tramitacao data_tram = self.instance.data_tramitacao
@ -570,6 +548,8 @@ class DocumentoAdministrativoForm(ModelForm):
widgets = {'protocolo': forms.HiddenInput()} widgets = {'protocolo': forms.HiddenInput()}
def clean(self): def clean(self):
super(DocumentoAdministrativoForm, self).clean()
numero_protocolo = self.data['numero_protocolo'] numero_protocolo = self.data['numero_protocolo']
ano_protocolo = self.data['ano_protocolo'] ano_protocolo = self.data['ano_protocolo']

19
sapl/protocoloadm/urls.py

@ -3,10 +3,7 @@ from django.conf.urls import include, url
from sapl.protocoloadm.views import (AnularProtocoloAdmView, from sapl.protocoloadm.views import (AnularProtocoloAdmView,
ComprovanteProtocoloView, ComprovanteProtocoloView,
CriarDocumentoProtocolo, CriarDocumentoProtocolo,
DetailDocumentoAdministrativo,
DocumentoAcessorioAdministrativoCrud, DocumentoAcessorioAdministrativoCrud,
DocumentoAcessorioAdministrativoEditView,
DocumentoAcessorioAdministrativoView,
DocumentoAdministrativoCrud, DocumentoAdministrativoCrud,
PesquisarDocumentoAdministrativoView, PesquisarDocumentoAdministrativoView,
ProtocoloDocumentoView, ProtocoloDocumentoView,
@ -26,20 +23,12 @@ app_name = AppConfig.name
urlpatterns_documento_administrativo = [ urlpatterns_documento_administrativo = [
url(r'^docadm/', url(r'^docadm/',
include(DocumentoAdministrativoCrud.get_urls())), include(DocumentoAdministrativoCrud.get_urls() +
url(r'^docadm/doc-acessorio/', TramitacaoAdmCrud.get_urls() +
include(DocumentoAcessorioAdministrativoCrud.get_urls())), DocumentoAcessorioAdministrativoCrud.get_urls())),
url(r'^docadm/tramitacao-doc-adm/',
include(TramitacaoAdmCrud.get_urls())),
url(r'^docadm/pesq-doc-adm', url(r'^docadm/pesq-doc-adm',
PesquisarDocumentoAdministrativoView.as_view(), name='pesq_doc_adm'), PesquisarDocumentoAdministrativoView.as_view(), name='pesq_doc_adm'),
url(r'^docadm/doc-adm/(?P<pk>\d+)$',
DetailDocumentoAdministrativo.as_view(), name='detail_doc_adm'),
url(r'^docadm/doc-ace-adm/(?P<pk>\d+)',
DocumentoAcessorioAdministrativoView.as_view(), name='doc_ace_adm'),
url(r'^docadm/doc-ace-adm/edit/(?P<pk>\d+)/(?P<ano>\d+)',
DocumentoAcessorioAdministrativoEditView.as_view(),
name='doc_ace_adm_edit'),
url(r'^docadm/texto_integral/(?P<pk>\d+)$', doc_texto_integral, url(r'^docadm/texto_integral/(?P<pk>\d+)$', doc_texto_integral,
name='doc_texto_integral'), name='doc_texto_integral'),

179
sapl/protocoloadm/views.py

@ -37,10 +37,6 @@ TipoDocumentoAdministrativoCrud = CrudAux.build(
# ProtocoloMateriaCrud = Crud.build(Protocolo, '') # ProtocoloMateriaCrud = Crud.build(Protocolo, '')
DocumentoAcessorioAdministrativoCrud = Crud.build(
DocumentoAcessorioAdministrativo, '')
def doc_texto_integral(request, pk): def doc_texto_integral(request, pk):
can_see = True can_see = True
@ -87,9 +83,18 @@ class DocumentoAdministrativoCrud(Crud):
class BaseMixin(Crud.BaseMixin): class BaseMixin(Crud.BaseMixin):
list_field_names = ['tipo', 'numero', 'ano', 'data', list_field_names = ['tipo', 'numero', 'ano', 'data',
'numero_protocolo', 'ano_protocolo', 'assunto', 'numero_protocolo', 'assunto',
'interessado', 'tramitacao', 'texto_integral'] 'interessado', 'tramitacao', 'texto_integral']
@property
def search_url(self):
namespace = self.model._meta.app_config.name
return reverse('%s:%s' % (namespace, 'pesq_doc_adm'))
@property
def list_url(self):
return ''
class ListView(DocumentoAdministrativoMixin, Crud.ListView): class ListView(DocumentoAdministrativoMixin, Crud.ListView):
pass pass
@ -116,6 +121,10 @@ class DocumentoAdministrativoCrud(Crud):
kwargs={'pk': self.object.pk})) kwargs={'pk': self.object.pk}))
return context return context
class DeleteView(DocumentoAdministrativoMixin, Crud.DeleteView):
def get_success_url(self):
return reverse('sapl.protocoloadm:pesq_doc_adm', kwargs={})
class StatusTramitacaoAdministrativoCrud(CrudAux): class StatusTramitacaoAdministrativoCrud(CrudAux):
model = StatusTramitacaoAdministrativo model = StatusTramitacaoAdministrativo
@ -416,6 +425,7 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
'%H:%M') '%H:%M')
protocolo.timestamp = datetime.strptime( protocolo.timestamp = datetime.strptime(
datetime.now().strftime("%Y-%m-%d %H:%M"), "%Y-%m-%d %H:%M") datetime.now().strftime("%Y-%m-%d %H:%M"), "%Y-%m-%d %H:%M")
protocolo.tipo_protocolo = 0
protocolo.tipo_processo = '1' # TODO validar o significado protocolo.tipo_processo = '1' # TODO validar o significado
protocolo.anulado = False protocolo.anulado = False
@ -425,6 +435,7 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
id=self.request.POST['tipo_materia']) id=self.request.POST['tipo_materia'])
protocolo.numero_paginas = self.request.POST['numero_paginas'] protocolo.numero_paginas = self.request.POST['numero_paginas']
protocolo.observacao = self.request.POST['observacao'] protocolo.observacao = self.request.POST['observacao']
protocolo.assunto_ementa = self.request.POST['assunto_ementa']
protocolo.save() protocolo.save()
return redirect(self.get_success_url(protocolo)) return redirect(self.get_success_url(protocolo))
@ -508,159 +519,55 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
return self.render_to_response(context) return self.render_to_response(context)
class DetailDocumentoAdministrativo(PermissionRequiredMixin, DetailView): class TramitacaoAdmCrud(MasterDetailCrud):
template_name = "protocoloadm/detail_doc_adm.html" model = TramitacaoAdministrativo
permission_required = ('protocoloadm.detail_documentoadministrativo', ) parent_field = 'documento'
help_path = ''
def get(self, request, *args, **kwargs):
documento = DocumentoAdministrativo.objects.get(
id=self.kwargs['pk'])
form = DocumentoAdministrativoForm(
instance=documento)
return self.render_to_response({
'form': form,
'pk': kwargs['pk']})
def post(self, request, *args, **kwargs):
if 'Salvar' in request.POST:
form = DocumentoAdministrativoForm(request.POST)
if form.is_valid():
doc = form.save(commit=False)
if 'texto_integral' in request.FILES:
doc.texto_integral = request.FILES['texto_integral']
doc.save()
return self.form_valid(form)
else:
return self.render_to_response({'form': form})
elif 'Excluir' in request.POST:
DocumentoAdministrativo.objects.get(
id=kwargs['pk']).delete()
return HttpResponseRedirect(self.get_success_delete())
return HttpResponseRedirect(self.get_success_url())
def get_success_delete(self):
return reverse('sapl.protocoloadm:pesq_doc_adm')
def get_success_url(self):
return reverse('sapl.protocoloadm:detail_doc_adm', kwargs={
'pk': self.kwargs['pk']})
class DocumentoAcessorioAdministrativoEditView(PermissionRequiredMixin,
FormView):
template_name = "protocoloadm/documento_acessorio_administrativo_edit.html"
permission_required = (
'protocoloadm.change_documentoacessorioadministrativo', )
def get(self, request, *args, **kwargs): class BaseMixin(MasterDetailCrud.BaseMixin):
doc = DocumentoAdministrativo.objects.get( list_field_names = ['data_tramitacao', 'unidade_tramitacao_local',
id=kwargs['pk']) 'unidade_tramitacao_destino', 'status']
doc_ace = DocumentoAcessorioAdministrativo.objects.get(
id=kwargs['ano'])
form = DocumentoAcessorioAdministrativoForm(instance=doc_ace,
excluir=True)
return self.render_to_response({'pk': self.kwargs['pk'],
'doc': doc,
'doc_ace': doc_ace,
'form': form})
def post(self, request, *args, **kwargs):
form = DocumentoAcessorioAdministrativoForm(request.POST, excluir=True)
doc_ace = DocumentoAcessorioAdministrativo.objects.get(
id=kwargs['ano'])
if form.is_valid():
if 'Salvar' in request.POST:
if 'arquivo' in request.FILES:
doc_ace.arquivo = request.FILES['arquivo']
doc_ace.documento = DocumentoAdministrativo.objects.get(
id=kwargs['pk'])
doc_ace.tipo = TipoDocumentoAdministrativo.objects.get(
id=form.data['tipo'])
doc_ace.nome = form.data['nome']
doc_ace.autor = form.data['autor']
doc_ace.data = datetime.strptime(
form.data['data'], '%d/%m/%Y')
doc_ace.assunto = form.data['assunto']
doc_ace.save()
elif 'Excluir' in request.POST:
doc_ace.delete()
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_success_url(self): class CreateView(MasterDetailCrud.CreateView):
pk = self.kwargs['pk'] form_class = TramitacaoAdmForm
return reverse('sapl.protocoloadm:doc_ace_adm', kwargs={'pk': pk})
class UpdateView(MasterDetailCrud.UpdateView):
form_class = TramitacaoAdmEditForm
class DocumentoAcessorioAdministrativoView(PermissionRequiredMixin, FormView): class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView):
template_name = "protocoloadm/documento_acessorio_administrativo.html"
permission_required = (
'protocoloadm.add_documentoacessorioadministrativo', )
def get(self, request, *args, **kwargs): def get_queryset(self):
form = DocumentoAcessorioAdministrativoForm() qs = super(MasterDetailCrud.ListView, self).get_queryset()
doc = DocumentoAdministrativo.objects.get( kwargs = {self.crud.parent_field: self.kwargs['pk']}
id=kwargs['pk']) return qs.filter(**kwargs).order_by('-data_tramitacao', '-id')
doc_ace_null = ''
doc_acessorio = DocumentoAcessorioAdministrativo.objects.filter(
documento_id=kwargs['pk'])
if not doc_acessorio:
doc_ace_null = _('Nenhum documento acessório' +
'cadastrado para este processo.')
return self.render_to_response({'pk': kwargs['pk'],
'doc': doc,
'doc_ace': doc_acessorio,
'doc_ace_null': doc_ace_null,
'form': form})
def post(self, request, *args, **kwargs):
form = DocumentoAcessorioAdministrativoForm(request.POST)
if form.is_valid():
doc_ace = form.save(commit=False)
if 'arquivo' in request.FILES:
doc_ace.arquivo = request.FILES['arquivo']
doc = DocumentoAdministrativo.objects.get(
id=kwargs['pk'])
doc_ace.documento = doc
doc_ace.save()
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_success_url(self): class DetailView(DocumentoAdministrativoMixin,
pk = self.kwargs['pk'] MasterDetailCrud.DetailView):
return reverse('sapl.protocoloadm:doc_ace_adm', kwargs={'pk': pk}) pass
class TramitacaoAdmCrud(MasterDetailCrud): class DocumentoAcessorioAdministrativoCrud(MasterDetailCrud):
model = TramitacaoAdministrativo model = DocumentoAcessorioAdministrativo
parent_field = 'documento' parent_field = 'documento'
help_path = '' help_path = ''
class BaseMixin(MasterDetailCrud.BaseMixin): class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['data_tramitacao', 'unidade_tramitacao_local', list_field_names = ['nome', 'tipo',
'unidade_tramitacao_destino', 'status'] 'data', 'autor',
'assunto']
class CreateView(MasterDetailCrud.CreateView): class CreateView(MasterDetailCrud.CreateView):
form_class = TramitacaoAdmForm form_class = DocumentoAcessorioAdministrativoForm
class UpdateView(MasterDetailCrud.UpdateView): class UpdateView(MasterDetailCrud.UpdateView):
form_class = TramitacaoAdmEditForm form_class = DocumentoAcessorioAdministrativoForm
class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView): class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView):
def get_queryset(self): def get_queryset(self):
qs = super(MasterDetailCrud.ListView, self).get_queryset() qs = super(MasterDetailCrud.ListView, self).get_queryset()
kwargs = {self.crud.parent_field: self.kwargs['pk']} kwargs = {self.crud.parent_field: self.kwargs['pk']}
return qs.filter(**kwargs).order_by('-data_tramitacao', '-id') return qs.filter(**kwargs).order_by('-data', '-id')
class DetailView(DocumentoAdministrativoMixin, class DetailView(DocumentoAdministrativoMixin,
MasterDetailCrud.DetailView): MasterDetailCrud.DetailView):

0
sapl/redireciona_urls/__init__.py

8
sapl/redireciona_urls/apps.py

@ -0,0 +1,8 @@
from django import apps
from django.utils.translation import ugettext_lazy as _
class AppConfig(apps.AppConfig):
name = 'sapl.redireciona_urls'
label = 'redireciona_urls'
verbose_name = _('Redirecionador de URLs')

13
sapl/redireciona_urls/exceptions.py

@ -0,0 +1,13 @@
from django.utils.translation import ugettext as _
class UnknownUrlNameError(Exception):
def __init__(self, url_name):
self.url_name = url_name
def __str__(self):
return repr(
_("Funcionalidade")
+ " '%s' " % (self.url_name)
+ _("pode ter sido removida ou movida para outra url."))

710
sapl/redireciona_urls/tests.py

@ -0,0 +1,710 @@
from django.core.urlresolvers import reverse
from django.test import TestCase
MovedPermanentlyHTTPStatusCode = 301
EMPTY_STRING = ''
class RedirecionaURLsTests(TestCase):
def test_redireciona_index_SAPL(self):
response = self.client.get(reverse(
'sapl.redireciona_urls:redireciona_sapl_index')
)
url_e = reverse('sapl_index')
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaParlamentarTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_parlamentar'
def test_redireciona_parlamentar_list(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.parlamentares:parlamentar_list')
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_parlamentar_list_por_legislatura(self):
numero_legislatura = 123
url = reverse(self.url_pattern)
url_e = reverse('sapl.parlamentares:parlamentar_list')
url = "%s%s" % (
url,
"?hdn_num_legislatura=%s" % (numero_legislatura)
)
url_e = "%s%s" % (url_e, "?pk=%s" % numero_legislatura)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_parlamentar_detail(self):
url = reverse(self.url_pattern)
pk_parlamentar = 21
url = "%s%s" % (url, "?cod_parlamentar=%s" % (pk_parlamentar))
url_e = reverse(
'sapl.parlamentares:parlamentar_detail',
kwargs={'pk': pk_parlamentar}
)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaComissaoTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_comissao'
def test_redireciona_comissao_detail(self):
url = reverse(self.url_pattern)
pk_comissao = 21
url = "%s%s" % (url, "?cod_comissao=%s" % (pk_comissao))
url_e = reverse(
'sapl.comissoes:comissao_detail',
kwargs={'pk': pk_comissao}
)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_comissao_list(self):
url = reverse(self.url_pattern)
url_e = reverse(
'sapl.comissoes:comissao_list')
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaPautaSessaoTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_pauta_sessao_'
def test_redireciona_pauta_sessao_detail(self):
url = reverse(self.url_pattern)
pk_pauta_sessao = 21
url = "%s%s" % (url, "?cod_sessao_plen=%s" % (pk_pauta_sessao))
url_e = reverse(
'sapl.sessao:pauta_sessao_detail',
kwargs={'pk': pk_pauta_sessao}
)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_pauta_sessao_list(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.sessao:pesquisar_pauta')
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_pauta_sessao_list_por_dat_sessao_sel(self):
url = reverse(self.url_pattern)
ano_s_p = "2016"
mes_s_p = "05"
dia_s_p = "14"
data_s_p = "%s/%s/%s" % (dia_s_p, mes_s_p, ano_s_p)
url = "%s%s" % (url, "?dat_sessao_sel=%s" % data_s_p)
url_e = reverse('sapl.sessao:pesquisar_pauta')
args_e = EMPTY_STRING
args_e += "?data_inicio__year=%s" % (ano_s_p)
args_e += "&data_inicio__month=%s" % (mes_s_p.lstrip("0"))
args_e += "&data_inicio__day=%s" % (dia_s_p.lstrip("0"))
args_e += "&tipo=&salvar=Pesquisar"
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaMesaDiretoraTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_mesa_diretora'
def test_redireciona_mesa_diretora(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.parlamentares:mesa_diretora')
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaMesaDiretoraParlamentarTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_mesa_diretora_parlamentar'
def test_redireciona_mesa_diretora_parlamentar(self):
url = reverse(self.url_pattern)
pk_parlamentar = 21
url = "%s%s" % (url, "?cod_parlamentar=%s" % (pk_parlamentar))
url_e = reverse(
'sapl.parlamentares:parlamentar_detail',
kwargs={'pk': pk_parlamentar}
)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaNormasJuridicasListTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_norma_juridica_pesquisa'
def test_redireciona_norma_juridica_pesquisa_sem_parametros(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.norma:norma_pesquisa')
tipo_norma = EMPTY_STRING
numero_norma = EMPTY_STRING
ano_norma = EMPTY_STRING
periodo_inicial_aprovacao = EMPTY_STRING
periodo_final_aprovacao = EMPTY_STRING
periodo_inicial_publicacao = EMPTY_STRING
periodo_final_publicacao = EMPTY_STRING
ementa_norma = EMPTY_STRING
assuntos_norma = EMPTY_STRING
args = EMPTY_STRING
args += "?lst_tip_norma=%s" % (tipo_norma)
args += "&txt_numero=%s" % (numero_norma)
args += "&txt_ano=%s" % (ano_norma)
args += "&dt_norma=%s" % (periodo_inicial_aprovacao)
args += "&dt_norma2=%s" % (periodo_final_aprovacao)
args += "&dt_public=%s" % (periodo_inicial_publicacao)
args += "&dt_public2=%s" % (periodo_final_publicacao)
args += "&txt_assunto=%s" % (ementa_norma)
args += "&lst_assunto_norma=%s" % (assuntos_norma)
args += "&salvar=%s" % ('Pesquisar')
url = "%s%s" % (url, args)
args_e = EMPTY_STRING
args_e += "?tipo=%s" % (tipo_norma)
args_e += "&numero=%s" % (numero_norma)
args_e += "&ano=%s" % (ano_norma)
args_e += "&data_0=%s" % (periodo_inicial_aprovacao)
args_e += "&data_1=%s" % (periodo_final_aprovacao)
args_e += "&data_publicacao_0=%s" % (periodo_inicial_publicacao)
args_e += "&data_publicacao_1=%s" % (periodo_final_publicacao)
args_e += "&ementa=%s" % (ementa_norma)
args_e += "&assuntos=%s" % (assuntos_norma)
args_e += "&salvar=%s" % ('Pesquisar')
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_norma_juridica_pesquisa_por_tipo(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.norma:norma_pesquisa')
tipo_norma = '4'
numero_norma = EMPTY_STRING
ano_norma = EMPTY_STRING
periodo_inicial_aprovacao = EMPTY_STRING
periodo_final_aprovacao = EMPTY_STRING
periodo_inicial_publicacao = EMPTY_STRING
periodo_final_publicacao = EMPTY_STRING
ementa_norma = EMPTY_STRING
assuntos_norma = EMPTY_STRING
args = EMPTY_STRING
args += "?lst_tip_norma=%s" % (tipo_norma)
args += "&txt_numero=%s" % (numero_norma)
args += "&txt_ano=%s" % (ano_norma)
args += "&dt_norma=%s" % (periodo_inicial_aprovacao)
args += "&dt_norma2=%s" % (periodo_final_aprovacao)
args += "&dt_public=%s" % (periodo_inicial_publicacao)
args += "&dt_public2=%s" % (periodo_final_publicacao)
args += "&txt_assunto=%s" % (ementa_norma)
args += "&lst_assunto_norma=%s" % (assuntos_norma)
args += "&salvar=%s" % ('Pesquisar')
url = "%s%s" % (url, args)
args_e = EMPTY_STRING
args_e += "?tipo=%s" % (tipo_norma)
args_e += "&numero=%s" % (numero_norma)
args_e += "&ano=%s" % (ano_norma)
args_e += "&data_0=%s" % (periodo_inicial_aprovacao)
args_e += "&data_1=%s" % (periodo_final_aprovacao)
args_e += "&data_publicacao_0=%s" % (periodo_inicial_publicacao)
args_e += "&data_publicacao_1=%s" % (periodo_final_publicacao)
args_e += "&ementa=%s" % (ementa_norma)
args_e += "&assuntos=%s" % (assuntos_norma)
args_e += "&salvar=%s" % ('Pesquisar')
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_norma_juridica_pesquisa_por_ano(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.norma:norma_pesquisa')
tipo_norma = EMPTY_STRING
numero_norma = EMPTY_STRING
ano_norma = '2010'
periodo_inicial_aprovacao = EMPTY_STRING
periodo_final_aprovacao = EMPTY_STRING
periodo_inicial_publicacao = EMPTY_STRING
periodo_final_publicacao = EMPTY_STRING
ementa_norma = EMPTY_STRING
assuntos_norma = EMPTY_STRING
args = EMPTY_STRING
args += "?lst_tip_norma=%s" % (tipo_norma)
args += "&txt_numero=%s" % (numero_norma)
args += "&txt_ano=%s" % (ano_norma)
args += "&dt_norma=%s" % (periodo_inicial_aprovacao)
args += "&dt_norma2=%s" % (periodo_final_aprovacao)
args += "&dt_public=%s" % (periodo_inicial_publicacao)
args += "&dt_public2=%s" % (periodo_final_publicacao)
args += "&txt_assunto=%s" % (ementa_norma)
args += "&lst_assunto_norma=%s" % (assuntos_norma)
args += "&salvar=%s" % ('Pesquisar')
url = "%s%s" % (url, args)
args_e = EMPTY_STRING
args_e += "?tipo=%s" % (tipo_norma)
args_e += "&numero=%s" % (numero_norma)
args_e += "&ano=%s" % (ano_norma)
args_e += "&data_0=%s" % (periodo_inicial_aprovacao)
args_e += "&data_1=%s" % (periodo_final_aprovacao)
args_e += "&data_publicacao_0=%s" % (periodo_inicial_publicacao)
args_e += "&data_publicacao_1=%s" % (periodo_final_publicacao)
args_e += "&ementa=%s" % (ementa_norma)
args_e += "&assuntos=%s" % (assuntos_norma)
args_e += "&salvar=%s" % ('Pesquisar')
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaNormasJuridicasDetailTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_norma_juridica_detail'
def test_redireciona_norma_juridica_detail(self):
url = reverse(self.url_pattern)
pk_norma = 120
args = EMPTY_STRING
args += "?cod_norma=%s" % (pk_norma)
url = "%s%s" % (url, args)
url_e = reverse(
'sapl.norma:normajuridica_detail',
kwargs={
'pk': pk_norma}
)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_norma_juridica_detail_sem_parametros(self):
url = reverse(self.url_pattern)
pk_norma = EMPTY_STRING
args = EMPTY_STRING
args += "?cod_norma=%s" % (pk_norma)
url = "%s%s" % (url, args)
url_e = reverse('sapl.norma:norma_pesquisa')
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaSessaoPlenariaTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_sessao_plenaria_'
def test_redireciona_sessao_plenaria_detail(self):
url = reverse(self.url_pattern)
pk_sessao_plenaria = 258
url = "%s%s" % (url, "?cod_sessao_plen=%s" % (pk_sessao_plenaria))
url_e = reverse(
'sapl.sessao:sessaoplenaria_detail',
kwargs={'pk': pk_sessao_plenaria}
)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_sessao_plenaria_list_sem_parametro(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.sessao:pesquisar_sessao')
year = EMPTY_STRING
month = EMPTY_STRING
day = EMPTY_STRING
tipo_sessao = EMPTY_STRING
args = EMPTY_STRING
args += "?ano_sessao_sel=%s" % (year)
args += "&mes_sessao_sel=%s" % (month)
args += "&dia_sessao_sel=%s" % (day)
args += "&tip_sessao_sel=%s" % (tipo_sessao)
url = "%s%s" % (url, args)
# Remove zeros à esquerda
day = day.lstrip("0")
month = month.lstrip("0")
args_e = EMPTY_STRING
args_e += "?data_inicio__year=%s" % (year)
args_e += "&data_inicio__month=%s" % (month)
args_e += "&data_inicio__day=%s" % (day)
args_e += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao)
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_sessao_plenaria_list_sem_tipo(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.sessao:pesquisar_sessao')
year = '2015'
month = '04'
day = '06'
tipo_sessao = EMPTY_STRING
args = EMPTY_STRING
args += "?ano_sessao_sel=%s" % (year)
args += "&mes_sessao_sel=%s" % (month)
args += "&dia_sessao_sel=%s" % (day)
args += "&tip_sessao_sel=%s" % (tipo_sessao)
url = "%s%s" % (url, args)
# Remove zeros à esquerda
day = day.lstrip("0")
month = month.lstrip("0")
args_e = EMPTY_STRING
args_e += "?data_inicio__year=%s" % (year)
args_e += "&data_inicio__month=%s" % (month)
args_e += "&data_inicio__day=%s" % (day)
args_e += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao)
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_sessao_plenaria_list_sem_tipo_e_ano(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.sessao:pesquisar_sessao')
year = EMPTY_STRING
month = '04'
day = '06'
tipo_sessao = EMPTY_STRING
args = EMPTY_STRING
args += "?ano_sessao_sel=%s" % (year)
args += "&mes_sessao_sel=%s" % (month)
args += "&dia_sessao_sel=%s" % (day)
args += "&tip_sessao_sel=%s" % (tipo_sessao)
url = "%s%s" % (url, args)
# Remove zeros à esquerda
day = day.lstrip("0")
month = month.lstrip("0")
args_e = EMPTY_STRING
args_e += "?data_inicio__year=%s" % (year)
args_e += "&data_inicio__month=%s" % (month)
args_e += "&data_inicio__day=%s" % (day)
args_e += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao)
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_sessao_plenaria_list_sem_ano(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.sessao:pesquisar_sessao')
year = EMPTY_STRING
month = '04'
day = '06'
tipo_sessao = '4'
args = EMPTY_STRING
args += "?ano_sessao_sel=%s" % (year)
args += "&mes_sessao_sel=%s" % (month)
args += "&dia_sessao_sel=%s" % (day)
args += "&tip_sessao_sel=%s" % (tipo_sessao)
url = "%s%s" % (url, args)
# Remove zeros à esquerda
day = day.lstrip("0")
month = month.lstrip("0")
args_e = EMPTY_STRING
args_e += "?data_inicio__year=%s" % (year)
args_e += "&data_inicio__month=%s" % (month)
args_e += "&data_inicio__day=%s" % (day)
args_e += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao)
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_sessao_plenaria_list_sem_mes_dia(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.sessao:pesquisar_sessao')
year = '2015'
month = EMPTY_STRING
day = EMPTY_STRING
tipo_sessao = '4'
args = EMPTY_STRING
args += "?ano_sessao_sel=%s" % (year)
args += "&mes_sessao_sel=%s" % (month)
args += "&dia_sessao_sel=%s" % (day)
args += "&tip_sessao_sel=%s" % (tipo_sessao)
url = "%s%s" % (url, args)
# Remove zeros à esquerda
day = day.lstrip("0")
month = month.lstrip("0")
args_e = EMPTY_STRING
args_e += "?data_inicio__year=%s" % (year)
args_e += "&data_inicio__month=%s" % (month)
args_e += "&data_inicio__day=%s" % (day)
args_e += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao)
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaHistoricoTramitacoesListTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_historico_tramitacoes'
def test_redireciona_historico_tramitacoes_sem_parametros(self):
args_e = EMPTY_STRING
args = EMPTY_STRING
url = reverse(self.url_pattern)
url_e = reverse('sapl.base:historico_tramitacoes')
inicio_dt_tramitacao = EMPTY_STRING
fim_dt_tramitacao = EMPTY_STRING
tipo_materia = EMPTY_STRING
unidade_local_tramitacao = EMPTY_STRING
status_tramitacao = EMPTY_STRING
args += "?txt_dat_inicio_periodo=%s" % (inicio_dt_tramitacao)
args += "&txt_dat_fim_periodo=%s" % (fim_dt_tramitacao)
args += "&lst_tip_materia=%s" % (tipo_materia)
args += "&lst_cod_unid_tram_dest=%s" % (unidade_local_tramitacao)
args += "&lst_status=%s" % (status_tramitacao)
args += "&btn_materia_pesquisar=%s" % ('Pesquisar')
url = "%s%s" % (url, args)
# Remove zeros à esquerda
inicio_dt_tramitacao = inicio_dt_tramitacao.lstrip("0")
fim_dt_tramitacao = fim_dt_tramitacao.lstrip("0")
tipo_materia = tipo_materia.lstrip("0")
unidade_local_tramitacao = unidade_local_tramitacao.lstrip("0")
status_tramitacao = status_tramitacao.lstrip("0")
if (
(inicio_dt_tramitacao != EMPTY_STRING) or
(fim_dt_tramitacao != EMPTY_STRING) or
(tipo_materia != EMPTY_STRING) or
(unidade_local_tramitacao != EMPTY_STRING) or
(status_tramitacao != EMPTY_STRING)):
args_e += "?tramitacao__data_tramitacao_0=%s" % (
inicio_dt_tramitacao)
args_e += "&tramitacao__data_tramitacao_1=%s" % (
fim_dt_tramitacao)
args_e += "&tipo=%s" % (tipo_materia)
args_e += "&tramitacao__unidade_tramitacao_local=%s" % (
unidade_local_tramitacao)
args_e += "&tramitacao__status=%s" % (status_tramitacao)
args_e += "&salvar=%s" % ('Pesquisar')
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_historico_tramitacoes(self):
args = EMPTY_STRING
args_e = EMPTY_STRING
url = reverse(self.url_pattern)
url_e = reverse('sapl.base:historico_tramitacoes')
inicio_dt_tramitacao = '12/07/2000'
fim_dt_tramitacao = '26/05/2017'
unidade_local_tramitacao = '0'
tipo_materia = '0'
status_tramitacao = '0'
args += "?txt_dat_inicio_periodo=%s" % (inicio_dt_tramitacao)
args += "&txt_dat_fim_periodo=%s" % (fim_dt_tramitacao)
args += "&lst_tip_materia=%s" % (tipo_materia)
args += "&lst_cod_unid_tram_dest=%s" % (unidade_local_tramitacao)
args += "&lst_status=%s" % (status_tramitacao)
args += "&btn_materia_pesquisar=%s" % ('Pesquisar')
url = "%s%s" % (url, args)
# Remove zeros à esquerda
inicio_dt_tramitacao = inicio_dt_tramitacao.lstrip("0")
fim_dt_tramitacao = fim_dt_tramitacao.lstrip("0")
tipo_materia = tipo_materia.lstrip("0")
unidade_local_tramitacao = unidade_local_tramitacao.lstrip("0")
status_tramitacao = status_tramitacao.lstrip("0")
if (
(inicio_dt_tramitacao != EMPTY_STRING) or
(fim_dt_tramitacao != EMPTY_STRING) or
(tipo_materia != EMPTY_STRING) or
(unidade_local_tramitacao != EMPTY_STRING) or
(status_tramitacao != EMPTY_STRING)):
args_e += "?tramitacao__data_tramitacao_0=%s" % (
inicio_dt_tramitacao)
args_e += "&tramitacao__data_tramitacao_1=%s" % (
fim_dt_tramitacao)
args_e += "&tipo=%s" % (tipo_materia)
args_e += "&tramitacao__unidade_tramitacao_local=%s" % (
unidade_local_tramitacao)
args_e += "&tramitacao__status=%s" % (status_tramitacao)
args_e += "&salvar=%s" % ('Pesquisar')
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaPresencaParlamentaresTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_presencaparlamentar_list'
def test_redireciona_presenca_list_sem_parametros(self):
args_e = EMPTY_STRING
args = EMPTY_STRING
url = reverse(self.url_pattern)
url_e = reverse('sapl.base:presenca_sessao')
inicio_intervalo_presenca = EMPTY_STRING
fim_intervalo_presenca = EMPTY_STRING
args += "?txt_dat_inicio=%s" % (
inicio_intervalo_presenca)
args += "&txt_dat_fim=%s" % (
fim_intervalo_presenca)
url = "%s%s" % (url, args)
# Remove zeros à esquerda
inicio_intervalo_presenca = inicio_intervalo_presenca.lstrip("0")
fim_intervalo_presenca = fim_intervalo_presenca.lstrip("0")
args_e += "?data_inicio_0=%s" % (
inicio_intervalo_presenca)
args_e += "&data_inicio_1=%s" % (
fim_intervalo_presenca)
args_e += "&salvar=%s" % ('Pesquisar')
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_presenca_list(self):
args_e = EMPTY_STRING
args = EMPTY_STRING
url = reverse(self.url_pattern)
url_e = reverse('sapl.base:presenca_sessao')
inicio_intervalo_presenca = '01/02/2015'
fim_intervalo_presenca = '01/02/2017'
args += "?txt_dat_inicio=%s" % (
inicio_intervalo_presenca)
args += "&txt_dat_fim=%s" % (
fim_intervalo_presenca)
url = "%s%s" % (url, args)
# Remove zeros à esquerda
inicio_intervalo_presenca = inicio_intervalo_presenca.lstrip("0")
fim_intervalo_presenca = fim_intervalo_presenca.lstrip("0")
args_e += "?data_inicio_0=%s" % (
inicio_intervalo_presenca)
args_e += "&data_inicio_1=%s" % (
fim_intervalo_presenca)
args_e += "&salvar=%s" % ('Pesquisar')
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaMateriasPorAutorTests(TestCase):
url_pattern = 'sapl.redireciona_urls:redireciona_materias_por_autor_list'
def test_redireciona_materias_por_autor_list_sem_parametros(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.base:materia_por_autor')
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
class RedirecionaMateriasPorAnoAutorTipoTests(TestCase):
url_pattern = (
'sapl.redireciona_urls:redireciona_materia_por_ano_autor_tipo_list')
def test_redireciona_materias_por_ano_autor_tipo_list_sem_parametros(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.base:materia_por_ano_autor_tipo')
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)
def test_redireciona_materias_por_ano_autor_tipo_list(self):
url = reverse(self.url_pattern)
url_e = reverse('sapl.base:materia_por_ano_autor_tipo')
ano = 2017
args = "?ano=%s" % (ano)
url = "%s%s" % (url, args)
args_e = "?ano=%s&salvar=Pesquisar" % (ano)
url_e = "%s%s" % (url_e, args_e)
response = self.client.get(url)
self.assertEqual(response.status_code, MovedPermanentlyHTTPStatusCode)
self.assertEqual(response.url, url_e)

80
sapl/redireciona_urls/urls.py

@ -0,0 +1,80 @@
from .apps import AppConfig
from .views import (
RedirecionaAtasList,
RedirecionaComissao,
RedirecionaHistoricoTramitacoesList,
RedirecionaMateriaLegislativaDetail,
RedirecionaMateriaLegislativaList,
RedirecionaMateriasPorAnoAutorTipo,
RedirecionaMateriasPorAutor,
RedirecionaMesaDiretoraView,
RedirecionaNormasJuridicasDetail,
RedirecionaNormasJuridicasList,
RedirecionaParlamentar,
RedirecionaPautaSessao,
RedirecionaPresencaParlamentares,
RedirecionaRelatoriosList,
RedirecionaRelatoriosMateriasEmTramitacaoList,
RedirecionaSessaoPlenaria,
RedirecionaSAPLIndex)
from django.conf.urls import url
app_name = AppConfig.name
urlpatterns = [
url(r'^default_index_html$',
RedirecionaSAPLIndex.as_view(),
name='redireciona_sapl_index'),
url(r'^consultas/parlamentar/parlamentar_',
RedirecionaParlamentar.as_view(),
name='redireciona_parlamentar'),
url(r'^consultas/comissao/comissao_',
RedirecionaComissao.as_view(),
name='redireciona_comissao'),
url(r'^consultas/pauta_sessao/pauta_sessao_',
RedirecionaPautaSessao.as_view(),
name='redireciona_pauta_sessao_'),
url(r'^consultas/mesa_diretora/mesa_diretora_index_html',
RedirecionaMesaDiretoraView.as_view(),
name='redireciona_mesa_diretora'),
url(r'^consultas/mesa_diretora/parlamentar/parlamentar_',
RedirecionaParlamentar.as_view(),
name='redireciona_mesa_diretora_parlamentar'),
url(r'^consultas/sessao_plenaria/',
RedirecionaSessaoPlenaria.as_view(),
name='redireciona_sessao_plenaria_'),
url(r'^generico/norma_juridica_pesquisar_',
RedirecionaNormasJuridicasList.as_view(),
name='redireciona_norma_juridica_pesquisa'),
url(r'^consultas/norma_juridica/norma_juridica_mostrar_proc',
RedirecionaNormasJuridicasDetail.as_view(),
name='redireciona_norma_juridica_detail'),
url(r'^relatorios_administrativos/relatorios_administrativos_index_html$',
RedirecionaRelatoriosList.as_view(),
name='redireciona_relatorios_list'),
url(r'tramitacaoMaterias/tramitacaoMaterias',
RedirecionaRelatoriosMateriasEmTramitacaoList.as_view(),
name='redireciona_relatorio_materia_por_tramitacao'),
url(r'tramitacaoMaterias/materia_mostrar_proc$',
RedirecionaMateriaLegislativaDetail.as_view(),
name='redireciona_materialegislativa_detail'),
url(r'^generico/materia_pesquisar_',
RedirecionaMateriaLegislativaList.as_view(),
name='redireciona_materialegislativa_list'),
url(r'historicoTramitacoes/historicoTramitacoes',
RedirecionaHistoricoTramitacoesList.as_view(),
name='redireciona_historico_tramitacoes'),
url(r'atasSessao',
RedirecionaAtasList.as_view(),
name='redireciona_atas_list'),
url(r'presencaSessao',
RedirecionaPresencaParlamentares.as_view(),
name='redireciona_presencaparlamentar_list'),
url(r'resumoPropositurasAutor',
RedirecionaMateriasPorAutor.as_view(),
name='redireciona_materias_por_autor_list'),
url(r'propositurasAnoAutorTipo',
RedirecionaMateriasPorAnoAutorTipo.as_view(),
name='redireciona_materia_por_ano_autor_tipo_list'),
]

577
sapl/redireciona_urls/views.py

@ -0,0 +1,577 @@
from .exceptions import UnknownUrlNameError
from django.core.urlresolvers import NoReverseMatch, reverse
from django.views.generic import RedirectView
from sapl.base.apps import AppConfig as atasConfig
from sapl.base.apps import AppConfig as presenca_sessaoConfig
from sapl.base.apps import AppConfig as relatoriosConfig
from sapl.comissoes.apps import AppConfig as comissoesConfig
from sapl.materia.apps import AppConfig as materiaConfig
from sapl.norma.apps import AppConfig as normaConfig
from sapl.parlamentares.apps import AppConfig as parlamentaresConfig
from sapl.sessao.apps import AppConfig as sessaoConfig
EMPTY_STRING = ''
app_parlamentares = parlamentaresConfig.name
app_atas = atasConfig.name
app_presenca_sessao = presenca_sessaoConfig.name
app_comissoes = comissoesConfig.name
app_materia = materiaConfig.name
app_sessao = sessaoConfig.name
app_norma = normaConfig.name
app_relatorios = relatoriosConfig.name
pesquisar_atas = (app_atas + ':atas')
presenca_sessao = (app_presenca_sessao + ':presenca_sessao')
parlamentar_list = (app_parlamentares + ':parlamentar_list')
parlamentar_detail = (app_parlamentares + ':parlamentar_detail')
parlamentar_mesa_diretora = (app_parlamentares + ':mesa_diretora')
comissao_list = (app_comissoes + ':comissao_list')
comissao_detail = (app_comissoes + ':comissao_detail')
materialegislativa_detail = (app_materia + ':materialegislativa_detail')
materialegislativa_list = (app_materia + ':pesquisar_materia')
pauta_sessao_list = (app_sessao + ':pesquisar_pauta')
pauta_sessao_detail = (app_sessao + ':pauta_sessao_detail')
sessao_plenaria_list = (app_sessao + ':pesquisar_sessao')
sessao_plenaria_detail = (app_sessao + ':sessaoplenaria_detail')
norma_juridica_detail = (app_norma + ':normajuridica_detail')
norma_juridica_pesquisa = (app_norma + ':norma_pesquisa')
relatorios_list = (app_relatorios + ':relatorios_list')
relatorio_materia_por_tramitacao = (app_relatorios + ':materia_por_tramitacao')
relatorio_materia_por_autor = (app_relatorios + ':materia_por_autor')
relatorio_materia_por_ano_autor_tipo = (
app_relatorios + ':materia_por_ano_autor_tipo')
historico_tramitacoes = (app_relatorios + ':historico_tramitacoes')
class RedirecionaSAPLIndex(RedirectView):
permanent = True
def get_redirect_url(self):
url_pattern = 'sapl_index'
try:
url = reverse(url_pattern)
except NoReverseMatch:
raise UnknownUrlNameError(url_pattern)
return url
class RedirecionaParlamentar(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
pk_parlamentar = self.request.GET.get(
'cod_parlamentar',
EMPTY_STRING)
if pk_parlamentar:
try:
kwargs = {'pk': pk_parlamentar}
url = reverse(parlamentar_detail, kwargs=kwargs)
except NoReverseMatch:
raise UnknownUrlNameError(parlamentar_detail, kwargs=kwargs)
else:
try:
url = reverse(parlamentar_list)
except NoReverseMatch:
raise UnknownUrlNameError(parlamentar_list)
numero_legislatura = self.request.GET.get(
'hdn_num_legislatura',
EMPTY_STRING)
if numero_legislatura:
args = '?pk=' + numero_legislatura
url = "%s%s" % (url, args)
return url
class RedirecionaComissao(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
pk_comissao = self.request.GET.get('cod_comissao', EMPTY_STRING)
if pk_comissao:
kwargs = {'pk': pk_comissao}
try:
url = reverse(comissao_detail, kwargs=kwargs)
except NoReverseMatch:
raise UnknownUrlNameError(comissao_detail)
else:
try:
url = reverse(comissao_list)
except NoReverseMatch:
raise UnknownUrlNameError(comissao_list)
return url
class RedirecionaPautaSessao(RedirectView):
permanent = True
def get_redirect_url(self):
pk_sessao_plenaria = self.request.GET.get(
'cod_sessao_plen',
EMPTY_STRING)
if pk_sessao_plenaria:
kwargs = {'pk': pk_sessao_plenaria}
try:
url = reverse(pauta_sessao_detail, kwargs=kwargs)
except NoReverseMatch:
raise UnknownUrlNameError(pauta_sessao_detail)
else:
try:
url = reverse(pauta_sessao_list)
except NoReverseMatch:
raise UnknownUrlNameError(pauta_sessao_list)
data_sessao_plenaria = self.request.GET.get(
'dat_sessao_sel',
EMPTY_STRING)
if data_sessao_plenaria:
dia_s_p, mes_s_p, ano_s_p = data_sessao_plenaria.split('/')
# Remove zeros à esquerda de dia_s_p e mes_s_p
dia_s_p = dia_s_p.lstrip("0")
mes_s_p = mes_s_p.lstrip("0")
args = EMPTY_STRING
args += "?data_inicio__year=%s" % (ano_s_p)
args += "&data_inicio__month=%s" % (mes_s_p)
args += "&data_inicio__day=%s" % (dia_s_p)
args += "&tipo=&salvar=Pesquisar"
url = "%s%s" % (url, args)
return url
class RedirecionaSessaoPlenaria(RedirectView):
permanent = True
def get_redirect_url(self):
pk_sessao_plenaria = self.request.GET.get(
'cod_sessao_plen',
EMPTY_STRING)
url = EMPTY_STRING
if pk_sessao_plenaria:
kwargs = {'pk': pk_sessao_plenaria}
try:
url = reverse(sessao_plenaria_detail, kwargs=kwargs)
except NoReverseMatch:
raise UnknownUrlNameError(sessao_plenaria_detail)
else:
try:
url = reverse(sessao_plenaria_list)
except NoReverseMatch:
raise UnknownUrlNameError(sessao_plenaria_list)
year = self.request.GET.get(
'ano_sessao_sel',
EMPTY_STRING)
month = self.request.GET.get(
'mes_sessao_sel',
EMPTY_STRING)
day = self.request.GET.get(
'dia_sessao_sel',
EMPTY_STRING)
tipo_sessao = self.request.GET.get(
'tip_sessao_sel',
EMPTY_STRING)
# Remove zeros à esquerda
day = day.lstrip("0")
month = month.lstrip("0")
args = EMPTY_STRING
args += "?data_inicio__year=%s" % (year)
args += "&data_inicio__month=%s" % (month)
args += "&data_inicio__day=%s" % (day)
args += "&tipo=%s&salvar=Pesquisar" % (tipo_sessao)
url = "%s%s" % (url, args)
return url
class RedirecionaRelatoriosList(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
try:
url = reverse(relatorios_list)
except NoReverseMatch:
raise UnknownUrlNameError(relatorios_list)
return url
class RedirecionaRelatoriosMateriasEmTramitacaoList(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
try:
url = reverse(relatorio_materia_por_tramitacao)
except NoReverseMatch:
raise UnknownUrlNameError(relatorio_materia_por_tramitacao)
year = self.request.GET.get(
'selAno',
EMPTY_STRING)
if year:
tramitacao_tipo = self.request.GET.get(
'lst_tip_materia',
EMPTY_STRING)
tramitacao_unidade_local = self.request.GET.get(
'lst_cod_unid_tram_dest',
EMPTY_STRING)
tramitacao_status = self.request.GET.get(
'lst_status',
EMPTY_STRING)
salvar = self.request.GET.get(
'btn_materia_pesquisar',
'Pesquisar')
tramitacao_tipo = tramitacao_tipo.lstrip("0")
tramitacao_unidade_local = tramitacao_unidade_local.lstrip("0")
tramitacao_status = tramitacao_status.lstrip("0")
args = EMPTY_STRING
args += "?ano=%s" % (year)
args += "&tipo=%s" % (tramitacao_tipo)
args += "&tramitacao__unidade_tramitacao_local=%s" % (
tramitacao_unidade_local)
args += "&tramitacao__status=%s" % (tramitacao_status)
args += "&salvar=%s" % (salvar)
url = "%s%s" % (url, args)
return url
class RedirecionaMateriaLegislativaDetail(RedirectView):
permanent = True
def get_redirect_url(self):
pk = self.request.GET.get('cod_materia', EMPTY_STRING)
if pk:
kwargs = {'pk': pk}
return reverse(materialegislativa_detail, kwargs=kwargs)
else:
return reverse(materialegislativa_list)
class RedirecionaMateriaLegislativaList(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
args = EMPTY_STRING
try:
url = reverse(materialegislativa_list)
except NoReverseMatch:
raise UnknownUrlNameError(materialegislativa_list)
tipo_materia = self.request.GET.get(
'lst_tip_materia',
EMPTY_STRING)
numero_materia = self.request.GET.get(
'txt_numero',
EMPTY_STRING)
ano_materia = self.request.GET.get(
'txt_ano',
EMPTY_STRING)
num_protocolo_materia = self.request.GET.get(
'txt_num_protocolo',
EMPTY_STRING)
periodo_inicial_apresentacao = self.request.GET.get(
'dt_apres',
EMPTY_STRING)
periodo_final_apresentacao = self.request.GET.get(
'dt_apres2',
EMPTY_STRING)
periodo_inicial_publicacao = self.request.GET.get(
'dt_public',
EMPTY_STRING)
periodo_final_publicacao = self.request.GET.get(
'dt_public2',
EMPTY_STRING)
tipo_autor = self.request.GET.get(
'lst_tip_autor',
EMPTY_STRING)
ementa_materia = self.request.GET.get(
'txt_assunto',
EMPTY_STRING)
tramitando = self.request.GET.get(
'rad_tramitando',
EMPTY_STRING)
status_tramitacao = self.request.GET.get(
'lst_status',
EMPTY_STRING)
args += "?tipo=%s" % (tipo_materia)
args += "&numero=%s" % (numero_materia)
args += "&ano=%s" % (ano_materia)
args += "&numero_protocolo=%s" % (num_protocolo_materia)
args += "&data_apresentacao_0=%s" % (periodo_inicial_apresentacao)
args += "&data_apresentacao_1=%s" % (periodo_final_apresentacao)
args += "&data_publicacao_0=%s" % (periodo_inicial_publicacao)
args += "&data_publicacao_1=%s" % (periodo_final_publicacao)
args += "&autoria__autor=%s" % (EMPTY_STRING)
args += "&autoria__autor__tipo=%s" % (tipo_autor)
args += "&relatoria__parlamentar_id=%s" % (EMPTY_STRING)
args += "&local_origem_externa=%s" % (EMPTY_STRING)
args += "&tramitacao__unidade_tramitacao_destino=%s" % (EMPTY_STRING)
args += "&tramitacao__status=%s" % (status_tramitacao)
args += "&em_tramitacao=%s" % (tramitando)
args += "&o=%s" % (EMPTY_STRING)
args += "&materiaassunto__assunto=%s" % (EMPTY_STRING)
args += "&ementa=%s" % (ementa_materia)
args += "&salvar=%s" % ('Pesquisar') # Default in both SAPL version
url = "%s%s" % (url, args)
return url
class RedirecionaMesaDiretoraView(RedirectView):
permanent = True
def get_redirect_url(self):
try:
url = reverse(parlamentar_mesa_diretora)
except NoReverseMatch:
raise UnknownUrlNameError(parlamentar_mesa_diretora)
return url
class RedirecionaNormasJuridicasDetail(RedirectView):
permanent = True
def get_redirect_url(self):
pk_norma = self.request.GET.get('cod_norma', EMPTY_STRING)
if pk_norma:
kwargs = {'pk': pk_norma}
return reverse(norma_juridica_detail, kwargs=kwargs)
else:
return reverse(norma_juridica_pesquisa)
class RedirecionaNormasJuridicasList(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
args = EMPTY_STRING
try:
url = reverse(norma_juridica_pesquisa)
except NoReverseMatch:
raise UnknownUrlNameError(norma_juridica_pesquisa)
tipo_norma = self.request.GET.get(
'lst_tip_norma',
EMPTY_STRING)
numero_norma = self.request.GET.get(
'txt_numero',
EMPTY_STRING)
ano_norma = self.request.GET.get(
'txt_ano',
EMPTY_STRING)
periodo_inicial_aprovacao = self.request.GET.get(
'dt_norma',
EMPTY_STRING)
periodo_final_aprovacao = self.request.GET.get(
'dt_norma2',
EMPTY_STRING)
periodo_inicial_publicacao = self.request.GET.get(
'dt_public',
EMPTY_STRING)
periodo_final_publicacao = self.request.GET.get(
'dt_public2',
EMPTY_STRING)
ementa_norma = self.request.GET.get(
'txt_assunto',
EMPTY_STRING)
assuntos_norma = self.request.GET.get(
'lst_assunto_norma',
EMPTY_STRING)
args += "?tipo=%s" % (tipo_norma)
args += "&numero=%s" % (numero_norma)
args += "&ano=%s" % (ano_norma)
args += "&data_0=%s" % (periodo_inicial_aprovacao)
args += "&data_1=%s" % (periodo_final_aprovacao)
args += "&data_publicacao_0=%s" % (periodo_inicial_publicacao)
args += "&data_publicacao_1=%s" % (periodo_final_publicacao)
args += "&ementa=%s" % (ementa_norma)
args += "&assuntos=%s" % (assuntos_norma)
args += "&salvar=%s" % ('Pesquisar') # Default in both SAPL version
url = "%s%s" % (url, args)
return url
class RedirecionaHistoricoTramitacoesList(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
args = EMPTY_STRING
try:
url = reverse(historico_tramitacoes)
except NoReverseMatch:
raise UnknownUrlNameError(historico_tramitacoes)
inicio_intervalo_data_tramitacao = self.request.GET.get(
'txt_dat_inicio_periodo',
EMPTY_STRING
).lstrip("0")
fim_intervalo_data_tramitacao = self.request.GET.get(
'txt_dat_fim_periodo',
EMPTY_STRING
).lstrip("0")
tipo_materia = self.request.GET.get(
'lst_tip_materia',
EMPTY_STRING
).lstrip("0")
unidade_local_tramitacao = self.request.GET.get(
'lst_cod_unid_tram_dest',
EMPTY_STRING
).lstrip("0")
status_tramitacao = self.request.GET.get(
'lst_status',
EMPTY_STRING
).lstrip("0")
if (
(inicio_intervalo_data_tramitacao != EMPTY_STRING) or
(fim_intervalo_data_tramitacao != EMPTY_STRING) or
(tipo_materia != EMPTY_STRING) or
(unidade_local_tramitacao != EMPTY_STRING) or
(status_tramitacao != EMPTY_STRING)):
args += "?tramitacao__data_tramitacao_0=%s" % (
inicio_intervalo_data_tramitacao)
args += "&tramitacao__data_tramitacao_1=%s" % (
fim_intervalo_data_tramitacao)
args += "&tipo=%s" % (tipo_materia)
args += "&tramitacao__unidade_tramitacao_local=%s" % (
unidade_local_tramitacao)
args += "&tramitacao__status=%s" % (status_tramitacao)
args += "&salvar=%s" % ('Pesquisar')
url = "%s%s" % (url, args)
return url
class RedirecionaAtasList(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
args = EMPTY_STRING
try:
url = reverse(pesquisar_atas)
except NoReverseMatch:
raise UnknownUrlNameError(pesquisar_atas)
inicio_intervalo_data_ata = self.request.GET.get(
'txt_dat_inicio',
EMPTY_STRING
).lstrip("0")
fim_intervalo_data_ata = self.request.GET.get(
'txt_dat_fim',
EMPTY_STRING
).lstrip("0")
args += "?data_inicio_0=%s" % (
inicio_intervalo_data_ata)
args += "&data_inicio_1=%s" % (
fim_intervalo_data_ata)
args += "&salvar=%s" % ('Pesquisar')
url = "%s%s" % (url, args)
return url
class RedirecionaPresencaParlamentares(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
args = EMPTY_STRING
try:
url = reverse(presenca_sessao)
except NoReverseMatch:
raise UnknownUrlNameError(presenca_sessao)
inicio_intervalo_data_presenca_parlamentar = self.request.GET.get(
'txt_dat_inicio',
EMPTY_STRING
).lstrip("0")
fim_intervalo_data_presenca_parlamentar = self.request.GET.get(
'txt_dat_fim',
EMPTY_STRING
).lstrip("0")
args += "?data_inicio_0=%s" % (
inicio_intervalo_data_presenca_parlamentar)
args += "&data_inicio_1=%s" % (
fim_intervalo_data_presenca_parlamentar)
args += "&salvar=%s" % ('Pesquisar')
url = "%s%s" % (url, args)
return url
class RedirecionaMateriasPorAutor(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
try:
url = reverse(relatorio_materia_por_autor)
except NoReverseMatch:
raise UnknownUrlNameError(relatorio_materia_por_autor)
return url
class RedirecionaMateriasPorAnoAutorTipo(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
ano = self.request.GET.get('ano', '')
try:
url = reverse(relatorio_materia_por_ano_autor_tipo)
except NoReverseMatch:
raise UnknownUrlNameError(relatorio_materia_por_ano_autor_tipo)
if ano:
args = "?ano=%s" % (ano)
args += "&salvar=%s" % ('Pesquisar')
url = "%s%s" % (url, args)
return url

30
sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py

@ -6,6 +6,8 @@
""" """
import time import time
from sapl.sessao.models import ResumoOrdenacao
from trml2pdf import parseString from trml2pdf import parseString
@ -287,6 +289,33 @@ def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_me
tmp += '\t</template>\n' tmp += '\t</template>\n'
tmp += paraStyle() tmp += paraStyle()
tmp += '\t<story>\n' tmp += '\t<story>\n'
ordenacao = ResumoOrdenacao.objects.first()
dict_ord_template = {
'cont_mult': '',
'exp': expedientes(lst_expedientes),
'id_basica': inf_basicas(inf_basicas_dic),
'lista_p': presenca(lst_presenca_sessao),
'lista_p_o_d': presenca_ordem_dia(lst_presenca_ordem_dia),
'mat_exp': expediente_materia(lst_expediente_materia),
'mat_o_d': votacao(lst_votacao),
'mesa_d': mesa(lst_mesa),
'oradores_exped': oradores_expediente(lst_oradores_expediente),
'oradores_expli': oradores(lst_oradores)
}
if ordenacao:
tmp += dict_ord_template[ordenacao.primeiro]
tmp += dict_ord_template[ordenacao.segundo]
tmp += dict_ord_template[ordenacao.terceiro]
tmp += dict_ord_template[ordenacao.quarto]
tmp += dict_ord_template[ordenacao.quinto]
tmp += dict_ord_template[ordenacao.sexto]
tmp += dict_ord_template[ordenacao.setimo]
tmp += dict_ord_template[ordenacao.oitavo]
tmp += dict_ord_template[ordenacao.nono]
tmp += dict_ord_template[ordenacao.decimo]
else:
tmp += inf_basicas(inf_basicas_dic) tmp += inf_basicas(inf_basicas_dic)
tmp += mesa(lst_mesa) tmp += mesa(lst_mesa)
tmp += presenca(lst_presenca_sessao) tmp += presenca(lst_presenca_sessao)
@ -296,6 +325,7 @@ def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_me
tmp += presenca_ordem_dia(lst_presenca_ordem_dia) tmp += presenca_ordem_dia(lst_presenca_ordem_dia)
tmp += votacao(lst_votacao) tmp += votacao(lst_votacao)
tmp += oradores(lst_oradores) tmp += oradores(lst_oradores)
tmp += '\t</story>\n' tmp += '\t</story>\n'
tmp += '</document>\n' tmp += '</document>\n'

51
sapl/relatorios/views.py

@ -1,7 +1,6 @@
from datetime import datetime
import re
import html import html
import re
from datetime import datetime
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
@ -494,20 +493,21 @@ def get_sessao_plenaria(sessao, casa):
dic_mesa = {} dic_mesa = {}
dic_mesa['nom_parlamentar'] = parlamentar.nome_parlamentar dic_mesa['nom_parlamentar'] = parlamentar.nome_parlamentar
partido_sigla = Filiacao.objects.filter( partido_sigla = Filiacao.objects.filter(
parlamentar=parlamentar).first().partido.sigla parlamentar=parlamentar).first()
if not partido_sigla: if not partido_sigla:
partido_sigla = '' sigla = ''
dic_mesa['sgl_partido'] = partido_sigla else:
sigla = partido_sigla.partido.sigla
dic_mesa['sgl_partido'] = sigla
dic_mesa['des_cargo'] = cargo.descricao dic_mesa['des_cargo'] = cargo.descricao
lst_mesa.append(dic_mesa) lst_mesa.append(dic_mesa)
# Lista de presença na sessão # Lista de presença na sessão
lst_presenca_sessao = [] lst_presenca_sessao = []
for presenca in SessaoPlenariaPresenca.objects.filter( presenca = SessaoPlenariaPresenca.objects.filter(
sessao_plenaria=sessao): sessao_plenaria=sessao).order_by('parlamentar__nome_parlamentar')
for parlamentar in Parlamentar.objects.filter( for parlamentar in [p.parlamentar for p in presenca]:
id=presenca.parlamentar.id):
dic_presenca = {} dic_presenca = {}
dic_presenca["nom_parlamentar"] = parlamentar.nome_parlamentar dic_presenca["nom_parlamentar"] = parlamentar.nome_parlamentar
partido = Filiacao.objects.filter( partido = Filiacao.objects.filter(
@ -635,26 +635,29 @@ def get_sessao_plenaria(sessao, casa):
dic_oradores_expediente["nom_parlamentar"] = ( dic_oradores_expediente["nom_parlamentar"] = (
parlamentar.nome_parlamentar) parlamentar.nome_parlamentar)
partido_sigla = Filiacao.objects.filter( partido_sigla = Filiacao.objects.filter(
parlamentar=parlamentar).first().partido.sigla parlamentar=parlamentar).first()
if not partido_sigla: if not partido_sigla:
partido_sigla = '' sigla = ''
dic_oradores_expediente['sgl_partido'] = partido_sigla else:
sigla = partido_sigla.partido.sigla
dic_oradores_expediente['sgl_partido'] = sigla
lst_oradores_expediente.append(dic_oradores_expediente) lst_oradores_expediente.append(dic_oradores_expediente)
# Lista presença na ordem do dia # Lista presença na ordem do dia
lst_presenca_ordem_dia = [] lst_presenca_ordem_dia = []
for presenca_ordem_dia in PresencaOrdemDia.objects.filter( presenca_ordem_dia = PresencaOrdemDia.objects.filter(
sessao_plenaria=sessao): sessao_plenaria=sessao).order_by('parlamentar__nome_parlamentar')
for parlamentar in Parlamentar.objects.filter( for parlamentar in [p.parlamentar for p in presenca_ordem_dia]:
id=presenca_ordem_dia.parlamentar.id):
dic_presenca_ordem_dia = {} dic_presenca_ordem_dia = {}
dic_presenca_ordem_dia['nom_parlamentar'] = ( dic_presenca_ordem_dia['nom_parlamentar'] = (
parlamentar.nome_parlamentar) parlamentar.nome_parlamentar)
partido_sigla = Filiacao.objects.filter( partido_sigla = Filiacao.objects.filter(
parlamentar=parlamentar).first().partido.sigla parlamentar=parlamentar).first()
if not partido_sigla: if not partido_sigla:
partido_sigla = '' sigla = ''
dic_presenca_ordem_dia['sgl_partido'] = partido_sigla else:
sigla = partido_sigla.partido.sigla
dic_presenca_ordem_dia['sgl_partido'] = sigla
lst_presenca_ordem_dia.append(dic_presenca_ordem_dia) lst_presenca_ordem_dia.append(dic_presenca_ordem_dia)
# Lista das matérias da Ordem do Dia, incluindo o resultado das votacoes # Lista das matérias da Ordem do Dia, incluindo o resultado das votacoes
@ -746,10 +749,12 @@ def get_sessao_plenaria(sessao, casa):
dic_oradores["num_ordem"] = orador.numero_ordem dic_oradores["num_ordem"] = orador.numero_ordem
dic_oradores["nom_parlamentar"] = parlamentar.nome_parlamentar dic_oradores["nom_parlamentar"] = parlamentar.nome_parlamentar
partido_sigla = Filiacao.objects.filter( partido_sigla = Filiacao.objects.filter(
parlamentar=parlamentar).first().partido.sigla parlamentar=parlamentar).first()
if not partido_sigla: if not partido_sigla:
partido_sigla = '' sigla = ''
dic_oradores['sgl_partido'] = partido_sigla else:
sigla = partido_sigla.partido.sigla
dic_oradores['sgl_partido'] = sigla
lst_oradores.append(dic_oradores) lst_oradores.append(dic_oradores)
return (inf_basicas_dic, return (inf_basicas_dic,

3
sapl/rules/map_rules.py

@ -152,8 +152,6 @@ rules_group_sessao = {
(sessao.PresencaOrdemDia, __base__), (sessao.PresencaOrdemDia, __base__),
(sessao.RegistroVotacao, __base__), (sessao.RegistroVotacao, __base__),
(sessao.VotoParlamentar, __base__), (sessao.VotoParlamentar, __base__),
(sessao.VotoNominal, __base__),
] ]
} }
@ -251,6 +249,7 @@ rules_group_geral = {
(sessao.TipoResultadoVotacao, __base__), (sessao.TipoResultadoVotacao, __base__),
(sessao.TipoExpediente, __base__), (sessao.TipoExpediente, __base__),
(sessao.Bloco, __base__), (sessao.Bloco, __base__),
(sessao.ResumoOrdenacao, __base__),
(lexml.LexmlProvedor, __base__), (lexml.LexmlProvedor, __base__),
(lexml.LexmlPublicador, __base__), (lexml.LexmlPublicador, __base__),

4
sapl/rules/tests/test_rules.py

@ -6,8 +6,8 @@ from django.contrib.contenttypes.models import ContentType
from django.utils import six from django.utils import six
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from sapl.base.models import (CasaLegislativa, ProblemaMigracao, Argumento, from sapl.base.models import (Argumento, CasaLegislativa, Constraint,
Constraint) ProblemaMigracao)
from sapl.compilacao.models import (PerfilEstruturalTextoArticulado, from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
TipoDispositivo, TipoDispositivo,
TipoDispositivoRelationship) TipoDispositivoRelationship)

139
sapl/sessao/forms.py

@ -28,17 +28,30 @@ def recupera_anos():
# apos a adicao do .dates(), por isso o reversed() abaixo # apos a adicao do .dates(), por isso o reversed() abaixo
anos = [(k.year, k.year) for k in reversed(anos_list)] anos = [(k.year, k.year) for k in reversed(anos_list)]
return anos return anos
except: except Exception:
return [] return []
def ANO_CHOICES(): def ANO_CHOICES():
return [('', '---------')] + recupera_anos() return [('', '---------')] + recupera_anos()
MES_CHOICES = [('', '---------')] + RANGE_MESES MES_CHOICES = [('', '---------')] + RANGE_MESES
DIA_CHOICES = [('', '---------')] + RANGE_DIAS_MES 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')]
class BancadaForm(ModelForm): class BancadaForm(ModelForm):
class Meta: class Meta:
@ -47,6 +60,8 @@ class BancadaForm(ModelForm):
'data_extincao', 'descricao'] 'data_extincao', 'descricao']
def clean(self): def clean(self):
super(BancadaForm, self).clean()
if self.cleaned_data['data_extincao']: if self.cleaned_data['data_extincao']:
if (self.cleaned_data['data_extincao'] < if (self.cleaned_data['data_extincao'] <
self.cleaned_data['data_criacao']): self.cleaned_data['data_criacao']):
@ -57,6 +72,8 @@ class BancadaForm(ModelForm):
class ExpedienteMateriaForm(ModelForm): class ExpedienteMateriaForm(ModelForm):
_model = ExpedienteMateria
tipo_materia = forms.ModelChoiceField( tipo_materia = forms.ModelChoiceField(
label=_('Tipo Matéria'), label=_('Tipo Matéria'),
required=True, required=True,
@ -87,7 +104,7 @@ class ExpedienteMateriaForm(ModelForm):
sessao_plenaria=sessao, sessao_plenaria=sessao,
numero_ordem=self.cleaned_data['numero_ordem']).exists() numero_ordem=self.cleaned_data['numero_ordem']).exists()
if numero_ordem_exists: if numero_ordem_exists and not self.instance.pk:
msg = _('Esse número de ordem já existe.') msg = _('Esse número de ordem já existe.')
raise ValidationError(msg) raise ValidationError(msg)
@ -97,6 +114,8 @@ class ExpedienteMateriaForm(ModelForm):
return self.instance.sessao_plenaria.data_inicio return self.instance.sessao_plenaria.data_inicio
def clean(self): def clean(self):
super(ExpedienteMateriaForm, self).clean()
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
sessao = self.instance.sessao_plenaria sessao = self.instance.sessao_plenaria
@ -112,11 +131,11 @@ class ExpedienteMateriaForm(ModelForm):
else: else:
cleaned_data['materia'] = materia cleaned_data['materia'] = materia
ex = ExpedienteMateria.objects.filter( exists = self._model.objects.filter(
sessao_plenaria=sessao, sessao_plenaria=sessao,
materia=materia).count() materia=materia).exists()
if ex >= 1: if exists and not self.instance.pk:
msg = _('Essa matéria já foi cadastrada.') msg = _('Essa matéria já foi cadastrada.')
raise ValidationError(msg) raise ValidationError(msg)
@ -131,6 +150,8 @@ class ExpedienteMateriaForm(ModelForm):
class OrdemDiaForm(ExpedienteMateriaForm): class OrdemDiaForm(ExpedienteMateriaForm):
_model = OrdemDia
class Meta: class Meta:
model = OrdemDia model = OrdemDia
fields = ['data_ordem', 'numero_ordem', 'tipo_materia', 'observacao', fields = ['data_ordem', 'numero_ordem', 'tipo_materia', 'observacao',
@ -139,47 +160,22 @@ class OrdemDiaForm(ExpedienteMateriaForm):
def clean_data_ordem(self): def clean_data_ordem(self):
return self.instance.sessao_plenaria.data_inicio return self.instance.sessao_plenaria.data_inicio
def clean_numero_ordem(self): def clean_numero_ordem(self):
sessao = self.instance.sessao_plenaria sessao = self.instance.sessao_plenaria
numero_ordem_exists = OrdemDia.objects.filter( numero_ordem_exists = OrdemDia.objects.filter(
sessao_plenaria=sessao, sessao_plenaria=sessao,
numero_ordem=self.cleaned_data[ numero_ordem=self.cleaned_data['numero_ordem']).exists()
'numero_ordem']).exists()
if numero_ordem_exists: if numero_ordem_exists and not self.instance.pk:
msg = _('Esse número de ordem já existe.') msg = _('Esse número de ordem já existe.')
raise ValidationError(msg) raise ValidationError(msg)
return self.cleaned_data['numero_ordem'] return self.cleaned_data['numero_ordem']
def clean(self): def clean(self):
cleaned_data = self.cleaned_data super(OrdemDiaForm, self).clean()
sessao = self.instance.sessao_plenaria return self.cleaned_data
try:
materia = MateriaLegislativa.objects.get(
numero=self.cleaned_data['numero_materia'],
ano=self.cleaned_data['ano_materia'],
tipo=self.cleaned_data['tipo_materia'])
except ObjectDoesNotExist:
msg = _('A matéria a ser inclusa não existe no cadastro'
' de matérias legislativas.')
raise ValidationError(msg)
else:
cleaned_data['materia'] = materia
ex = ExpedienteMateria.objects.filter(
sessao_plenaria=sessao,
materia=materia).count()
if ex >= 1:
msg = _('Essa matéria já foi cadastrada.')
raise ValidationError(msg)
return cleaned_data
def save(self, commit=False): def save(self, commit=False):
ordem = super(OrdemDiaForm, self).save(commit) ordem = super(OrdemDiaForm, self).save(commit)
@ -224,13 +220,13 @@ class VotacaoEditForm(forms.Form):
class SessaoPlenariaFilterSet(django_filters.FilterSet): class SessaoPlenariaFilterSet(django_filters.FilterSet):
data_inicio__year = django_filters.ChoiceFilter(required=False, data_inicio__year = django_filters.ChoiceFilter(required=False,
label=u'Ano', label='Ano',
choices=ANO_CHOICES) choices=ANO_CHOICES)
data_inicio__month = django_filters.ChoiceFilter(required=False, data_inicio__month = django_filters.ChoiceFilter(required=False,
label=u'Mês', label='Mês',
choices=MES_CHOICES) choices=MES_CHOICES)
data_inicio__day = django_filters.ChoiceFilter(required=False, data_inicio__day = django_filters.ChoiceFilter(required=False,
label=u'Dia', label='Dia',
choices=DIA_CHOICES) choices=DIA_CHOICES)
titulo = _('Pesquisa de Sessão Plenária') titulo = _('Pesquisa de Sessão Plenária')
@ -359,3 +355,72 @@ class OradorExpedienteForm(ModelForm):
class PautaSessaoFilterSet(SessaoPlenariaFilterSet): class PautaSessaoFilterSet(SessaoPlenariaFilterSet):
titulo = _('Pesquisa de Pauta de Sessão') titulo = _('Pesquisa de Pauta de Sessão')
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)
def __init__(self, *args, **kwargs):
super(ResumoOrdenacaoForm, self).__init__(*args, **kwargs)
row1 = to_row(
[('primeiro', 12)])
row2 = to_row(
[('segundo', 12)])
row3 = to_row(
[('terceiro', 12)])
row4 = to_row(
[('quarto', 12)])
row5 = to_row(
[('quinto', 12)])
row6 = to_row(
[('sexto', 12)])
row7 = to_row(
[('setimo', 12)])
row8 = to_row(
[('oitavo', 12)])
row9 = to_row(
[('nono', 12)])
row10 = to_row(
[('decimo', 12)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_(''),
row1, row2, row3, row4, row5,
row6, row7, row8, row9, row10,
form_actions(save_label='Atualizar'))
)
def clean(self):
super(ResumoOrdenacaoForm, self).clean()
cleaned_data = self.cleaned_data
for c1 in cleaned_data:
i = 0
for c2 in cleaned_data:
if cleaned_data[str(c1)] == cleaned_data[str(c2)]:
i = i + 1
if i > 1:
raise ValidationError(_(
'Não é possível ter campos repetidos'))

35
sapl/sessao/migrations/0003_resumoordenacao.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-05-22 10:51
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sessao', '0002_sessaoplenaria_interativa'),
]
operations = [
migrations.CreateModel(
name='ResumoOrdenacao',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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)),
],
options={
'verbose_name': 'Ordenação do Resumo de uma Sessão',
'verbose_name_plural': 'Ordenação do Resumo de uma Sessão',
},
),
]

21
sapl/sessao/migrations/0004_votonominal_registro_votacao.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-06-01 11:06
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sessao', '0003_resumoordenacao'),
]
operations = [
migrations.AddField(
model_name='votonominal',
name='registro_votacao',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.RegistroVotacao'),
),
]

39
sapl/sessao/migrations/0005_auto_20170601_1246.py

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-06-01 12:46
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from datetime import datetime
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('sessao', '0004_votonominal_registro_votacao'),
]
operations = [
migrations.RemoveField(
model_name='votonominal',
name='registro_votacao',
),
migrations.AddField(
model_name='votoparlamentar',
name='data_hora',
field=models.DateTimeField(auto_now_add=True, blank=True, null=True, verbose_name='Data/Hora'),
preserve_default=False,
),
migrations.AddField(
model_name='votoparlamentar',
name='ip',
field=models.CharField(blank=True, max_length=30, null=True, verbose_name='IP'),
),
migrations.AddField(
model_name='votoparlamentar',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
]

26
sapl/sessao/migrations/0006_auto_20170601_1257.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-06-01 12:57
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sessao', '0005_auto_20170601_1246'),
]
operations = [
migrations.AddField(
model_name='votonominal',
name='votacao',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.RegistroVotacao'),
),
migrations.AlterField(
model_name='votoparlamentar',
name='votacao',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.RegistroVotacao'),
),
]

26
sapl/sessao/migrations/0007_auto_20170606_1238.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-06-06 12:38
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sessao', '0006_auto_20170601_1257'),
]
operations = [
migrations.AddField(
model_name='votoparlamentar',
name='expediente',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.ExpedienteMateria'),
),
migrations.AddField(
model_name='votoparlamentar',
name='ordem',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sessao.OrdemDia'),
),
]

38
sapl/sessao/migrations/0008_auto_20170607_1220.py

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2017-06-07 12:20
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('sessao', '0007_auto_20170606_1238'),
]
operations = [
migrations.RemoveField(
model_name='votonominal',
name='materia',
),
migrations.RemoveField(
model_name='votonominal',
name='parlamentar',
),
migrations.RemoveField(
model_name='votonominal',
name='sessao',
),
migrations.RemoveField(
model_name='votonominal',
name='user',
),
migrations.RemoveField(
model_name='votonominal',
name='votacao',
),
migrations.DeleteModel(
name='VotoNominal',
),
]

20
sapl/sessao/migrations/0009_auto_20170619_1441.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2017-06-19 14:41
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sessao', '0008_auto_20170607_1220'),
]
operations = [
migrations.AlterField(
model_name='votoparlamentar',
name='ip',
field=models.CharField(blank=True, default='', max_length=30, verbose_name='IP'),
),
]

87
sapl/sessao/models.py

@ -86,17 +86,19 @@ def get_sessao_media_path(instance, subpath, filename):
def pauta_upload_path(instance, filename): def pauta_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='pauta') return texto_upload_path(
instance, filename, subpath='pauta', pk_first=True)
# return get_sessao_media_path(instance, 'pauta', filename) # return get_sessao_media_path(instance, 'pauta', filename)
def ata_upload_path(instance, filename): def ata_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='ata') return texto_upload_path(instance, filename, subpath='ata', pk_first=True)
# return get_sessao_media_path(instance, 'ata', filename) # return get_sessao_media_path(instance, 'ata', filename)
def anexo_upload_path(instance, filename): def anexo_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='anexo') return texto_upload_path(
instance, filename, subpath='anexo', pk_first=True)
# return get_sessao_media_path(instance, 'anexo', filename) # return get_sessao_media_path(instance, 'anexo', filename)
@ -412,11 +414,39 @@ class RegistroVotacao(models.Model):
@reversion.register() @reversion.register()
class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar
votacao = models.ForeignKey(RegistroVotacao, on_delete=models.PROTECT) '''
As colunas ordem e expediente são redundantes, levando em consideração
que RegistroVotacao possui ordem/expediente. Entretanto, para
viabilizar a votação interativa, uma vez que ela é feita antes de haver
um RegistroVotacao, é preciso identificar o voto por ordem/expediente.
'''
votacao = models.ForeignKey(RegistroVotacao,
blank=True,
null=True)
parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT) parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT)
# XXX change to restricted choices
voto = models.CharField(max_length=10) voto = models.CharField(max_length=10)
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)
ordem = models.ForeignKey(OrdemDia,
blank=True,
null=True)
expediente = models.ForeignKey(ExpedienteMateria,
blank=True,
null=True)
class Meta: class Meta:
verbose_name = _('Registro de Votação de Parlamentar') verbose_name = _('Registro de Votação de Parlamentar')
verbose_name_plural = _('Registros de Votações de Parlamentares') verbose_name_plural = _('Registros de Votações de Parlamentares')
@ -426,28 +456,6 @@ class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar
'votacao': self.votacao, 'parlamentar': self.parlamentar} 'votacao': self.votacao, 'parlamentar': self.parlamentar}
@reversion.register()
class VotoNominal(models.Model):
parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT)
voto = models.CharField(verbose_name=_('Voto'), max_length=10)
sessao = models.ForeignKey(SessaoPlenaria, on_delete=models.PROTECT)
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.PROTECT)
user = models.ForeignKey(get_settings_auth_user_model(),
on_delete=models.PROTECT)
ip = models.CharField(verbose_name=_('IP'), max_length=30)
data_hora = models.DateTimeField(
verbose_name=_('Data/Hora'), auto_now_add=True)
class Meta:
verbose_name = _('Registro do Voto do Parlamentar')
verbose_name_plural = _('Registros dos Votos dos Parlamentares')
def __str__(self):
return '%s - %s' % (self.parlamentar.nome_parlamentar, self.voto)
@reversion.register() @reversion.register()
class SessaoPlenariaPresenca(models.Model): class SessaoPlenariaPresenca(models.Model):
sessao_plenaria = models.ForeignKey(SessaoPlenaria, sessao_plenaria = models.ForeignKey(SessaoPlenaria,
@ -493,3 +501,28 @@ class Bloco(models.Model):
def __str__(self): def __str__(self):
return self.nome return self.nome
@reversion.register()
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)
class Meta:
verbose_name = _('Ordenação do Resumo de uma Sessão')
verbose_name_plural = _('Ordenação do Resumo de uma Sessão')
def __str__(self):
return 'Ordenação do Resumo de uma Sessão'

1
sapl/sessao/serializers.py

@ -2,6 +2,7 @@ from rest_framework import serializers
from .models import SessaoPlenaria from .models import SessaoPlenaria
class SessaoPlenariaSerializer(serializers.Serializer): class SessaoPlenariaSerializer(serializers.Serializer):
class Meta: class Meta:
model = SessaoPlenaria model = SessaoPlenaria

30
sapl/sessao/urls.py

@ -10,19 +10,22 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente,
PautaSessaoDetailView, PautaSessaoListView, PautaSessaoDetailView, PautaSessaoListView,
PesquisarPautaSessaoView, PesquisarPautaSessaoView,
PesquisarSessaoPlenariaView, PesquisarSessaoPlenariaView,
PresencaOrdemDiaView, PresencaView, ResumoView, PresencaOrdemDiaView, PresencaView,
SessaoCrud, TipoExpedienteCrud, ResumoOrdenacaoView, ResumoView, SessaoCrud,
TipoResultadoVotacaoCrud, TipoSessaoCrud, TipoExpedienteCrud, TipoResultadoVotacaoCrud,
VotacaoEditView, VotacaoExpedienteEditView, TipoSessaoCrud, VotacaoEditView,
VotacaoExpedienteEditView,
VotacaoExpedienteView, VotacaoNominalEditView, VotacaoExpedienteView, VotacaoNominalEditView,
VotacaoNominalExpedienteEditView,
VotacaoNominalExpedienteDetailView, VotacaoNominalExpedienteDetailView,
VotacaoNominalExpedienteEditView,
VotacaoNominalExpedienteView, VotacaoNominalExpedienteView,
VotacaoNominalView, VotacaoView, VotacaoNominalView, VotacaoView,
abrir_votacao_expediente_view, abrir_votacao_expediente_view,
abrir_votacao_ordem_view, abrir_votacao_ordem_view, atualizar_mesa,
insere_parlamentar_composicao,
mudar_ordem_materia_sessao, recuperar_materia, mudar_ordem_materia_sessao, recuperar_materia,
recuperar_numero_sessao, recuperar_numero_sessao,
remove_parlamentar_composicao,
reordernar_materias_expediente, reordernar_materias_expediente,
reordernar_materias_ordem, reordernar_materias_ordem,
sessao_legislativa_legislatura_ajax) sessao_legislativa_legislatura_ajax)
@ -40,6 +43,18 @@ urlpatterns = [
url(r'^sessao/(?P<pk>\d+)/mesa$', MesaView.as_view(), name='mesa'), url(r'^sessao/(?P<pk>\d+)/mesa$', MesaView.as_view(), name='mesa'),
url(r'^sessao/mesa/atualizar-mesa/$',
atualizar_mesa,
name='atualizar_mesa'),
url(r'^sessao/mesa/insere-parlamentar/composicao/$',
insere_parlamentar_composicao,
name='insere_parlamentar_composicao'),
url(r'^sessao/mesa/remove-parlamentar-composicao/$',
remove_parlamentar_composicao,
name='remove_parlamentar_composicao'),
url(r'^sessao/recuperar-materia/', recuperar_materia), url(r'^sessao/recuperar-materia/', recuperar_materia),
url(r'^sessao/recuperar-numero-sessao/', recuperar_numero_sessao), url(r'^sessao/recuperar-numero-sessao/', recuperar_numero_sessao),
url(r'^sessao/sessao-legislativa-legislatura-ajax/', url(r'^sessao/sessao-legislativa-legislatura-ajax/',
@ -68,6 +83,9 @@ urlpatterns = [
include(BlocoCrud.get_urls())), include(BlocoCrud.get_urls())),
url(r'^sistema/cargo-bancada/', url(r'^sistema/cargo-bancada/',
include(CargoBancadaCrud.get_urls())), include(CargoBancadaCrud.get_urls())),
url(r'^sistema/resumo-ordenacao/',
ResumoOrdenacaoView.as_view(),
name='resumo_ordenacao'),
url(r'^sessao/(?P<pk>\d+)/adicionar-varias-materias-expediente/', url(r'^sessao/(?P<pk>\d+)/adicionar-varias-materias-expediente/',
AdicionarVariasMateriasExpediente.as_view(), AdicionarVariasMateriasExpediente.as_view(),
name='adicionar_varias_materias_expediente'), name='adicionar_varias_materias_expediente'),

1106
sapl/sessao/views.py

File diff suppressed because it is too large

8
sapl/settings.py

@ -53,6 +53,7 @@ SAPL_APPS = (
'sapl.lexml', 'sapl.lexml',
'sapl.painel', 'sapl.painel',
'sapl.protocoloadm', 'sapl.protocoloadm',
'sapl.redireciona_urls',
'sapl.compilacao', 'sapl.compilacao',
'sapl.api', 'sapl.api',
@ -85,6 +86,7 @@ INSTALLED_APPS = (
) + SAPL_APPS ) + SAPL_APPS
# FTS = Full Text Search # FTS = Full Text Search
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
SEARCH_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine' SEARCH_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine'
SEARCH_URL = ('PATH', PROJECT_DIR.child('whoosh')) SEARCH_URL = ('PATH', PROJECT_DIR.child('whoosh'))
@ -180,6 +182,8 @@ DATABASES = {
# https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#substituting-a-custom-user-model # https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#substituting-a-custom-user-model
AUTH_USER_MODEL = 'auth.User' AUTH_USER_MODEL = 'auth.User'
X_FRAME_OPTIONS = 'ALLOWALL'
EMAIL_HOST = config('EMAIL_HOST', default='localhost') EMAIL_HOST = config('EMAIL_HOST', default='localhost')
EMAIL_PORT = config('EMAIL_PORT', cast=int, default=587) EMAIL_PORT = config('EMAIL_PORT', cast=int, default=587)
EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='') EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='')
@ -189,14 +193,14 @@ EMAIL_SEND_USER = config('EMAIL_SEND_USER', cast=str, default='')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='') DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='')
SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='') SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='')
MAX_DOC_UPLOAD_SIZE = 5 * 1024 * 1024 # 5MB MAX_DOC_UPLOAD_SIZE = 20 * 1024 * 1024 # 20MB
MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/ # https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'pt-br' LANGUAGE_CODE = 'pt-br'
LANGUAGES = ( LANGUAGES = (
('pt-br', u'Português'), ('pt-br','Português'),
) )
TIME_ZONE = 'America/Sao_Paulo' TIME_ZONE = 'America/Sao_Paulo'

BIN
sapl/static/img/avatar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
sapl/static/img/beta.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

8
sapl/static/js/app.js

@ -89,10 +89,12 @@ function autorModal() {
}); });
$("#pesquisar").click(function() { $("#pesquisar").click(function() {
var query = $("#q").val() var name_in_query = $("#q").val()
var q_0 = "q_0=parlamentar_set__ativo,parlamentar_set__nome_parlamentar__icontains"
var q_1 = "q_1=True," + name_in_query
query = q_0 + "&" + q_1
$.get("/api/autor?" + query, function(data, status) {
$.get("/api/autor?q=" + query, function(data, status) {
$("#div-resultado").children().remove(); $("#div-resultado").children().remove();
if (data.pagination.total_entries == 0) { if (data.pagination.total_entries == 0) {
$("#selecionar").attr("hidden", "hidden"); $("#selecionar").attr("hidden", "hidden");

2
sapl/static/styles/app.scss

@ -235,6 +235,8 @@ fieldset {
.avatar-parlamentar { .avatar-parlamentar {
height: 84px; height: 84px;
width: 84px; width: 84px;
margin: 0 auto;
display: table;
} }
/* INDEX */ /* INDEX */

2
sapl/templates/ajuda/sessao_plenaria_materias_ordem_dia.html

@ -13,7 +13,7 @@
<br>Na identifica&ccedil;&atilde;o da mat&eacute;ria h&aacute; um link que, quando acionado, permite o acesso aos meta dados da mat&eacute;ria propriamente.<br /> <br>Na identifica&ccedil;&atilde;o da mat&eacute;ria h&aacute; um link que, quando acionado, permite o acesso aos meta dados da mat&eacute;ria propriamente.<br />
<br>As mat&eacute;rias legislativas s&atilde;o inseridas na Ordem do Dia, por meio da fun&ccedil;&atilde;o <i>Ordem do Dia</i>, integrada a <i>Sess&atilde;o Plen&aacute;ria</i>.<br /> <br>As mat&eacute;rias legislativas s&atilde;o inseridas na Ordem do Dia, por meio da fun&ccedil;&atilde;o <i>Ordem do Dia</i>, integrada a <i>Sess&atilde;o Plen&aacute;ria</i>.<br />
<br>Esta fun&ccedil;&atilde;o permite o acesso as fun&ccedil;&otilde;es <b>inclus&atilde;o individual</b> ou de <b>v&aacute;rias mat&eacute;rias</b> na mesma transa&ccedil;&atilde;o, conforme o bot&atilde;o que for acionado, via clique do mouse. <br /> <br>Esta fun&ccedil;&atilde;o permite o acesso as fun&ccedil;&otilde;es <b>inclus&atilde;o individual</b> ou de <b>v&aacute;rias mat&eacute;rias</b> na mesma transa&ccedil;&atilde;o, conforme o bot&atilde;o que for acionado, via clique do mouse. <br />
<br>Tamb&eacute;m, &eacute; poss&iacute;vel reordenar as mat&eacute;rias na Ordem do Dia, de modo a restaurar o n&uacute;mero de ordem sequencial, bastando para isso, clicar no bot&atilde;o 'Reordenar Mat&eacute;rias na Ordem do Dia', as quais ser&atilde;o renumeradas em ordem de tipo, ano e n&uacute;mero. <br /> <br>Tamb&eacute;m, &eacute; poss&iacute;vel ajustar as mat&eacute;rias na Ordem do Dia, de modo a restaurar o n&uacute;mero de ordem sequencial, bastando para isso, clicar no bot&atilde;o 'Ajustar Ordena&ccedil;&atilde;o na Ordem do Dia', as quais ser&atilde;o renumeradas em ordem de tipo, ano e n&uacute;mero. <br />
<br>O retorno a tela anterior &eacute; feito ao acionar o bot&atilde;o 'Retornar', que se encontra na parte inferior da tela.<br /> <br>O retorno a tela anterior &eacute; feito ao acionar o bot&atilde;o 'Retornar', que se encontra na parte inferior da tela.<br />
<br> <a href="sessao_plenaria">Anterior</a> | <br> <a href="sessao_plenaria">Anterior</a> |

26
sapl/templates/base.html

@ -28,8 +28,8 @@
<body> <body>
<div class="page fadein"> <div class="page fadein">
{% if not request|has_iframe %}
{% block navigation %} {% block navigation %}
<nav class="navbar navbar-inverse navbar-static-top"> <nav class="navbar navbar-inverse navbar-static-top">
<div class="container"> <div class="container">
<div class="navbar-header"> <div class="navbar-header">
@ -64,6 +64,11 @@
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a>{{user.username}}</a></li> <li><a>{{user.username}}</a></li>
{% if 'parlamentares.can_vote' in request.user.get_all_permissions %}
<li><a href="{% url 'sapl.painel:voto_individual' %}">
Votar Matéria
</a></li>
{% endif %}
<li><a href="{% url 'sapl.base:logout' %}">Sair</a></li> <li><a href="{% url 'sapl.base:logout' %}">Sair</a></li>
</ul> </ul>
</li> </li>
@ -100,11 +105,19 @@
</div> </div>
</header> </header>
{% endblock main_header %} {% endblock main_header %}
{% else %}
<header class="masthead">
<div class="container">
<div class="hidden-print">
{% subnav %}
</div>
</div>
</header>
{% endif %}
{# Main content #} {# Main content #}
{% block content_container %} {% block content_container %}
<main id="content" class="content page__row"> <main id="content" class="content page__row">
<div class="container"> <div class="container">
{# Feedback messages #} {# Feedback messages #}
@ -148,7 +161,7 @@
{% endblock content_container %} {% endblock content_container %}
{% if not request|has_iframe %}
{% block footer_container %} {% block footer_container %}
<footer id="footer" class="footer page__row hidden-print"> <footer id="footer" class="footer page__row hidden-print">
<div class="container"> <div class="container">
@ -157,11 +170,13 @@
<div class="col-md-4"> <div class="col-md-4">
<a class="footer__logo" href="#"> <a class="footer__logo" href="#">
<a href="http://www.interlegis.leg.br/">
<img src="{% static 'img/logo_interlegis.png' %}" alt="{% trans 'Logo do Interlegis' %} "> <img src="{% static 'img/logo_interlegis.png' %}" alt="{% trans 'Logo do Interlegis' %} ">
</a> </a>
</a>
<p> <p>
<small> <small>
Desenvolvido pelo <a href="#">Interlegis</a> em software livre e aberto. Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small> </small>
</p> </p>
</div> </div>
@ -171,7 +186,7 @@
</a> </a>
<p> <p>
<small> <small>
Conteúdo e dados sob licença <a href="#">Creative Commons</a> 4.0 <a href="#">Atribuir Fonte - Compartilhar Igual</a> Conteúdo e dados sob licença <a href="https://creativecommons.org">Creative Commons</a> 4.0 <a href="https://creativecommons.org/licenses/by/4.0/">Atribuir Fonte - Compartilhar Igual</a>
</small> </small>
</p> </p>
</div> </div>
@ -203,6 +218,7 @@
</footer> </footer>
</div> </div>
{% endblock footer_container %} {% endblock footer_container %}
{% endif %}
{% block foot_js %} {% block foot_js %}
<!-- Bootstrap core JavaScript ================================================== --> <!-- Bootstrap core JavaScript ================================================== -->

2
sapl/templates/base/RelatorioMateriasPorTramitacao_filter.html

@ -45,7 +45,7 @@
<td><a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}"> <td><a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}">
{{materia.tipo.descricao}} {{materia.numero}}/{{materia.ano}} {{materia.tipo.descricao}} {{materia.numero}}/{{materia.ano}}
</a></td> </a></td>
<td>{{materia.tramitacao_set.last.unidade_tramitacao_local}}</td> <td>{{materia.tramitacao_set.last.unidade_tramitacao_destino}}</td>
<td>{{materia.tramitacao_set.last.status}}</td> <td>{{materia.tramitacao_set.last.status}}</td>
</tr> </tr>
{% endfor %} {% endfor %}

2
sapl/templates/compilacao/textoarticulado_detail.html

@ -10,7 +10,7 @@
{% if request.GET.back_type == 'history' and object.content_object %} {% if request.GET.back_type == 'history' and object.content_object %}
<a href="javascript:window.history.back()" title="{% trans 'Voltar para '%}{{object.content_object}}">{% trans 'Voltar para '%}{{object.content_object}}</a> <a href="javascript:window.history.back()" title="{% trans 'Voltar para '%}{{object.content_object}}">{% trans 'Voltar para '%}{{object.content_object}}</a>
{% elif object.content_object%} {% elif object.content_object%}
<a href="{% url object|urldetail_content_type object.content_object.pk %}" title="{% trans 'Voltar para '%}{{object.content_object}}">{% trans 'Voltar para '%}{{object.content_object}}</a> <a href="{% url object|urldetail_content_type:object.content_object object.content_object.pk %}" title="{% trans 'Voltar para '%}{{object.content_object}}">{% trans 'Voltar para '%}{{object.content_object}}</a>
{%else%} {%else%}
<a href="{% url 'sapl.compilacao:ta_detail' object.pk %}">{% trans 'Início' %}</a> <a href="{% url 'sapl.compilacao:ta_detail' object.pk %}">{% trans 'Início' %}</a>
{%endif%} {%endif%}

4
sapl/templates/crud/detail.html

@ -66,9 +66,9 @@
<div class="controls"> <div class="controls">
{% comment %}TODO Transformar os links em URLs diretamente no CRUD{% endcomment %} {% comment %}TODO Transformar os links em URLs diretamente no CRUD{% endcomment %}
{% if column.text|url %} {% if column.text|url %}
<div class="form-control-static"><a href="{{ column.text|safe }}"> {{ column.text|safe }} </a></div> <div class="form-control-static"><a href="{{ column.text|safe }}"> {{ column.text|safe|default:"" }} </a></div>
{% else %} {% else %}
<div class="form-control-static">{{ column.text|safe }}</div> <div class="form-control-static">{{ column.text|safe|default:"" }}</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>

6
sapl/templates/crud/list.html

@ -58,12 +58,12 @@
{% for value, href in value_list %} {% for value, href in value_list %}
<td> <td>
{% if href %} {% if href %}
<a href="{{ href }}">{{ value|safe }}</a> <a href="{{ href }}">{{ value|safe|default:"" }}</a>
{% elif valu != 'core.Cep.None' %} {% elif valu != 'core.Cep.None' %}
{% if value|url %} {% if value|url %}
<a href="{{ value|safe }}"> {{ value|safe }} </a></div> <a href="{{ value|safe }}"> {{ value|safe|default:"" }} </a></div>
{% else %} {% else %}
{{ value|safe }} {{ value|safe|default:"" }}
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>

6
sapl/templates/email/acompanhar.html

@ -1,14 +1,12 @@
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
<html><head></head><body bgcolor='#ffffff'> <html><head></head><body bgcolor='#ffffff'>
<p align='center'>
<img src="{{base_url}}{% if logotipo %}{{ MEDIA_URL }}{{ logotipo }}{% else %}{% static 'img/logo.png' %}{% endif %}"
alt="Logo" class="img-responsive visible-lg-inline-block vcenter" >
</p>
<h2 align='center'><b>{{casa_legislativa}}</b> <h2 align='center'><b>{{casa_legislativa}}</b>
<br/> <br/>
Sistema de Apoio ao Processo Legislativo Sistema de Apoio ao Processo Legislativo
</h2> </h2>
<p>Registramos seu pedido para acompanhamento por e-mail da matéria legislativa identificada a seguir:</p> <p>Registramos seu pedido para acompanhamento por e-mail da matéria legislativa identificada a seguir:</p>
<a href="{{base_url}}{{materia_url}}">{{materia}}<b> - {{descricao_materia}}</b></a><br/> <a href="{{base_url}}{{materia_url}}">{{materia}}<b> - {{descricao_materia}}</b></a><br/>
{{ementa}}<br/> {{ementa}}<br/>

9
sapl/templates/email/tramitacao.html

@ -3,16 +3,12 @@
<html> <html>
<head></head> <head></head>
<body bgcolor='#ffffff'> <body bgcolor='#ffffff'>
<p align='center'>
<img src="{{base_url}}{% if logotipo %}{{ MEDIA_URL }}{{ logotipo }}{% else %}{% static 'img/logo.png' %}{% endif %}"
alt="Logo" class="img-responsive visible-lg-inline-block vcenter" >
</p>
<h2 align='center'><b>{{casa_legislativa}}</b> <h2 align='center'><b>{{casa_legislativa}}</b>
<br/> <br/>
Sistema de Apoio ao Processo Legislativo Sistema de Apoio ao Processo Legislativo
</h2> </h2>
<p>A seguinte mat&eacute;ria de seu interesse sofreu <p>A seguinte mat&eacute;ria, de seu interesse, sofreu
tramita&ccedil;&atilde;o registrada em {{data_registro}} Tramita&ccedil;&atilde;o registrada em <b>{{data_registro}}</b>.
</p> </p>
<h4> <h4>
<a href="{{base_url}}{{materia_url}}"><b>{{materia}} - {{descricao_materia}}</b></a> <a href="{{base_url}}{{materia_url}}"><b>{{materia}} - {{descricao_materia}}</b></a>
@ -26,6 +22,7 @@
<p> <p>
<b>Data da a&ccedil;&atilde;o</b>: {{data}}<br/> <b>Data da a&ccedil;&atilde;o</b>: {{data}}<br/>
<b>Status</b>: {{status}}<br/> <b>Status</b>: {{status}}<br/>
<b>Localização Atual:</b> {{localizacao}}<br/>
<b>Texto da a&ccedil;&atilde;o</b>: {{texto_acao}}</p> <b>Texto da a&ccedil;&atilde;o</b>: {{texto_acao}}</p>
<hr> <hr>
<p> <p>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save