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. 9
      create_admin.py
  6. 2
      docker-compose.yml
  7. 12
      docs/deploy.rst
  8. 29
      docs/importacao_25_31.rst
  9. 41
      docs/instalacao31.rst
  10. 3
      docs/solr.rst
  11. 1
      genkey.py
  12. 2
      pytest.ini
  13. 64
      sapl/api/forms.py
  14. 127
      sapl/api/serializers.py
  15. 70
      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. 79
      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. 68
      sapl/legacy/migracao_documentos.py
  30. 214
      sapl/legacy/migration.py
  31. 146
      sapl/legacy/scripts/street_sweeper.py
  32. 211
      sapl/materia/email_utils.py
  33. 139
      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. 536
      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. 295
      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. 48
      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. 141
      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. 1178
      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. 28
      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. 14
      sapl/templates/email/acompanhar.html
  100. 49
      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 \
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 && \
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
RUN python3 manage.py bower_install -- --allow-root --no-input && \
python3 manage.py compilescss && \
python3 manage.py collectstatic --no-input && \
rm -rf /var/interlegis/sapl/sapl/.env && \
python3 manage.py compilescss
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
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/static *
recursive-include sapl/relatorios/templates *.py
recursive-include sapl/compilacao *.sql
global-exclude __pycache__
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 <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

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/*

9
create_admin.py

@ -1,13 +1,13 @@
import sys
import os
import sys
import django
from sapl import settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings")
django.setup()
from django.contrib.auth.models import User
def create_superuser():
from django.contrib.auth.models import User
username = "admin"
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 ''
@ -30,4 +30,5 @@ def create_superuser():
sys.exit(0)
if __name__ == '__main__':
django.setup()
create_superuser()

2
docker-compose.yml

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

12
docs/deploy.rst

@ -17,16 +17,20 @@ alterando o variável DEBUG para false::
DEBUG = False
Entrar no ambiente virtual::
workon sapl
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::
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::
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.
@ -40,7 +44,7 @@ Instalar o NGINX::
Instalar o Gunicorn::
sudo pip install gunicorn
sudo pip3 install gunicorn
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
Reiniciar o nginx
Reiniciar o nginx::
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
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

41
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 \
software-properties-common build-essential libxml2-dev libjpeg-dev \
libmysqlclient-dev libssl-dev libffi-dev libxslt1-dev python3-setuptools \
python3-pip curl
python3-pip curl poppler-utils default-jre
sudo -i
curl -sL https://deb.nodesource.com/setup_5.x | bash -
@ -145,13 +145,15 @@ Criação da `SECRET_KEY <https://docs.djangoproject.com/es/1.9/ref/settings/#st
EMAIL_HOST_PASSWORD = [Insira este parâmetro]
DEFAULT_FROM_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::
DATABASE_URL = postgresql://sapl:sapl@localhost:5432/sapl
SECRET_KEY = 'cole aqui entre as aspas simples a chave gerada pelo comando abaixo'
DEBUG = True
DEBUG = False
EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST =
@ -160,8 +162,7 @@ Criação da `SECRET_KEY <https://docs.djangoproject.com/es/1.9/ref/settings/#st
DEFAULT_FROM_EMAIL =
SERVER_EMAIL =
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::
@ -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``::
sudo chown -R sapl31:sapl31 /home/sapl31/
./manage.py bower install
* 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
* Preparar o ambiente para indexação de arquivos::
./manage.py rebuild_index
* 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/
================================
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
===========================================================================

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

1
genkey.py

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

2
pytest.ini

@ -2,6 +2,8 @@
DJANGO_SETTINGS_MODULE=sapl.settings
norecursedirs = legacy crud
python_files = tests.py test_*.py *_tests.py
# 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
#

64
sapl/api/forms.py

@ -1,5 +1,8 @@
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 rest_framework.compat import django_filters
from rest_framework.filters import FilterSet
from sapl.base.models import Autor, TipoAutor
@ -35,10 +38,11 @@ class SaplGenericRelationSearchFilterSet(FilterSet):
item.related_query_name(),
field[0])
)
q_fs = q_fs | Q(**{'%s__%s%s' % (
item.related_query_name(),
field[0],
field[1]): qtext})
if len(field) == 3 and field[2](qtext) is not None:
q_fs = q_fs | Q(**{'%s__%s%s' % (
item.related_query_name(),
field[0],
field[1]): qtext if len(field) == 2 else field[2](qtext)})
q = q & q_fs
@ -48,6 +52,37 @@ class SaplGenericRelationSearchFilterSet(FilterSet):
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):
q = MethodFilter()
tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all())
@ -60,4 +95,23 @@ class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet):
def filter_q(self, queryset, value):
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')

127
sapl/api/serializers.py

@ -1,8 +1,8 @@
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.sessao.models import SessaoPlenaria
from sapl.sessao.models import OrdemDia, SessaoPlenaria
class ChoiceSerializer(serializers.Serializer):
@ -46,6 +46,7 @@ class AutorSerializer(serializers.ModelSerializer):
class Meta:
model = Autor
fields = '__all__'
class MateriaLegislativaSerializer(serializers.ModelSerializer):
@ -57,21 +58,113 @@ class MateriaLegislativaSerializer(serializers.ModelSerializer):
class SessaoPlenariaSerializer(serializers.ModelSerializer):
tipo = serializers.StringRelatedField(many=False)
sessao_legislativa = serializers.StringRelatedField(many=False)
legislatura = serializers.StringRelatedField(many=False)
codReuniao = serializers.SerializerMethodField('get_pk_sessao')
codReuniaoPrincipal = serializers.SerializerMethodField('get_pk_sessao')
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:
model = SessaoPlenaria
fields = ('pk',
'tipo',
'sessao_legislativa',
'legislatura',
'data_inicio',
'hora_inicio',
'hora_fim',
'url_video',
'iniciada',
'finalizada',
'interativa'
)
fields = (
'codReuniao',
'codReuniaoPrincipal',
'txtTituloReuniao',
'txtSiglaOrgao',
'txtApelido',
'txtNomeOrgao',
'codEstadoReuniao',
'txtTipoReuniao',
'txtObjeto',
'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

70
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.generics import ListAPIView
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.permissions import (IsAuthenticated,
IsAuthenticatedOrReadOnly,
AllowAny)
from rest_framework.permissions import (AllowAny, IsAuthenticated,
IsAuthenticatedOrReadOnly)
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,
ChoiceSerializer,
MateriaLegislativaSerializer,
@ -79,7 +78,57 @@ class AutorListView(ListAPIView):
o django-filter é desativado e a busca é feita
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
@ -125,6 +174,9 @@ class AutorListView(ListAPIView):
self.serializer_class = AutorSerializer
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)
def get_queryset(self):
@ -186,9 +238,12 @@ class AutorListView(ListAPIView):
q_filter = q_filter & q_fs
qs = qs.filter(q_filter).distinct(
fields[0].fields_search[0][0])
fields[0].fields_search[0][0]).order_by(
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])
r += list(qs)
@ -207,6 +262,7 @@ class MateriaLegislativaViewSet(ListModelMixin,
filter_backends = (DjangoFilterBackend,)
filter_fields = ('numero', 'ano', 'tipo', )
class SessaoPlenariaViewSet(ListModelMixin,
RetrieveModelMixin,
GenericViewSet):

13
sapl/base/forms.py

@ -227,6 +227,8 @@ class AutorForm(ModelForm):
return True
def clean(self):
super(AutorForm, self).clean()
User = get_user_model()
cd = self.cleaned_data
@ -505,6 +507,11 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
'widget': RangeWidgetOverride}
}}
@property
def qs(self):
parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs
return parent.distinct().order_by('-ano', 'tipo', 'numero')
class Meta:
model = MateriaLegislativa
fields = ['tipo', 'tramitacao__unidade_tramitacao_local',
@ -534,7 +541,7 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True,
label=u'Ano da Matéria',
label='Ano da Matéria',
choices=RANGE_ANOS)
class Meta:
@ -565,7 +572,7 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True,
label=u'Ano da Matéria',
label='Ano da Matéria',
choices=RANGE_ANOS)
class Meta:
@ -720,6 +727,8 @@ class RecuperarSenhaForm(PasswordResetForm):
super(RecuperarSenhaForm, self).__init__(*args, **kwargs)
def clean(self):
super(RecuperarSenhaForm, self).clean()
email_existente = User.objects.filter(
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'))
descricao = models.CharField(max_length=300, verbose_name=_('Descrição'))
eh_stub = models.BooleanField(verbose_name=_('É stub?'))
eh_importante = models.BooleanField(
default=False, verbose_name=_('É importante?'))
critico = models.BooleanField(
default=False, verbose_name=_('Crítico'))
class Meta:
verbose_name = _('Problema na Migração')
@ -211,7 +211,7 @@ class Autor(models.Model):
autor_related = GenericForeignKey('content_type', 'object_id')
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)

79
sapl/base/search_indexes.py

@ -1,11 +1,18 @@
import logging
import os.path
import re
import string
import textract
from django.template import Context, loader
from django.template import loader
from haystack import indexes
from textract.exceptions import ExtensionNotSupported
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa
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):
@ -21,6 +28,37 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable):
def index_queryset(self, using=None):
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):
if not self.filename or not self.model or not self.template_name:
raise Exception
@ -30,26 +68,39 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable):
arquivo = getattr(obj, self.filename)
if arquivo:
try:
arquivo.open()
except OSError:
if not os.path.exists(arquivo.path):
return self.prepared_data
if not os.path.splitext(arquivo.path)[1][:1]:
return self.prepared_data
extracted_data = textract.process(
arquivo.path).decode(
'utf-8').replace('\n', ' ')
extracted_data = extracted_data.replace('\t', ' ')
# Em ambiente de produção utiliza-se o SOLR
if SOLR_URL:
try:
extracted_data = self.solr_extraction(arquivo)
except Exception:
self.print_error(arquivo)
return self.prepared_data
# 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
# text field with *all* of our metadata visible for templating:
t = loader.select_template((
'search/indexes/' + self.template_name, ))
data['text'] = t.render(Context({'object': obj,
'extracted': extracted_data}))
data['text'] = t.render({'object': obj,
'extracted': extracted_data})
return data
@ -63,6 +114,9 @@ class MateriaLegislativaIndex(DocumentoAcessorioIndex):
model = MateriaLegislativa
template_name = 'materia/materialegislativa_text.txt'
def get_updated_field(self):
return 'data_ultima_atualizacao'
class NormaJuridicaIndex(DocumentoAcessorioIndex):
text = indexes.CharField(document=True, use_template=True)
@ -70,3 +124,6 @@ class NormaJuridicaIndex(DocumentoAcessorioIndex):
filename = 'texto_integral'
model = NormaJuridica
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:
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
def url(value):
@ -151,3 +168,15 @@ def search_get_model(object):
return 'n'
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'
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$',
RelatorioMateriasPorAutorView.as_view(), name='materia_por_autor'),
url(r'^sistema/relatorios/materia-por-ano-autor-tipo$',
@ -92,7 +92,8 @@ urlpatterns = [
# todos os sublinks de sistema devem vir acima deste
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, {
'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.db.models import F
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import ListView
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):
context = super().get_context_data(**kwargs)
context['composicao_pk'] = context['composicao_list'].last(
).pk if self.take_composicao_pk(
) == 0 else self.take_composicao_pk()
# context['composicao_pk'] = context['composicao_list'].last(
# ).pk if 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(
composicao__pk=context['composicao_pk']
).order_by('parlamentar')
@ -69,9 +82,20 @@ class ComissaoCrud(Crud):
public = [RP_LIST, RP_DETAIL, ]
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'
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):
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
@register.filter
def urldetail_content_type(obj):
return '%s:%s_detail' % (
obj.content_object._meta.app_config.name, obj.content_type.model)
@register.filter
def list(obj):

25
sapl/crispy_layout_mixin.py

@ -1,12 +1,13 @@
from math import ceil
import rtyaml
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Div, Fieldset, Layout, Submit
from django import template
from django.core.urlresolvers import reverse
from django.utils import formats
from django.utils.translation import ugettext as _
import rtyaml
def heads_and_tails(list_of_lists):
@ -77,29 +78,30 @@ def get_field_display(obj, fieldname):
else:
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:
display = ''
elif 'date' in str_type:
elif 'date' in str_type_from_value:
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')
elif 'ImageFieldFile' in str(type(value)):
if value:
display = '<img src="{}" />'.format(value.url)
else:
display = ''
elif 'FieldFile' in str(type(value)):
elif 'FieldFile' in str_type_from_value:
if value:
display = '<a href="{}">{}</a>'.format(
value.url,
value.name.split('/')[-1:][0])
else:
display = ''
elif 'ManyRelatedManager' in str(type(value))\
or 'RelatedManager' in str(type(value))\
or 'GenericRelatedObjectManager' in str(type(value)):
elif 'ManyRelatedManager' in str_type_from_value\
or 'RelatedManager' in str_type_from_value\
or 'GenericRelatedObjectManager' in str_type_from_value:
display = '<ul>'
for v in value.all():
display += '<li>%s</li>' % str(v)
@ -110,6 +112,13 @@ def get_field_display(obj, fieldname):
field.related_model._meta.verbose_name_plural)
elif hasattr(field, 'model'):
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:
display = str(value)
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.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models.fields.related import ForeignKey
@ -1133,7 +1134,11 @@ class MasterDetailCrud(Crud):
parent_field = obj.parent_field.split('__')
if not obj.is_m2m or len(parent_field) > 1:
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)
return form
@ -1155,7 +1160,7 @@ class MasterDetailCrud(Crud):
try:
parent_object = parent_model.objects.get(**params)
except:
except Exception:
raise Http404()
else:
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):
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):
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):
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):
migrar_documentos()

68
sapl/legacy/migracao_documentos.py

@ -3,19 +3,19 @@ import os
import re
import magic
from django.db.models.signals import post_delete, post_save
from sapl.base.models import CasaLegislativa
from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa,
Proposicao)
from sapl.norma.models import NormaJuridica
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.settings import MEDIA_ROOT
from sapl.utils import delete_texto, save_texto
# MIGRAÇÃO DE DOCUMENTOS ###################################################
EXTENSOES = {
'application/msword': '.doc',
@ -29,6 +29,17 @@ EXTENSOES = {
'text/html': '.html',
'text/rtf': '.rtf',
'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
'application/octet-stream': '', # binário
@ -43,35 +54,41 @@ DOCS = {
Parlamentar: [(
'fotografia',
'parlamentar/fotos/{}_foto_parlamentar',
'parlamentar/{0}/{0}_foto_parlamentar{1}')],
'public/parlamentar/{0}/{0}_foto_parlamentar{1}')],
MateriaLegislativa: [(
'texto_original',
'materia/{}_texto_integral',
'materialegislativa/{0}/{0}_texto_integral{1}')],
'public/materialegislativa/{2}/{0}/{0}_texto_integral{1}')],
DocumentoAcessorio: [(
'arquivo',
'materia/{}',
'documentoacessorio/{0}/{0}{1}')],
'public/documentoacessorio/{2}/{0}/{0}{1}')],
NormaJuridica: [(
'texto_original',
'texto_integral',
'norma_juridica/{}_texto_integral',
'normajuridica/{0}/{0}_texto_integral{1}')],
'public/normajuridica/{2}/{0}/{0}_texto_integral{1}')],
SessaoPlenaria: [
('upload_ata',
'ata_sessao/{}_ata_sessao',
'sessaoplenaria/{0}/ata/{0}_ata_sessao{1}'),
'public/sessaoplenaria/{0}/ata/{0}_ata_sessao{1}'),
('upload_anexo',
'anexo_sessao/{}_texto_anexado',
'sessaoplenaria/{0}/anexo/{0}_texto_anexado{1}')
'public/sessaoplenaria/{0}/anexo/{0}_texto_anexado{1}')
],
Proposicao: [(
'texto_original',
'proposicao/{}',
'proposicao/{0}/{0}{1}')],
'private/proposicao/{0}/{0}{1}')],
DocumentoAdministrativo: [(
'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,
@ -105,9 +122,14 @@ def migrar_docs_logo():
print('#### Migrando logotipo da casa ####')
[(_, origem, destino)] = DOCS[CasaLegislativa]
props_sapl = os.path.dirname(origem)
# 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))) < {
'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)
casa = get_casa_legislativa()
casa.logotipo = destino
@ -146,15 +168,22 @@ def migrar_docs_por_ids(tipo):
for arq in os.listdir(dir_origem):
match = pat.match(arq)
if match:
origem = os.path.join(dir_origem, match.group(0))
id = match.group(1)
extensao = get_extensao(origem)
destino = base_destino.format(id, extensao)
mover_documento(origem, destino)
# associa documento ao objeto
try:
origem = os.path.join(dir_origem, match.group(0))
id = match.group(1)
obj = tipo.objects.get(pk=id)
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)
mover_documento(origem, destino)
setattr(obj, campo, destino)
obj.save()
except tipo.DoesNotExist:
@ -199,6 +228,7 @@ def migrar_documentos():
SessaoPlenaria,
Proposicao,
DocumentoAdministrativo,
DocumentoAcessorioAdministrativo,
]:
migrar_docs_por_ids(tipo)

214
sapl/legacy/migration.py

@ -8,10 +8,11 @@ import yaml
from django.apps import apps
from django.apps.config import AppConfig
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
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.signals import post_delete, post_save
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.comissoes.models import Comissao, Composicao, Participacao
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,
TipoMateriaLegislativa, TipoProposicao,
Tramitacao)
from sapl.norma.models import (AssuntoNorma, NormaJuridica,
TipoVinculoNormaJuridica, NormaRelacionada)
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import Protocolo, StatusTramitacaoAdministrativo
from sapl.parlamentares.models import (Legislatura,Mandato, Parlamentar,
TipoAfastamento)
from sapl.protocoloadm.models import (DocumentoAdministrativo,Protocolo,
StatusTramitacaoAdministrativo)
from sapl.sessao.models import ExpedienteMateria, OrdemDia, RegistroVotacao
from sapl.settings import PROJECT_DIR
from sapl.utils import delete_texto, normalize, save_texto
from sapl.utils import normalize
# BASE ######################################################################
# 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)
value = TipoProposicao.objects.create(
id=value, descricao='Erro', content_type=ct)
ultimo_valor = get_last_value(type(value))
alter_sequence(type(value), ultimo_valor+1)
else:
value = tipo[0]
else:
@ -240,6 +246,25 @@ def delete_constraints(model):
(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():
constraints = Constraint.objects.all()
for con in constraints:
@ -249,6 +274,8 @@ def recria_constraints():
args = [a.argumento for a in con.argumento_set.all()]
args_string = ''
args_string = "(" + "_".join(map(str, args[2:-1])) + ")"
model = ContentType.objects.filter(
model=con.nome_model.lower())[0].model_class()
try:
exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" %
(nome_tabela, nome_constraint, args_string))
@ -268,18 +295,30 @@ def recria_constraints():
args[i] = args[i] + '_id'
args_string = ''
args_string += "(" + ', '.join(map(str, args)) + ")"
try:
exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" %
(nome_tabela, nome_constraint, args_string))
except ProgrammingError:
info('A constraint %s já foi recriada!' % nome_constraint)
except Exception as err:
problema = re.findall('\(.*?\)', err.args[0])
erro('A constraint [%s] da tabela [%s] não pode ser recriada' %
(nome_constraint, nome_tabela))
erro('Os dados %s = %s estão duplicados. '
'Arrume antes de recriar as constraints!' %
(problema[0], problema[1]))
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:
exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" %
(nome_tabela, nome_constraint, args_string))
except ProgrammingError:
info('A constraint %s já foi recriada!' % nome_constraint)
except Exception as err:
problema = re.findall('\(.*?\)', err.args[0])
erro('A constraint [%s] da tabela [%s] não pode ser" \
recriada' % (nome_constraint, nome_tabela))
erro('Os dados %s = %s estão duplicados. '
'Arrume antes de recriar as constraints!' %
(problema[0], problema[1]))
def obj_desnecessario(obj):
@ -311,10 +350,10 @@ def save_with_id(new, id):
def save_relation(obj, nome_campo='', problema='', descricao='',
eh_stub=False):
eh_stub=False, critico=False):
link = ProblemaMigracao(
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()
@ -369,6 +408,34 @@ def fill_vinculo_norma_juridica():
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:
def __init__(self):
@ -445,8 +512,6 @@ class DataMigrator:
call([PROJECT_DIR.child('manage.py'), 'flush',
'--database=default', '--no-input'], stdout=PIPE)
desconecta_sinais_indexacao()
fill_vinculo_norma_juridica()
info('Começando migração: %s...' % obj)
self._do_migrate(obj)
@ -464,11 +529,15 @@ class DataMigrator:
save_relation(obj=obj, problema=msg,
descricao=descricao, eh_stub=False)
info('Excluindo possíveis duplicações em RegistroVotacao...')
excluir_registrovotacao_duplicados()
info('Deletando stubs desnecessários...')
while self.delete_stubs():
pass
conecta_sinais_indexacao()
info('Recriando constraints...')
recria_constraints()
def _do_migrate(self, obj):
if isinstance(obj, AppConfig):
@ -580,11 +649,46 @@ def migrate(obj=appconfs, interativo=True):
# MIGRATION_ADJUSTMENTS #####################################################
def adjust_ordemdia(new, old):
# Prestar atenção
def adjust_acompanhamentomateria(new, old):
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:
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):
if old.ind_unid_deliberativa:
@ -616,6 +720,25 @@ def adjust_participacao(new, old):
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):
tipo = TipoVinculoNormaJuridica.objects.filter(sigla=old.tip_vinculo)
assert len(tipo) == 1
@ -660,6 +783,12 @@ def adjust_registrovotacao_depois_salvar(new, old):
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):
if old.ind_mat_ou_doc == 'M':
new.tipo_conteudo_related = TipoMateriaLegislativa.objects.get(
@ -723,6 +852,7 @@ def adjust_autor(new, old):
new.nome = new.autor_related.nome_parlamentar
elif old.cod_comissao:
new.autor_related = Comissao.objects.get(pk=old.cod_comissao)
new.nome = new.autor_related.nome
if old.col_username:
if not get_user_model().objects.filter(
@ -732,6 +862,13 @@ def adjust_autor(new, old):
with reversion.create_revision():
user.save()
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
else:
new.user = get_user_model().objects.filter(
@ -750,14 +887,19 @@ def adjust_comissao(new, old):
AJUSTE_ANTES_SALVAR = {
Autor: adjust_autor,
AcompanhamentoMateria: adjust_acompanhamentomateria,
Comissao: adjust_comissao,
DocumentoAdministrativo: adjust_documentoadministrativo,
Mandato: adjust_mandato,
NormaJuridica: adjust_normajuridica_antes_salvar,
NormaRelacionada: adjust_normarelacionada,
OrdemDia: adjust_ordemdia,
OrdemDia: adjust_ordemdia_antes_salvar,
Parlamentar: adjust_parlamentar,
Participacao: adjust_participacao,
Proposicao: adjust_proposicao_antes_salvar,
Protocolo: adjust_protocolo,
RegistroVotacao: adjust_registrovotacao_antes_salvar,
TipoAfastamento: adjust_tipoafastamento,
TipoProposicao: adjust_tipoproposicao,
StatusTramitacao: adjust_statustramitacao,
StatusTramitacaoAdministrativo: adjust_statustramitacaoadm,
@ -766,6 +908,8 @@ AJUSTE_ANTES_SALVAR = {
AJUSTE_DEPOIS_SALVAR = {
NormaJuridica: adjust_normajuridica_depois_salvar,
OrdemDia: adjust_ordemdia_depois_salvar,
Proposicao: adjust_proposicao_depois_salvar,
Protocolo: adjust_protocolo_depois_salvar,
RegistroVotacao: adjust_registrovotacao_depois_salvar,
}
@ -801,23 +945,3 @@ def make_with_log(model, _quantity=None, make_m2m=False, **attrs):
return stub
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()

139
sapl/materia/forms.py

@ -1,8 +1,7 @@
import os
from datetime import date, datetime
import os
import django_filters
from crispy_forms.bootstrap import Alert, FormActions, InlineRadios
from crispy_forms.helper import FormHelper
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.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
import django_filters
import sapl
from sapl.base.models import Autor
from sapl.comissoes.models import Comissao
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC,
@ -40,6 +39,7 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
ChoiceWithoutValidationField,
MateriaPesquisaOrderingFilter, RangeWidgetOverride,
autor_label, autor_modal, models_with_gr_for_model)
import sapl
from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial,
DocumentoAcessorio, Numeracao, Proposicao, Relatoria,
@ -52,8 +52,8 @@ def ANO_CHOICES():
def em_tramitacao():
return [('', 'Tanto Faz'),
(True, 'Sim'),
(False, 'Não')]
(1, 'Sim'),
(0, 'Não')]
class AdicionarVariasAutoriasFilterSet(django_filters.FilterSet):
@ -124,6 +124,8 @@ class UnidadeTramitacaoForm(ModelForm):
fields = ['comissao', 'orgao', 'parlamentar']
def clean(self):
super(UnidadeTramitacaoForm, self).clean()
cleaned_data = self.cleaned_data
for key in list(cleaned_data.keys()):
@ -164,17 +166,6 @@ class DocumentoAcessorioForm(ModelForm):
class Meta:
model = DocumentoAcessorio
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):
@ -190,6 +181,8 @@ class RelatoriaForm(ModelForm):
super(RelatoriaForm, self).__init__(*args, **kwargs)
def clean(self):
super(RelatoriaForm, self).clean()
cleaned_data = self.cleaned_data
try:
@ -222,6 +215,7 @@ class TramitacaoForm(ModelForm):
self.fields['data_tramitacao'].initial = datetime.now()
def clean(self):
super(TramitacaoForm, self).clean()
if 'data_encaminhamento' in self.data:
data_enc_form = self.cleaned_data['data_encaminhamento']
@ -299,6 +293,8 @@ class TramitacaoUpdateForm(TramitacaoForm):
}
def clean(self):
super(TramitacaoUpdateForm, self).clean()
local = self.instance.unidade_tramitacao_local
data_tram = self.instance.data_tramitacao
@ -339,6 +335,8 @@ class LegislacaoCitadaForm(ModelForm):
'item']
def clean(self):
super(LegislacaoCitadaForm, self).clean()
if self.errors:
return self.errors
@ -400,6 +398,8 @@ class NumeracaoForm(ModelForm):
'data_materia']
def clean(self):
super(NumeracaoForm, self).clean()
if self.errors:
return self.errors
@ -443,6 +443,8 @@ class AnexadaForm(ModelForm):
return super(AnexadaForm, self).__init__(*args, **kwargs)
def clean(self):
super(AnexadaForm, self).clean()
if self.errors:
return self.errors
@ -483,32 +485,42 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
}}
ano = django_filters.ChoiceFilter(required=False,
label=u'Ano da Matéria',
label='Ano da Matéria',
choices=ANO_CHOICES)
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')
em_tramitacao = django_filters.ChoiceFilter(required=False,
label=u'Em tramitação',
label='Em tramitação',
choices=em_tramitacao)
materiaassunto__assunto = django_filters.ModelChoiceFilter(
queryset=AssuntoMateria.objects.all(),
label=_('Assunto da Matéria'))
numeracao__numero_materia = django_filters.NumberFilter(
required=False,
label=_('Número do Processo'))
o = MateriaPesquisaOrderingFilter()
class Meta:
model = MateriaLegislativa
fields = ['numero',
'numero_protocolo',
'numeracao__numero_materia',
'ano',
'tipo',
'data_apresentacao',
'data_publicacao',
'autoria__autor__tipo',
'autoria__primeiro_autor',
# FIXME 'autoria__autor__partido',
'relatoria__parlamentar_id',
'local_origem_externa',
@ -529,14 +541,16 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
row1 = to_row(
[('tipo', 12)])
row2 = to_row(
[('numero', 4),
('ano', 4),
('numero_protocolo', 4)])
[('numero', 3),
('numeracao__numero_materia', 3),
('numero_protocolo', 3),
('ano', 3)])
row3 = to_row(
[('data_apresentacao', 6),
('data_publicacao', 6)])
row4 = to_row(
[('autoria__autor', 0),
('autoria__primeiro_autor', 0),
(Button('pesquisar',
'Pesquisar Autor',
css_class='btn btn-primary btn-sm'), 2),
@ -617,6 +631,8 @@ class DespachoInicialForm(ModelForm):
fields = ['comissao']
def clean(self):
super(DespachoInicialForm, self).clean()
if self.errors:
return self.errors
@ -637,6 +653,8 @@ class AutoriaForm(ModelForm):
fields = ['autor', 'primeiro_autor']
def clean(self):
super(AutoriaForm, self).clean()
if self.errors:
return self.errors
@ -808,6 +826,8 @@ class TipoProposicaoForm(ModelForm):
'tipo_conteudo_related'].initial = self.instance.object_id
def clean(self):
super(TipoProposicaoForm, self).clean()
cd = self.cleaned_data
content_type = cd['content_type']
@ -821,9 +841,25 @@ class TipoProposicaoForm(ModelForm):
pk=cd['tipo_conteudo_related']).exists():
raise ValidationError(
_('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
def save(self, commit=False):
@ -854,11 +890,11 @@ class TipoProposicaoSelect(Select):
else:
selected_html = ''
return format_html(
'<option value="{}"{} data-has-perfil={}>{}</option>',
option_value,
selected_html,
str(data_has_perfil),
force_text(option_label))
'<option value="{}"{} data-has-perfil={}>{}</option>',
option_value,
selected_html,
str(data_has_perfil),
force_text(option_label))
def render_options(self, choices, selected_choices):
# Normalize to strings.
@ -986,10 +1022,14 @@ class ProposicaoForm(forms.ModelForm):
texto_original = self.cleaned_data.get('texto_original', False)
if texto_original:
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
def clean(self):
super(ProposicaoForm, self).clean()
cd = self.cleaned_data
tm, am, nm = (cd.get('tipo_materia', ''),
@ -1106,6 +1146,18 @@ class ConfirmarProposicaoForm(ProposicaoForm):
if 'numero_de_paginas' not in self._meta.fields:
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
super(ProposicaoForm, self).__init__(*args, **kwargs)
@ -1137,13 +1189,17 @@ class ConfirmarProposicaoForm(ProposicaoForm):
css_class="ementa_materia hidden alert-info",
dismiss=False), 12))))
itens_incorporacao = [to_column(('regime_tramitacao', 4))]
if self.proposicao_incorporacao_obrigatoria == 'C':
itens_incorporacao.append(to_column((InlineRadios(
'gerar_protocolo'), 4)))
itens_incorporacao = []
if self.instance.tipo.content_type.model_class() == \
TipoMateriaLegislativa:
itens_incorporacao = [to_column(('regime_tramitacao', 4))]
if self.proposicao_incorporacao_obrigatoria == 'C':
itens_incorporacao.append(to_column((InlineRadios(
'gerar_protocolo'), 4)))
if self.proposicao_incorporacao_obrigatoria != 'N':
itens_incorporacao.append(to_column(('numero_de_paginas', 4)))
if self.proposicao_incorporacao_obrigatoria != 'N':
itens_incorporacao.append(to_column(('numero_de_paginas', 4)))
itens_incorporacao.append(to_column((FormActions(Submit(
'incorporar', _('Incorporar'), css_class='pull-right')), 12)))
@ -1180,6 +1236,14 @@ class ConfirmarProposicaoForm(ProposicaoForm):
self.fields['gerar_protocolo'].initial = True
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:
cd = ProposicaoForm.clean(self)
@ -1367,6 +1431,9 @@ class ConfirmarProposicaoForm(ProposicaoForm):
proposicao.conteudo_gerado_related = conteudo_gerado
proposicao.save()
if self.instance.tipo.content_type.model_class() == TipoDocumento:
return self.instance
# Nunca gerar protocolo
if self.proposicao_incorporacao_obrigatoria == 'N':
return self.instance
@ -1402,7 +1469,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
protocolo.data = date.today()
protocolo.hora = datetime.now().time()
# TODO transformar campo timestamp em auto_now_add
# TODO transformar campo timestamp em auto_now_add
protocolo.timestamp = datetime.now()
protocolo.tipo_protocolo = '1'

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
import reversion
from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
@ -9,6 +8,7 @@ from django.db import models
from django.utils import formats
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
import reversion
from sapl.base.models import Autor
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,
texto_upload_path)
EM_TRAMITACAO = [(1, 'Sim'),
(0, 'Não')]
@ -33,7 +34,13 @@ def grupo_autor():
@reversion.register()
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
# retire o comentário quando resolver
@ -121,6 +128,14 @@ TIPO_APRESENTACAO_CHOICES = Choices(('O', 'oral', _('Oral')),
('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()
class MateriaLegislativa(models.Model):
@ -194,19 +209,27 @@ class MateriaLegislativa(models.Model):
texto_original = models.FileField(
blank=True,
null=True,
upload_to=texto_upload_path,
upload_to=materia_upload_path,
verbose_name=_('Texto Original'),
validators=[restringe_tipos_de_arquivo_txt])
texto_articulado = GenericRelation(
TextoArticulado, related_query_name='texto_articulado')
proposicao = GenericRelation(
'Proposicao', related_query_name='proposicao')
autores = models.ManyToManyField(
Autor,
through='Autoria',
through_fields=('materia', 'autor'),
symmetrical=False,)
data_ultima_atualizacao = models.DateTimeField(
blank=True, null=True,
auto_now=True,
verbose_name=_('Data'))
class Meta:
verbose_name = _('Matéria Legislativa')
verbose_name_plural = _('Matérias Legislativas')
@ -236,6 +259,10 @@ class MateriaLegislativa(models.Model):
if self.texto_original:
self.texto_original.delete()
for p in self.proposicao.all():
p.conteudo_gerado_related = None
p.save()
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
@ -281,7 +308,7 @@ class Autoria(models.Model):
@reversion.register()
class AcompanhamentoMateria(models.Model):
usuario = models.CharField(max_length=50)
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.PROTECT)
materia = models.ForeignKey(MateriaLegislativa)
email = models.EmailField(
max_length=100, verbose_name=_('E-mail'))
data_cadastro = models.DateField(auto_now_add=True)
@ -392,10 +419,18 @@ class DocumentoAcessorio(models.Model):
arquivo = models.FileField(
blank=True,
null=True,
upload_to=texto_upload_path,
upload_to=anexo_upload_path,
verbose_name=_('Texto Integral'),
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:
verbose_name = _('Documento Acessório')
verbose_name_plural = _('Documentos Acessórios')
@ -411,6 +446,10 @@ class DocumentoAcessorio(models.Model):
if self.arquivo:
self.arquivo.delete()
for p in self.proposicao.all():
p.conteudo_gerado_related = None
p.save()
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
@ -625,7 +664,7 @@ class Proposicao(models.Model):
('I', 'Incorporada')),
verbose_name=_('Status Proposição'))
texto_original = models.FileField(
upload_to=texto_upload_path,
upload_to=materia_upload_path,
blank=True,
null=True,
verbose_name=_('Texto Original'),
@ -834,4 +873,4 @@ class Tramitacao(models.Model):
return _('%(materia)s | %(status)s | %(data)s') % {
'materia': self.materia,
'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 sapl.utils import save_texto, delete_texto
import django.dispatch
from .models import DocumentoAcessorio, MateriaLegislativa
post_save.connect(save_texto, sender=MateriaLegislativa)
post_save.connect(save_texto, sender=DocumentoAcessorio)
post_delete.connect(delete_texto, sender=MateriaLegislativa)
post_delete.connect(delete_texto, sender=DocumentoAcessorio)
tramitacao_signal = django.dispatch.Signal(providing_args=['post', 'request'])

2
sapl/materia/tests/test_email_templates.py

@ -1,6 +1,6 @@
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():

2
sapl/materia/urls.py

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

536
sapl/materia/views.py

@ -1,28 +1,47 @@
from datetime import datetime
from datetime import datetime, date
from random import choice
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.layout import HTML
from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.core.mail import send_mail
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import (ObjectDoesNotExist,
MultipleObjectsReturned)
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.http import HttpResponse, JsonResponse
from django.http.response import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.template import Context, loader
from django.utils import formats
from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, TemplateView, UpdateView
from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView
from django_filters.views import FilterView
import sapl
from sapl.base.models import Autor, CasaLegislativa
from sapl.comissoes.models import Comissao
from sapl.comissoes.models import Comissao, Participacao
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT,
STATUS_TA_PRIVATE)
@ -33,29 +52,18 @@ from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL,
Crud, CrudAux, MasterDetailCrud,
PermissionRequiredForAppCrudMixin, make_pagination)
from sapl.materia.forms import (AnexadaForm, ConfirmarProposicaoForm,
LegislacaoCitadaForm, ProposicaoForm,
TipoProposicaoForm)
LegislacaoCitadaForm, AutoriaForm, ProposicaoForm,
TipoProposicaoForm, TramitacaoForm,
TramitacaoUpdateForm)
from sapl.materia.models import Autor
from sapl.norma.models import LegislacaoCitada
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import Protocolo
from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label,
autor_modal, gerar_hash_arquivo, get_base_url,
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')
@ -84,8 +92,8 @@ def proposicao_texto(request, pk):
proposicao = Proposicao.objects.get(pk=pk)
if proposicao.texto_original:
if not proposicao.data_recebimento:
if proposicao.autor.user_id != request.user.id:
if (not proposicao.data_recebimento and
proposicao.autor.user_id != request.user.id):
raise Http404
arquivo = proposicao.texto_original
@ -162,19 +170,43 @@ class CriarProtocoloMateriaView(CreateView):
context = super(
CriarProtocoloMateriaView, self).get_context_data(**kwargs)
protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
try:
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['numero'].initial = protocolo.numero
context['form'].fields['numero'].initial = numero
context['form'].fields['ano'].initial = protocolo.ano
context['form'].fields['data_apresentacao'].initial = protocolo.data
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
def form_valid(self, form):
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))
@ -419,7 +451,7 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView):
hasher = gerar_hash_arquivo(
proposicao.texto_original.path,
str(proposicao.pk)) \
if proposicao.texto_original else None
if proposicao.texto_original else None
if hasher == form.cleaned_data['cod_hash']:
return HttpResponseRedirect(
reverse('sapl.materia:proposicao-confirmar',
@ -524,7 +556,7 @@ class ProposicaoCrud(Crud):
class BaseMixin(Crud.BaseMixin):
list_field_names = ['data_envio', 'data_recebimento', 'descricao',
'tipo']
'tipo', 'conteudo_gerado_related']
class BaseLocalMixin:
form_class = ProposicaoForm
@ -826,6 +858,7 @@ class RelatoriaCrud(MasterDetailCrud):
composicao=composicao)
parlamentares = []
parlamentares.append(['', '---------'])
for p in participacao:
if p.titular:
parlamentares.append(
@ -854,6 +887,28 @@ class RelatoriaCrud(MasterDetailCrud):
class UpdateView(MasterDetailCrud.UpdateView):
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):
model = Tramitacao
@ -868,33 +923,77 @@ class TramitacaoCrud(MasterDetailCrud):
ordering = '-data_tramitacao',
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):
local = MateriaLegislativa.objects.get(
pk=self.kwargs['pk']).tramitacao_set.last()
pk=self.kwargs['pk']).tramitacao_set.order_by(
'-data_tramitacao').first()
if local:
self.initial['unidade_tramitacao_local'
] = local.unidade_tramitacao_destino.pk
else:
self.initial['unidade_tramitacao_local'] = ''
self.initial['data_tramitacao'] = datetime.now()
return self.initial
def post(self, request, *args, **kwargs):
materia = MateriaLegislativa.objects.get(id=kwargs['pk'])
do_envia_email_tramitacao(request, materia)
return super(CreateView, self).post(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
class UpdateView(MasterDetailCrud.UpdateView):
primeira_tramitacao = not(Tramitacao.objects.filter(
materia_id=int(kwargs['root_pk'])).exists())
def post(self, request, *args, **kwargs):
materia = MateriaLegislativa.objects.get(
tramitacao__id=kwargs['pk'])
do_envia_email_tramitacao(request, materia)
return super(UpdateView, self).post(request, *args, **kwargs)
# Se não for a primeira tramitação daquela matéria, o campo
# não pode ser modificado
if not primeira_tramitacao:
context['form'].fields[
'unidade_tramitacao_local'].widget.attrs['disabled'] = True
return context
def form_valid(self, form):
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
def layout_key(self):
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):
def get_queryset(self):
@ -951,25 +1050,21 @@ class DocumentoAcessorioCrud(MasterDetailCrud):
form_class = DocumentoAcessorioForm
def __init__(self, **kwargs):
montar_helper_documento_acessorio(self)
super(MasterDetailCrud.CreateView, self).__init__(**kwargs)
def get_context_data(self, **kwargs):
context = super(
MasterDetailCrud.CreateView, self).get_context_data(**kwargs)
context['helper'] = self.helper
return context
class UpdateView(MasterDetailCrud.UpdateView):
form_class = DocumentoAcessorioForm
def __init__(self, **kwargs):
montar_helper_documento_acessorio(self)
super(MasterDetailCrud.UpdateView, self).__init__(**kwargs)
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
context['helper'] = self.helper
return context
@ -979,6 +1074,32 @@ class AutoriaCrud(MasterDetailCrud):
help_path = ''
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):
model = DespachoInicial
@ -1222,25 +1343,41 @@ class DocumentoAcessorioView(PermissionRequiredMixin, CreateView):
class AcompanhamentoConfirmarView(TemplateView):
def get_redirect_url(self):
return reverse('sapl.sessao:list_pauta_sessao')
def get_redirect_url(self, email):
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):
materia_id = kwargs['pk']
hash_txt = request.GET.get('hash_txt', '')
acompanhar = AcompanhamentoMateria.objects.get(materia_id=materia_id,
hash=hash_txt)
try:
acompanhar = AcompanhamentoMateria.objects.get(
materia_id=materia_id,
hash=hash_txt)
except ObjectDoesNotExist:
raise Http404()
# except MultipleObjectsReturned:
# A melhor solução deve ser permitir que a exceção
# (MultipleObjectsReturned) seja lançada e vá para o log,
# pois só poderá ser causada por um erro de desenvolvimente
acompanhar.confirmado = True
acompanhar.save()
return HttpResponseRedirect(self.get_redirect_url())
return HttpResponseRedirect(self.get_redirect_url(acompanhar.email))
class AcompanhamentoExcluirView(TemplateView):
def get_redirect_url(self):
return reverse('sapl.sessao:list_pauta_sessao')
def get_success_url(self):
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):
materia_id = kwargs['pk']
@ -1252,7 +1389,7 @@ class AcompanhamentoExcluirView(TemplateView):
except ObjectDoesNotExist:
pass
return HttpResponseRedirect(self.get_redirect_url())
return HttpResponseRedirect(self.get_success_url())
class MateriaLegislativaPesquisaView(FilterView):
@ -1270,7 +1407,7 @@ class MateriaLegislativaPesquisaView(FilterView):
unidade_destino = self.request.GET.get(
'tramitacao__unidade_tramitacao_destino')
qs = self.get_queryset()
qs = self.get_queryset().distinct()
if status_tramitacao and unidade_destino:
lista = filtra_tramitacao_destino_and_status(status_tramitacao,
@ -1337,237 +1474,62 @@ class AcompanhamentoMateriaView(CreateView):
materia = MateriaLegislativa.objects.get(id=pk)
if form.is_valid():
email = form.cleaned_data['email']
usuario = request.user
hash_txt = self.get_random_chars()
try:
AcompanhamentoMateria.objects.get(
email=email,
materia=materia,
hash=hash_txt)
except ObjectDoesNotExist:
acompanhar = form.save(commit=False)
acompanhar = AcompanhamentoMateria.objects.get_or_create(
materia=materia,
email=form.data['email'])
# Se o segundo elemento do retorno do get_or_create for True
# quer dizer que o elemento não existia
if acompanhar[1]:
acompanhar = acompanhar[0]
acompanhar.hash = hash_txt
acompanhar.materia = materia
acompanhar.usuario = usuario.username
acompanhar.confirmado = False
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:
msg = _('Este e-mail já está acompanhando essa matéria.')
messages.add_message(request, messages.INFO, msg)
return self.render_to_response(
{'form': form,
'materia': materia,
'error': _('Essa matéria já está\
sendo acompanhada por este e-mail.')})
return self.form_valid(form)
return HttpResponseRedirect(self.get_success_url())
else:
return self.render_to_response(
{'form': form,
'materia': materia})
def get_success_url(self):
return reverse('sapl.sessao:list_pauta_sessao')
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
return reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': self.kwargs['pk']})
class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
@ -1586,6 +1548,8 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
qr = self.request.GET.copy()
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 ''
return context
@ -1600,15 +1564,15 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
tipo = TipoDocumento.objects.get(descricao=request.POST['tipo'])
for materia_id in marcadas:
DocumentoAcessorio.objects.create(
materia_id=materia_id,
tipo=tipo,
arquivo=request.POST['arquivo'],
nome=request.POST['nome'],
data=datetime.strptime(request.POST['data'], "%d/%m/%Y"),
autor=Autor.objects.get(id=request.POST['autor']),
ementa=request.POST['ementa']
)
doc = DocumentoAcessorio()
doc.materia_id = materia_id
doc.tipo = tipo
doc.arquivo = request.FILES['arquivo']
doc.nome = request.POST['nome']
doc.data = datetime.strptime(request.POST['data'], "%d/%m/%Y")
doc.autor = request.POST['autor']
doc.ementa = request.POST['ementa']
doc.save()
msg = _('Documento(s) criado(s).')
messages.add_message(request, messages.SUCCESS, msg)
return self.get(request, self.kwargs)
@ -1638,6 +1602,8 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
context['urgente_tramitacao'] = YES_NO_CHOICES
context['unidade_local'] = UnidadeTramitacao.objects.all()
context['primeira_tramitacao'] = True
# Pega somente matéria que não possuem tramitação
if (type(self.__dict__['filterset']).__name__ ==
'PrimeiraTramitacaoEmLoteFilterSet'):
@ -1674,8 +1640,12 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
else:
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:
Tramitacao.objects.create(
t = Tramitacao(
materia_id=materia_id,
data_tramitacao=datetime.strptime(
request.POST['data_tramitacao'], "%d/%m/%Y"),
@ -1685,11 +1655,12 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
'unidade_tramitacao_local'],
unidade_tramitacao_destino_id=request.POST[
'unidade_tramitacao_destino'],
urgente=request.POST['urgente'],
urgente=urgente,
status_id=request.POST['status'],
turno=request.POST['turno'],
texto=request.POST['texto']
)
t.save()
msg = _('Tramitação completa.')
messages.add_message(request, messages.SUCCESS, msg)
return self.get(request, self.kwargs)
@ -1697,3 +1668,24 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
class TramitacaoEmLoteView(PrimeiraTramitacaoEmLoteView):
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,
label=u'Ano',
label='Ano',
choices=ANO_CHOICES)
ementa = django_filters.CharFilter(lookup_expr='icontains')
@ -118,6 +118,8 @@ class NormaJuridicaForm(ModelForm):
widgets = {'assuntos': widgets.CheckboxSelectMultiple}
def clean(self):
super(NormaJuridicaForm, self).clean()
cleaned_data = self.cleaned_data
if (cleaned_data['tipo_materia'] and
@ -142,7 +144,9 @@ class NormaJuridicaForm(ModelForm):
texto_integral = self.cleaned_data.get('texto_integral', False)
if texto_integral:
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
def save(self, commit=False):
@ -175,6 +179,8 @@ class NormaRelacionadaForm(ModelForm):
super(NormaRelacionadaForm, self).__init__(*args, **kwargs)
def clean(self):
super(NormaRelacionadaForm, self).clean()
if self.errors:
return self.errors
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):
return self.descricao
def norma_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath=instance.ano)
@reversion.register()
class NormaJuridica(models.Model):
@ -67,10 +69,11 @@ class NormaJuridica(models.Model):
('F', 'federal', _('Federal')),
('M', 'municipal', _('Municipal')),
)
texto_integral = models.FileField(
blank=True,
null=True,
upload_to=texto_upload_path,
upload_to=norma_upload_path,
verbose_name=_('Texto Integral'),
validators=[restringe_tipos_de_arquivo_txt])
tipo = models.ForeignKey(
@ -113,11 +116,16 @@ class NormaJuridica(models.Model):
AssuntoNorma, blank=True,
verbose_name=_('Assuntos'))
data_vigencia = models.DateField(blank=True, null=True)
timestamp = models.DateTimeField()
timestamp = models.DateTimeField(null=True)
texto_articulado = GenericRelation(
TextoArticulado, related_query_name='texto_articulado')
data_ultima_atualizacao = models.DateTimeField(
blank=True, null=True,
auto_now=True,
verbose_name=_('Data'))
class Meta:
verbose_name = _('Norma Jurídica')
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 sapl.utils import save_texto, delete_texto
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 .apps import AppConfig
from .views import (controlador_painel, cronometro_painel, get_dados_painel,
painel_mensagem_view, painel_parlamentar_view, painel_view,
from .views import (cronometro_painel, get_dados_painel, painel_mensagem_view,
painel_parlamentar_view, painel_view,
painel_votacao_view, votante_view)
app_name = AppConfig.name
@ -11,8 +11,6 @@ urlpatterns = [
url(r'^painel-principal/(?P<pk>\d+)$', painel_view,
name="painel_principal"),
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/parlamentar$', painel_parlamentar_view,
name='painel_parlamentar'),
@ -20,6 +18,6 @@ urlpatterns = [
url(r'^painel/cronometro$', cronometro_painel, name='cronometro_painel'),
# url(r'^painel/cronometro$', include(CronometroPainelCrud.get_urls())),
url(r'^voto-individual/(?P<pk>\d+)$', votante_view,
name="voto_individual"),
url(r'^voto-individual/$', votante_view,
name='voto_individual'),
]

295
sapl/painel/views.py

@ -1,7 +1,9 @@
from datetime import date
from django.contrib import messages
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.http import HttpResponse, JsonResponse
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.sessao.models import (ExpedienteMateria, OrdemDia, PresencaOrdemDia,
RegistroVotacao, SessaoPlenaria,
SessaoPlenariaPresenca, VotoNominal,
VotoParlamentar)
SessaoPlenariaPresenca, VotoParlamentar)
from sapl.utils import get_client_ip
from .models import Cronometro
@ -31,127 +32,191 @@ def check_permission(user):
return user.has_module_perms(AppConfig.label)
def votante_view(request, pk):
if not Votante.objects.filter(user=request.user).exists():
raise Http404('Você não tem permissão para votar')
context = {'head_title': str(_('Votação Individual')), 'sessao_id': pk}
# Pega sessão
sessao = SessaoPlenaria.objects.get(pk=pk)
context.update({'sessao': sessao,
'data': sessao.data_inicio,
'hora': sessao.hora_inicio})
# Inicializa presentes
presentes = []
def votacao_aberta(request):
'''
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()
# Verifica votação aberta
# Se aberta, verifica se é nominal. ID nominal == 2
ordem_dia = get_materia_aberta(pk)
expediente = get_materia_expediente_aberta(pk)
materia = None
if ordem_dia:
materia = ordem_dia.materia
if ordem_dia.tipo_votacao == VOTACAO_NOMINAL:
context.update({'materia': materia, 'ementa': materia.ementa})
presentes = PresencaOrdemDia.objects.filter(sessao_plenaria_id=pk)
else:
context.update(
{'materia': 'A matéria aberta não é votação nominal.'})
elif expediente:
materia = expediente.materia
if expediente.tipo_votacao == VOTACAO_NOMINAL:
context.update({'materia': materia, 'ementa': materia.ementa})
presentes = SessaoPlenariaPresenca.objects.filter(
sessao_plenaria_id=pk)
else:
context.update(
{'materia': 'A matéria aberta não é votação nominal.'})
else:
context.update(
{'materia': 'Nenhuma matéria com votação nominal aberta.'})
context = {'head_title': str(_('Votação Individual'))}
# Verifica se usuário possui permissão para votar
if 'parlamentares.can_vote' in request.user.get_all_permissions():
context.update({'permissao': True})
else:
context.update({'permissao': False})
# Verifica se usuário está presente na sessão
try:
votante = Votante.objects.get(user=request.user)
except ObjectDoesNotExist:
context.update({'error_message':
'Erro ao recuperar parlamentar ligado ao usuário'})
else:
parlamentar = votante.parlamentar
context.update({'presente': False})
if len(presentes) > 0:
for p in presentes:
if p.parlamentar.id == parlamentar.id:
context.update({'presente': True})
break
# Pega sessão
sessao, msg = votacao_aberta(request)
if sessao and not msg:
pk = sessao.pk
context.update({'sessao_id': pk})
context.update({'sessao': sessao,
'data': sessao.data_inicio,
'hora': sessao.hora_inicio})
# Inicializa presentes
presentes = []
# Verifica votação aberta
# Se aberta, verifica se é nominal. ID nominal == 2
ordem_dia = get_materia_aberta(pk)
expediente = get_materia_expediente_aberta(pk)
materia_aberta = None
if ordem_dia:
materia_aberta = ordem_dia
presentes = PresencaOrdemDia.objects.filter(
sessao_plenaria_id=pk).values_list(
'parlamentar_id', flat=True).distinct()
elif expediente:
materia_aberta = expediente
presentes = SessaoPlenariaPresenca.objects.filter(
sessao_plenaria_id=pk).values_list(
'parlamentar_id', flat=True).distinct()
if materia_aberta:
if materia_aberta.tipo_votacao == VOTACAO_NOMINAL:
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:
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)
if voto:
try:
voto = voto.get(parlamentar=parlamentar)
context.update({'voto_parlamentar': voto.voto})
except ObjectDoesNotExist:
context.update(
{'voto_parlamentar': 'Voto não '
'computado.'})
else:
context.update({'error_message':
'Você não está presente na '
'Ordem do Dia/Expediente em votação.'})
else:
context.update(
{'error_message': 'A matéria aberta não é do tipo '
'votação nominal.'})
else:
context.update(
{'error_message': 'Não há nenhuma matéria aberta.'})
elif not sessao and msg:
return HttpResponseRedirect('/')
else:
context.update({'error_message':
'Nenhuma matéria com votação nominal aberta.'})
context.update(
{'error_message': 'Não há nenhuma sessão com matéria aberta.'})
# Recupera o voto do parlamentar logado
try:
voto = VotoNominal.objects.get(
sessao=sessao,
parlamentar=parlamentar,
materia=materia)
except ObjectDoesNotExist:
context.update({'voto_parlamentar': 'Voto não computado.'})
else:
context.update({'voto_parlamentar': voto.voto})
context.update({'permissao': False,
'error_message': 'Usuário sem permissão para votar.'})
# Salva o voto
if request.method == 'POST':
try:
voto = VotoNominal.objects.get(
sessao=sessao,
parlamentar=parlamentar,
materia=materia)
except ObjectDoesNotExist:
voto = VotoNominal.objects.create(
sessao=sessao,
parlamentar=parlamentar,
materia=materia,
voto=request.POST['voto'],
ip=get_client_ip(request),
user=request.user)
else:
voto.voto = request.POST['voto']
voto.ip = get_client_ip(request)
voto.save()
return HttpResponseRedirect(
reverse('sapl.painel:voto_individual', kwargs={'pk': pk}))
return render(request, 'painel/voto_nominal.html', context)
@user_passes_test(check_permission)
def controlador_painel(request):
if ordem_dia:
try:
voto = VotoParlamentar.objects.get(
parlamentar=parlamentar,
ordem=ordem_dia)
except ObjectDoesNotExist:
voto = VotoParlamentar.objects.create(
parlamentar=parlamentar,
voto=request.POST['voto'],
user=request.user,
ip=get_client_ip(request),
ordem=ordem_dia)
else:
voto.voto = request.POST['voto']
voto.ip = get_client_ip(request)
voto.user = request.user
voto.save()
painel_created = Painel.objects.get_or_create(data_painel=date.today())
painel = painel_created[0]
elif expediente:
try:
voto = VotoParlamentar.objects.get(
parlamentar=parlamentar,
expediente=expediente)
except ObjectDoesNotExist:
voto = VotoParlamentar.objects.create(
parlamentar=parlamentar,
voto=request.POST['voto'],
user=request.user,
ip=get_client_ip(request),
expediente=expediente)
else:
voto.voto = request.POST['voto']
voto.ip = get_client_ip(request)
voto.user = request.user
voto.save()
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()
return HttpResponseRedirect(
reverse('sapl.painel:voto_individual'))
context = {'painel': painel, 'PAINEL_TYPES': Painel.PAINEL_TYPES}
return render(request, 'painel/controlador.html', context)
return render(request, 'painel/voto_nominal.html', context)
@user_passes_test(check_permission)
@ -249,7 +314,8 @@ def get_presentes(pk, response, materia):
'num_presentes_sessao_plenaria': num_presentes_sessao_plen,
'status_painel': 'ABERTO',
'msg_painel': str(_('Votação aberta!')),
'tipo_resultado': tipo_votacao,
'tipo_resultado': materia.resultado,
'tipo_votacao': tipo_votacao,
'observacao_materia': materia.observacao,
'materia_legislativa_texto': str(materia.materia)})
@ -502,12 +568,12 @@ def get_dados_painel(request, pk):
else:
if ultimo_expediente_votado.tipo_votacao in [1, 3]:
return JsonResponse(
get_votos(get_presentes(
get_votos(get_presentes_expediente(
pk, response, ultimo_expediente_votado),
ultimo_expediente_votado))
elif ultimo_expediente_votado.tipo_votacao == 2:
return JsonResponse(
get_votos_nominal(get_presentes(
get_votos_nominal(get_presentes_expediente(
pk, response,
ultimo_expediente_votado),
ultimo_expediente_votado))
@ -518,8 +584,9 @@ def get_dados_painel(request, pk):
pk, response, ultima_ordem_votada))
# Caso a Ordem do dia não tenha resultado, mostra o último expediente
if last_expediente_voto:
return JsonResponse(get_presentes(pk, response,
ultimo_expediente_votado))
return JsonResponse(get_presentes_expediente(
pk, response,
ultimo_expediente_votado))
# Retorna que não há nenhuma matéria já votada ou aberta
return response_nenhuma_materia(response)

15
sapl/parlamentares/forms.py

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

1
sapl/parlamentares/legacy.yaml

@ -94,7 +94,6 @@ TipoAfastamento:
Mandato:
coligacao: cod_coligacao
data_expedicao_diploma: dat_expedicao_diploma
data_fim_mandato: dat_fim_mandato
legislatura: num_legislatura
observacao: txt_observacao
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
import reversion
from django.db import models
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
import reversion
from sapl.base.models import Autor
from sapl.utils import (INDICADOR_AFASTAMENTO, UF, YES_NO_CHOICES,
@ -26,17 +26,10 @@ class Legislatura(models.Model):
def atual(self):
current_year = datetime.now().year
if(self.data_inicio.year <= current_year and
self.data_fim.year >= current_year):
return True
else:
return False
return self.data_inicio.year <= current_year <= self.data_fim.year
def __str__(self):
if self.atual():
current = ' (%s)' % _('Atual')
else:
current = ''
current = ' (%s)' % _('Atual') if self.atual() else ''
return _('%(numero)sª (%(start)s - %(end)s)%(current)s') % {
'numero': self.numero,
@ -206,6 +199,15 @@ def foto_upload_path(instance, filename):
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()
class Parlamentar(models.Model):
FEMININO = 'F'
@ -311,7 +313,7 @@ class Parlamentar(models.Model):
ordering = ['nome_parlamentar']
def __str__(self):
return self.nome_completo
return self.nome_parlamentar
@property
def filiacao_atual(self):
@ -447,7 +449,12 @@ class Mandato(models.Model):
on_delete=models.PROTECT, verbose_name=_('Coligação'))
# TODO what is this field??????
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(
blank=True, null=True, verbose_name=_('Votos Recebidos (Mandato)'))
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'] ==
['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'] ==
['Este campo é obrigatório.'])

29
sapl/parlamentares/urls.py

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

281
sapl/parlamentares/views.py

@ -1,11 +1,13 @@
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.db.models import F, Q
from django.http import JsonResponse
from django.http.response import HttpResponseRedirect
from django.templatetags.static import static
from django.utils.datastructures import MultiValueDictKeyError
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import FormView
from sapl.comissoes.models import Participacao
@ -23,6 +25,15 @@ from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa,
NivelInstrucao, Parlamentar, Partido, SessaoLegislativa,
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')
PartidoCrud = CrudAux.build(Partido, 'partidos')
SessaoLegislativaCrud = CrudAux.build(SessaoLegislativa, 'sessao_legislativa')
@ -31,9 +42,6 @@ NivelInstrucaoCrud = CrudAux.build(NivelInstrucao, 'nivel_instrucao')
TipoAfastamentoCrud = CrudAux.build(TipoAfastamento, 'tipo_afastamento')
TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar')
FrenteCrud = CrudAux.build(Frente, 'tipo_situa_militar', list_field_names=[
'nome', 'data_criacao', 'parlamentares'])
DependenteCrud = MasterDetailCrud.build(
Dependente, 'parlamentar', 'dependente')
@ -110,8 +118,6 @@ class ProposicaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
def get_context_data(self, **kwargs):
context = CrudBaseForListAndDetailExternalAppView\
.ListView.get_context_data(self, **kwargs)
context['title'] = context['title'].replace(
'Proposições', 'Matérias')
return context
def get_queryset(self):
@ -182,10 +188,133 @@ class ColigacaoCrud(CrudAux):
class ListView(CrudAux.ListView):
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):
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):
model = Mandato
parent_field = 'parlamentar'
@ -199,6 +328,22 @@ class MandatoCrud(MasterDetailCrud):
class ListView(MasterDetailCrud.ListView):
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):
form_class = MandatoForm
@ -242,6 +387,22 @@ class LegislaturaCrud(CrudAux):
class UpdateView(CrudAux.UpdateView):
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):
model = Filiacao
@ -275,9 +436,18 @@ class ParlamentarCrud(Crud):
class DetailView(Crud.DetailView):
def get_template_names(self):
return ['crud/detail.html']\
if self.request.user.has_perm(self.permission(RP_CHANGE))\
else ['parlamentares/parlamentar_perfil_publico.html']
if self.request.user.has_perm(self.permission(RP_CHANGE)):
if 'iframe' not in self.request.GET:
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):
form_class = ParlamentarForm
@ -302,6 +472,10 @@ class ParlamentarCrud(Crud):
template_name = "parlamentares/parlamentares_list.html"
paginate_by = None
@xframe_options_exempt
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
def take_legislatura_id(self):
try:
return int(self.request.GET['pk'])
@ -345,6 +519,14 @@ class ParlamentarCrud(Crud):
# Tira Link do avatar_html e coloca no nome
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
if row[0][1]:
# Pega o Parlamentar por meio da pk
@ -387,15 +569,87 @@ class ParlamentarCrud(Crud):
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):
template_name = 'parlamentares/composicaomesa_form.html'
success_url = reverse_lazy('sapl.parlamentares:mesa_diretora')
def get_template_names(self):
return ['parlamentares/composicaomesa_form.html']\
if self.request.user.has_perm(
'parlamentares.change_composicaomesa')\
else ['parlamentares/public_composicaomesa_form.html']
if self.request.user.has_perm('parlamentares.change_composicaomesa'):
if 'iframe' not in self.request.GET:
if not self.request.session.get('iframe'):
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
def validation(self, request):
@ -410,6 +664,7 @@ class MesaDiretoraView(FormView):
'legislatura_selecionada': Legislatura.objects.last(),
'cargos_vagos': CargoMesa.objects.all()})
@xframe_options_exempt
def get(self, request, *args, **kwargs):
if (not Legislatura.objects.exists() or

46
sapl/protocoloadm/forms.py

@ -20,8 +20,8 @@ from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo)
TIPOS_PROTOCOLO = [('0', 'Enviado'), ('1', 'Recebido'), ('', 'Ambos')]
TIPOS_PROTOCOLO_CREATE = [('0', 'Enviado'), ('1', 'Recebido')]
TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'), ('', 'Ambos')]
TIPOS_PROTOCOLO_CREATE = [('0', 'Recebido'), ('1', 'Enviado')]
NATUREZA_PROCESSO = [('', 'Ambos'),
('0', 'Administrativo'),
@ -47,7 +47,7 @@ class ProtocoloFilterSet(django_filters.FilterSet):
}}
ano = django_filters.ChoiceFilter(required=False,
label=u'Ano',
label='Ano',
choices=ANO_CHOICES)
assunto_ementa = django_filters.CharFilter(lookup_expr='icontains')
@ -135,11 +135,11 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
}}
ano = django_filters.ChoiceFilter(required=False,
label=u'Ano',
label='Ano',
choices=ANO_CHOICES)
tramitacao = django_filters.ChoiceFilter(required=False,
label=u'Em Tramitação?',
label='Em Tramitação?',
choices=EM_TRAMITACAO)
assunto = django_filters.CharFilter(lookup_expr='icontains')
@ -213,6 +213,8 @@ class AnularProcoloAdmForm(ModelForm):
widget=forms.Textarea)
def clean(self):
super(AnularProcoloAdmForm, self).clean()
cleaned_data = super(AnularProcoloAdmForm, self).clean()
numero = cleaned_data.get("numero")
@ -409,34 +411,6 @@ class DocumentoAcessorioAdministrativoForm(ModelForm):
'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):
@ -458,6 +432,8 @@ class TramitacaoAdmForm(ModelForm):
}
def clean(self):
super(TramitacaoAdmForm, self).clean()
data_enc_form = self.cleaned_data['data_encaminhamento']
data_prazo_form = self.cleaned_data['data_fim_prazo']
data_tram_form = self.cleaned_data['data_tramitacao']
@ -530,6 +506,8 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm):
}
def clean(self):
super(TramitacaoAdmEditForm, self).clean()
local = self.instance.unidade_tramitacao_local
data_tram = self.instance.data_tramitacao
@ -570,6 +548,8 @@ class DocumentoAdministrativoForm(ModelForm):
widgets = {'protocolo': forms.HiddenInput()}
def clean(self):
super(DocumentoAdministrativoForm, self).clean()
numero_protocolo = self.data['numero_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,
ComprovanteProtocoloView,
CriarDocumentoProtocolo,
DetailDocumentoAdministrativo,
DocumentoAcessorioAdministrativoCrud,
DocumentoAcessorioAdministrativoEditView,
DocumentoAcessorioAdministrativoView,
DocumentoAdministrativoCrud,
PesquisarDocumentoAdministrativoView,
ProtocoloDocumentoView,
@ -26,20 +23,12 @@ app_name = AppConfig.name
urlpatterns_documento_administrativo = [
url(r'^docadm/',
include(DocumentoAdministrativoCrud.get_urls())),
url(r'^docadm/doc-acessorio/',
include(DocumentoAcessorioAdministrativoCrud.get_urls())),
url(r'^docadm/tramitacao-doc-adm/',
include(TramitacaoAdmCrud.get_urls())),
include(DocumentoAdministrativoCrud.get_urls() +
TramitacaoAdmCrud.get_urls() +
DocumentoAcessorioAdministrativoCrud.get_urls())),
url(r'^docadm/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,
name='doc_texto_integral'),

179
sapl/protocoloadm/views.py

@ -37,10 +37,6 @@ TipoDocumentoAdministrativoCrud = CrudAux.build(
# ProtocoloMateriaCrud = Crud.build(Protocolo, '')
DocumentoAcessorioAdministrativoCrud = Crud.build(
DocumentoAcessorioAdministrativo, '')
def doc_texto_integral(request, pk):
can_see = True
@ -87,9 +83,18 @@ class DocumentoAdministrativoCrud(Crud):
class BaseMixin(Crud.BaseMixin):
list_field_names = ['tipo', 'numero', 'ano', 'data',
'numero_protocolo', 'ano_protocolo', 'assunto',
'numero_protocolo', 'assunto',
'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):
pass
@ -116,6 +121,10 @@ class DocumentoAdministrativoCrud(Crud):
kwargs={'pk': self.object.pk}))
return context
class DeleteView(DocumentoAdministrativoMixin, Crud.DeleteView):
def get_success_url(self):
return reverse('sapl.protocoloadm:pesq_doc_adm', kwargs={})
class StatusTramitacaoAdministrativoCrud(CrudAux):
model = StatusTramitacaoAdministrativo
@ -416,6 +425,7 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
'%H:%M')
protocolo.timestamp = datetime.strptime(
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.anulado = False
@ -425,6 +435,7 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
id=self.request.POST['tipo_materia'])
protocolo.numero_paginas = self.request.POST['numero_paginas']
protocolo.observacao = self.request.POST['observacao']
protocolo.assunto_ementa = self.request.POST['assunto_ementa']
protocolo.save()
return redirect(self.get_success_url(protocolo))
@ -508,159 +519,55 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
return self.render_to_response(context)
class DetailDocumentoAdministrativo(PermissionRequiredMixin, DetailView):
template_name = "protocoloadm/detail_doc_adm.html"
permission_required = ('protocoloadm.detail_documentoadministrativo', )
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', )
class TramitacaoAdmCrud(MasterDetailCrud):
model = TramitacaoAdministrativo
parent_field = 'documento'
help_path = ''
def get(self, request, *args, **kwargs):
doc = DocumentoAdministrativo.objects.get(
id=kwargs['pk'])
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)
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['data_tramitacao', 'unidade_tramitacao_local',
'unidade_tramitacao_destino', 'status']
def get_success_url(self):
pk = self.kwargs['pk']
return reverse('sapl.protocoloadm:doc_ace_adm', kwargs={'pk': pk})
class CreateView(MasterDetailCrud.CreateView):
form_class = TramitacaoAdmForm
class UpdateView(MasterDetailCrud.UpdateView):
form_class = TramitacaoAdmEditForm
class DocumentoAcessorioAdministrativoView(PermissionRequiredMixin, FormView):
template_name = "protocoloadm/documento_acessorio_administrativo.html"
permission_required = (
'protocoloadm.add_documentoacessorioadministrativo', )
class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView):
def get(self, request, *args, **kwargs):
form = DocumentoAcessorioAdministrativoForm()
doc = DocumentoAdministrativo.objects.get(
id=kwargs['pk'])
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_queryset(self):
qs = super(MasterDetailCrud.ListView, self).get_queryset()
kwargs = {self.crud.parent_field: self.kwargs['pk']}
return qs.filter(**kwargs).order_by('-data_tramitacao', '-id')
def get_success_url(self):
pk = self.kwargs['pk']
return reverse('sapl.protocoloadm:doc_ace_adm', kwargs={'pk': pk})
class DetailView(DocumentoAdministrativoMixin,
MasterDetailCrud.DetailView):
pass
class TramitacaoAdmCrud(MasterDetailCrud):
model = TramitacaoAdministrativo
class DocumentoAcessorioAdministrativoCrud(MasterDetailCrud):
model = DocumentoAcessorioAdministrativo
parent_field = 'documento'
help_path = ''
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['data_tramitacao', 'unidade_tramitacao_local',
'unidade_tramitacao_destino', 'status']
list_field_names = ['nome', 'tipo',
'data', 'autor',
'assunto']
class CreateView(MasterDetailCrud.CreateView):
form_class = TramitacaoAdmForm
form_class = DocumentoAcessorioAdministrativoForm
class UpdateView(MasterDetailCrud.UpdateView):
form_class = TramitacaoAdmEditForm
form_class = DocumentoAcessorioAdministrativoForm
class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView):
def get_queryset(self):
qs = super(MasterDetailCrud.ListView, self).get_queryset()
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,
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

48
sapl/relatorios/templates/pdf_sessao_plenaria_gerar.py

@ -6,6 +6,8 @@
"""
import time
from sapl.sessao.models import ResumoOrdenacao
from trml2pdf import parseString
@ -287,15 +289,43 @@ def principal(cabecalho_dic, rodape_dic, imagem, sessao, inf_basicas_dic, lst_me
tmp += '\t</template>\n'
tmp += paraStyle()
tmp += '\t<story>\n'
tmp += inf_basicas(inf_basicas_dic)
tmp += mesa(lst_mesa)
tmp += presenca(lst_presenca_sessao)
tmp += expedientes(lst_expedientes)
tmp += expediente_materia(lst_expediente_materia)
tmp += oradores_expediente(lst_oradores_expediente)
tmp += presenca_ordem_dia(lst_presenca_ordem_dia)
tmp += votacao(lst_votacao)
tmp += oradores(lst_oradores)
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 += mesa(lst_mesa)
tmp += presenca(lst_presenca_sessao)
tmp += expedientes(lst_expedientes)
tmp += expediente_materia(lst_expediente_materia)
tmp += oradores_expediente(lst_oradores_expediente)
tmp += presenca_ordem_dia(lst_presenca_ordem_dia)
tmp += votacao(lst_votacao)
tmp += oradores(lst_oradores)
tmp += '\t</story>\n'
tmp += '</document>\n'

51
sapl/relatorios/views.py

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

3
sapl/rules/map_rules.py

@ -152,8 +152,6 @@ rules_group_sessao = {
(sessao.PresencaOrdemDia, __base__),
(sessao.RegistroVotacao, __base__),
(sessao.VotoParlamentar, __base__),
(sessao.VotoNominal, __base__),
]
}
@ -251,6 +249,7 @@ rules_group_geral = {
(sessao.TipoResultadoVotacao, __base__),
(sessao.TipoExpediente, __base__),
(sessao.Bloco, __base__),
(sessao.ResumoOrdenacao, __base__),
(lexml.LexmlProvedor, __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.translation import ugettext_lazy as _
from sapl.base.models import (CasaLegislativa, ProblemaMigracao, Argumento,
Constraint)
from sapl.base.models import (Argumento, CasaLegislativa, Constraint,
ProblemaMigracao)
from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
TipoDispositivo,
TipoDispositivoRelationship)

141
sapl/sessao/forms.py

@ -28,17 +28,30 @@ def recupera_anos():
# apos a adicao do .dates(), por isso o reversed() abaixo
anos = [(k.year, k.year) for k in reversed(anos_list)]
return anos
except:
except Exception:
return []
def ANO_CHOICES():
return [('', '---------')] + recupera_anos()
MES_CHOICES = [('', '---------')] + RANGE_MESES
DIA_CHOICES = [('', '---------')] + RANGE_DIAS_MES
ORDENACAO_RESUMO = [('cont_mult', 'Conteúdo Multimídia'),
('exp', 'Expedientes'),
('id_basica', 'Identificação Básica'),
('lista_p', 'Lista de Presença'),
('lista_p_o_d', 'Lista de Presença Ordem do Dia'),
('mat_exp', 'Matérias do Expediente'),
('mat_o_d', 'Matérias da Ordem do Dia'),
('mesa_d', 'Mesa Diretora'),
('oradores_exped', 'Oradores do Expediente'),
('oradores_expli', 'Oradores das Explicações Pessoais')]
class BancadaForm(ModelForm):
class Meta:
@ -47,6 +60,8 @@ class BancadaForm(ModelForm):
'data_extincao', 'descricao']
def clean(self):
super(BancadaForm, self).clean()
if self.cleaned_data['data_extincao']:
if (self.cleaned_data['data_extincao'] <
self.cleaned_data['data_criacao']):
@ -57,6 +72,8 @@ class BancadaForm(ModelForm):
class ExpedienteMateriaForm(ModelForm):
_model = ExpedienteMateria
tipo_materia = forms.ModelChoiceField(
label=_('Tipo Matéria'),
required=True,
@ -87,7 +104,7 @@ class ExpedienteMateriaForm(ModelForm):
sessao_plenaria=sessao,
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.')
raise ValidationError(msg)
@ -97,6 +114,8 @@ class ExpedienteMateriaForm(ModelForm):
return self.instance.sessao_plenaria.data_inicio
def clean(self):
super(ExpedienteMateriaForm, self).clean()
cleaned_data = self.cleaned_data
sessao = self.instance.sessao_plenaria
@ -112,11 +131,11 @@ class ExpedienteMateriaForm(ModelForm):
else:
cleaned_data['materia'] = materia
ex = ExpedienteMateria.objects.filter(
exists = self._model.objects.filter(
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.')
raise ValidationError(msg)
@ -131,6 +150,8 @@ class ExpedienteMateriaForm(ModelForm):
class OrdemDiaForm(ExpedienteMateriaForm):
_model = OrdemDia
class Meta:
model = OrdemDia
fields = ['data_ordem', 'numero_ordem', 'tipo_materia', 'observacao',
@ -139,47 +160,22 @@ class OrdemDiaForm(ExpedienteMateriaForm):
def clean_data_ordem(self):
return self.instance.sessao_plenaria.data_inicio
def clean_numero_ordem(self):
sessao = self.instance.sessao_plenaria
numero_ordem_exists = OrdemDia.objects.filter(
sessao_plenaria=sessao,
numero_ordem=self.cleaned_data[
'numero_ordem']).exists()
sessao_plenaria=sessao,
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.')
raise ValidationError(msg)
return self.cleaned_data['numero_ordem']
def clean(self):
cleaned_data = self.cleaned_data
sessao = self.instance.sessao_plenaria
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
super(OrdemDiaForm, self).clean()
return self.cleaned_data
def save(self, commit=False):
ordem = super(OrdemDiaForm, self).save(commit)
@ -224,13 +220,13 @@ class VotacaoEditForm(forms.Form):
class SessaoPlenariaFilterSet(django_filters.FilterSet):
data_inicio__year = django_filters.ChoiceFilter(required=False,
label=u'Ano',
label='Ano',
choices=ANO_CHOICES)
data_inicio__month = django_filters.ChoiceFilter(required=False,
label=u'Mês',
label='Mês',
choices=MES_CHOICES)
data_inicio__day = django_filters.ChoiceFilter(required=False,
label=u'Dia',
label='Dia',
choices=DIA_CHOICES)
titulo = _('Pesquisa de Sessão Plenária')
@ -359,3 +355,72 @@ class OradorExpedienteForm(ModelForm):
class PautaSessaoFilterSet(SessaoPlenariaFilterSet):
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):
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)
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)
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)
@ -412,11 +414,39 @@ class RegistroVotacao(models.Model):
@reversion.register()
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)
# XXX change to restricted choices
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:
verbose_name = _('Registro de Votação de Parlamentar')
verbose_name_plural = _('Registros de Votações de Parlamentares')
@ -426,28 +456,6 @@ class VotoParlamentar(models.Model): # RegistroVotacaoParlamentar
'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()
class SessaoPlenariaPresenca(models.Model):
sessao_plenaria = models.ForeignKey(SessaoPlenaria,
@ -493,3 +501,28 @@ class Bloco(models.Model):
def __str__(self):
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
class SessaoPlenariaSerializer(serializers.Serializer):
class Meta:
model = SessaoPlenaria

30
sapl/sessao/urls.py

@ -10,19 +10,22 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente,
PautaSessaoDetailView, PautaSessaoListView,
PesquisarPautaSessaoView,
PesquisarSessaoPlenariaView,
PresencaOrdemDiaView, PresencaView, ResumoView,
SessaoCrud, TipoExpedienteCrud,
TipoResultadoVotacaoCrud, TipoSessaoCrud,
VotacaoEditView, VotacaoExpedienteEditView,
PresencaOrdemDiaView, PresencaView,
ResumoOrdenacaoView, ResumoView, SessaoCrud,
TipoExpedienteCrud, TipoResultadoVotacaoCrud,
TipoSessaoCrud, VotacaoEditView,
VotacaoExpedienteEditView,
VotacaoExpedienteView, VotacaoNominalEditView,
VotacaoNominalExpedienteEditView,
VotacaoNominalExpedienteDetailView,
VotacaoNominalExpedienteEditView,
VotacaoNominalExpedienteView,
VotacaoNominalView, VotacaoView,
abrir_votacao_expediente_view,
abrir_votacao_ordem_view,
abrir_votacao_ordem_view, atualizar_mesa,
insere_parlamentar_composicao,
mudar_ordem_materia_sessao, recuperar_materia,
recuperar_numero_sessao,
remove_parlamentar_composicao,
reordernar_materias_expediente,
reordernar_materias_ordem,
sessao_legislativa_legislatura_ajax)
@ -40,6 +43,18 @@ urlpatterns = [
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-numero-sessao/', recuperar_numero_sessao),
url(r'^sessao/sessao-legislativa-legislatura-ajax/',
@ -68,6 +83,9 @@ urlpatterns = [
include(BlocoCrud.get_urls())),
url(r'^sistema/cargo-bancada/',
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/',
AdicionarVariasMateriasExpediente.as_view(),
name='adicionar_varias_materias_expediente'),

1178
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.painel',
'sapl.protocoloadm',
'sapl.redireciona_urls',
'sapl.compilacao',
'sapl.api',
@ -85,6 +86,7 @@ INSTALLED_APPS = (
) + SAPL_APPS
# FTS = Full Text Search
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
SEARCH_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine'
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
AUTH_USER_MODEL = 'auth.User'
X_FRAME_OPTIONS = 'ALLOWALL'
EMAIL_HOST = config('EMAIL_HOST', default='localhost')
EMAIL_PORT = config('EMAIL_PORT', cast=int, default=587)
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='')
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
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'pt-br'
LANGUAGES = (
('pt-br', u'Português'),
('pt-br','Português'),
)
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() {
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?q=" + query, function(data, status) {
$.get("/api/autor?" + query, function(data, status) {
$("#div-resultado").children().remove();
if (data.pagination.total_entries == 0) {
$("#selecionar").attr("hidden", "hidden");

2
sapl/static/styles/app.scss

@ -235,6 +235,8 @@ fieldset {
.avatar-parlamentar {
height: 84px;
width: 84px;
margin: 0 auto;
display: table;
}
/* 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>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>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> <a href="sessao_plenaria">Anterior</a> |

28
sapl/templates/base.html

@ -28,8 +28,8 @@
<body>
<div class="page fadein">
{% if not request|has_iframe %}
{% block navigation %}
<nav class="navbar navbar-inverse navbar-static-top">
<div class="container">
<div class="navbar-header">
@ -64,6 +64,11 @@
</a>
<ul class="dropdown-menu">
<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>
</ul>
</li>
@ -100,11 +105,19 @@
</div>
</header>
{% endblock main_header %}
{% else %}
<header class="masthead">
<div class="container">
<div class="hidden-print">
{% subnav %}
</div>
</div>
</header>
{% endif %}
{# Main content #}
{% block content_container %}
<main id="content" class="content page__row">
<div class="container">
{# Feedback messages #}
@ -148,7 +161,7 @@
{% endblock content_container %}
{% if not request|has_iframe %}
{% block footer_container %}
<footer id="footer" class="footer page__row hidden-print">
<div class="container">
@ -157,11 +170,13 @@
<div class="col-md-4">
<a class="footer__logo" href="#">
<img src="{% static 'img/logo_interlegis.png' %}" alt="{% trans 'Logo do Interlegis' %} ">
<a href="http://www.interlegis.leg.br/">
<img src="{% static 'img/logo_interlegis.png' %}" alt="{% trans 'Logo do Interlegis' %} ">
</a>
</a>
<p>
<small>
Desenvolvido pelo <a href="#">Interlegis</a> em software livre e aberto.
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small>
</p>
</div>
@ -171,7 +186,7 @@
</a>
<p>
<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>
</p>
</div>
@ -203,6 +218,7 @@
</footer>
</div>
{% endblock footer_container %}
{% endif %}
{% block foot_js %}
<!-- Bootstrap core JavaScript ================================================== -->

2
sapl/templates/base/RelatorioMateriasPorTramitacao_filter.html

@ -45,7 +45,7 @@
<td><a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}">
{{materia.tipo.descricao}} {{materia.numero}}/{{materia.ano}}
</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>
</tr>
{% endfor %}

2
sapl/templates/compilacao/textoarticulado_detail.html

@ -10,7 +10,7 @@
{% 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>
{% 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%}
<a href="{% url 'sapl.compilacao:ta_detail' object.pk %}">{% trans 'Início' %}</a>
{%endif%}

4
sapl/templates/crud/detail.html

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

6
sapl/templates/crud/list.html

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

14
sapl/templates/email/acompanhar.html

@ -1,14 +1,12 @@
{% load i18n %}
{% load static %}
<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>
<br/>
Sistema de Apoio ao Processo Legislativo
</h2>
<h2 align='center'><b>{{casa_legislativa}}</b>
<br/>
Sistema de Apoio ao Processo Legislativo
</h2>
<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/>
{{ementa}}<br/>

49
sapl/templates/email/tramitacao.html

@ -1,38 +1,35 @@
{% load i18n %}
{% load static %}
<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>
<br/>
Sistema de Apoio ao Processo Legislativo
</h2>
<p>A seguinte mat&eacute;ria de seu interesse sofreu
tramita&ccedil;&atilde;o registrada em {{data_registro}}
</p>
<head></head>
<body bgcolor='#ffffff'>
<h2 align='center'><b>{{casa_legislativa}}</b>
<br/>
Sistema de Apoio ao Processo Legislativo
</h2>
<p>A seguinte mat&eacute;ria, de seu interesse, sofreu
Tramita&ccedil;&atilde;o registrada em <b>{{data_registro}}</b>.
</p>
<h4>
<a href="{{base_url}}{{materia_url}}"><b>{{materia}} - {{descricao_materia}}</b></a>
<br/><br/>
<a href="{{base_url}}{{materia_url}}"><b>{{materia}} - {{descricao_materia}}</b></a>
<br/><br/>
<b>Autoria:</b></br>
{% for autor in autoria %}
{{ autor }}</br>
{{ autor }}</br>
{% endfor %}
</h4>
<p></p>
<p>
<b>Data da a&ccedil;&atilde;o</b>: {{data}}<br/>
<b>Status</b>: {{status}}<br/>
<b>Texto da a&ccedil;&atilde;o</b>: {{texto_acao}}</p>
<hr>
<p>
<a href="{{base_url}}{{excluir_url}}?hash_txt={{hash_txt}}">
Clique aqui para excluir seu e-mail da lista de envio</a>
<p>
<p>Esta &eacute; uma mensagem autom&aacute;tica.
Por favor, n&atilde;o a responda.</p>
<b>Data da a&ccedil;&atilde;o</b>: {{data}}<br/>
<b>Status</b>: {{status}}<br/>
<b>Localização Atual:</b> {{localizacao}}<br/>
<b>Texto da a&ccedil;&atilde;o</b>: {{texto_acao}}</p>
<hr>
<p>
<a href="{{base_url}}{{excluir_url}}?hash_txt={{hash_txt}}">
Clique aqui para excluir seu e-mail da lista de envio</a>
<p>
<p>Esta &eacute; uma mensagem autom&aacute;tica.
Por favor, n&atilde;o a responda.</p>
</body>
</html>

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

Loading…
Cancel
Save