Browse Source

Merge tag '3.1.145' into migracao

migracao
Marcio Mazza 6 years ago
parent
commit
3139ccda55
  1. 4
      .dockerignore
  2. 2
      .github/ISSUE_TEMPLATE.md
  3. 4
      .gitignore
  4. 2
      .travis.yml
  5. 17
      Dockerfile
  6. 1
      MANIFEST.in
  7. 14
      README.rst
  8. 18
      check_solr.sh
  9. 1
      config/env-sample
  10. 1
      config/env_dockerfile
  11. 5
      docker-compose.yml
  12. 107
      docs/instalacao31.rst
  13. 6
      release.sh
  14. 62
      requirements/requirements.txt
  15. 1
      sapl/.env_test
  16. 670
      sapl/api/deprecated.py
  17. 268
      sapl/api/forms.py
  18. 39
      sapl/api/permissions.py
  19. 148
      sapl/api/serializers.py
  20. 58
      sapl/api/urls.py
  21. 607
      sapl/api/views.py
  22. 8
      sapl/audiencia/forms.py
  23. 34
      sapl/audiencia/migrations/0010_auto_20190219_1511.py
  24. 22
      sapl/audiencia/models.py
  25. 5
      sapl/audiencia/urls.py
  26. 5
      sapl/audiencia/views.py
  27. 2
      sapl/base/email_utils.py
  28. 389
      sapl/base/forms.py
  29. 20
      sapl/base/migrations/0027_appconfig_relatorios_atos.py
  30. 20
      sapl/base/migrations/0028_appconfig_estatisticas_acesso_normas.py
  31. 19
      sapl/base/migrations/0029_remove_appconfig_relatorios_atos.py
  32. 20
      sapl/base/migrations/0030_appconfig_escolher_numero_materia_proposicao.py
  33. 20
      sapl/base/migrations/0030_appconfig_protocolo_manual.py
  34. 20
      sapl/base/migrations/0031_auto_20190218_1109.py
  35. 16
      sapl/base/migrations/0032_merge_20190219_0941.py
  36. 31
      sapl/base/models.py
  37. 72
      sapl/base/search_indexes.py
  38. 25
      sapl/base/templatetags/common_tags.py
  39. 58
      sapl/base/templatetags/menus.py
  40. 8
      sapl/base/tests/test_login.py
  41. 56
      sapl/base/urls.py
  42. 658
      sapl/base/views.py
  43. 43
      sapl/comissoes/forms.py
  44. 20
      sapl/comissoes/migrations/0019_auto_20181214_1023.py
  45. 1
      sapl/comissoes/models.py
  46. 3
      sapl/comissoes/tests/test_comissoes.py
  47. 99
      sapl/compilacao/forms.py
  48. 10
      sapl/compilacao/models.py
  49. 3
      sapl/compilacao/templatetags/compilacao_filters.py
  50. 16
      sapl/compilacao/urls.py
  51. 76
      sapl/compilacao/views.py
  52. 49
      sapl/crispy_layout_mixin.py
  53. 15
      sapl/crud/base.py
  54. 4
      sapl/crud/tests/stub_app/templates/base.html
  55. 1
      sapl/env-backup
  56. 285
      sapl/lexml/OAIServer.py
  57. 3
      sapl/lexml/urls.py
  58. 12
      sapl/lexml/views.py
  59. 501
      sapl/materia/forms.py
  60. 35
      sapl/materia/migrations/0034_auto_20190101_1618.py
  61. 35
      sapl/materia/migrations/0034_auto_20190107_1402.py
  62. 35
      sapl/materia/migrations/0035_auto_20190104_1021.py
  63. 41
      sapl/materia/migrations/0036_auto_20190106_0330.py
  64. 16
      sapl/materia/migrations/0037_merge_20190108_1538.py
  65. 35
      sapl/materia/migrations/0038_auto_20190108_1606.py
  66. 21
      sapl/materia/migrations/0039_auto_20190209_2346.py
  67. 20
      sapl/materia/migrations/0040_auto_20190211_1602.py
  68. 20
      sapl/materia/migrations/0041_proposicao_numero_materia_futuro.py
  69. 38
      sapl/materia/models.py
  70. 11
      sapl/materia/tests/test_materia.py
  71. 7
      sapl/materia/urls.py
  72. 85
      sapl/materia/views.py
  73. 92
      sapl/norma/forms.py
  74. 25
      sapl/norma/migrations/0017_normaestatisticas.py
  75. 25
      sapl/norma/migrations/0018_auto_20190101_1618.py
  76. 20
      sapl/norma/migrations/0018_normaestatisticas_ano.py
  77. 20
      sapl/norma/migrations/0019_auto_20181219_1807.py
  78. 25
      sapl/norma/migrations/0019_auto_20190104_1021.py
  79. 31
      sapl/norma/migrations/0020_auto_20190106_0454.py
  80. 31
      sapl/norma/migrations/0020_auto_20190107_1407.py
  81. 16
      sapl/norma/migrations/0021_merge_20190108_1538.py
  82. 21
      sapl/norma/migrations/0022_auto_20190108_1606.py
  83. 19
      sapl/norma/migrations/0023_auto_20190219_1535.py
  84. 40
      sapl/norma/models.py
  85. 16
      sapl/norma/tests/test_norma.py
  86. 27
      sapl/norma/views.py
  87. 39
      sapl/parlamentares/forms.py
  88. 6
      sapl/parlamentares/models.py
  89. 72
      sapl/parlamentares/tests/test_parlamentares.py
  90. 134
      sapl/parlamentares/views.py
  91. 438
      sapl/protocoloadm/forms.py
  92. 20
      sapl/protocoloadm/migrations/0010_auto_20181212_1900.py
  93. 25
      sapl/protocoloadm/migrations/0011_auto_20190101_1618.py
  94. 25
      sapl/protocoloadm/migrations/0011_auto_20190107_1407.py
  95. 25
      sapl/protocoloadm/migrations/0012_auto_20190104_1021.py
  96. 26
      sapl/protocoloadm/migrations/0013_auto_20190106_1336.py
  97. 35
      sapl/protocoloadm/migrations/0014_auto_20190110_1300.py
  98. 16
      sapl/protocoloadm/migrations/0014_merge_20190108_1538.py
  99. 25
      sapl/protocoloadm/migrations/0015_auto_20190108_1606.py
  100. 21
      sapl/protocoloadm/migrations/0015_protocolo_timestamp_data_hora_manual.py

4
.dockerignore

@ -0,0 +1,4 @@
media
collected_static
.git
whoosh

2
.github/ISSUE_TEMPLATE.md

@ -26,7 +26,7 @@
## Imagens do Ocorrido
<!--- Representação visual em vídeo ou imagem do ocorrido -->
<!--- Se está descrevendo um bug poste imagens ou vídeos na repordução do bug citado, caso se aplique --:
<!--- Se está descrevendo um bug poste imagens ou vídeos na repordução do bug citado, caso se aplique -->
## Seu Ambiente
<!--- Inclua detalhes relevantes sobre o ambiente em que você presenciou/experienciou o bug. -->

4
.gitignore

@ -8,6 +8,7 @@ __pycache__/
# Nodejs
node_modules/
yarn.lock
# Distribution / packaging
.Python
@ -53,7 +54,7 @@ coverage.xml
# Django stuff:
*.log
sapl.log.*
*.swp
# Sphinx documentation
@ -88,6 +89,7 @@ target/
.ipynb_checkpoints/
*.ipynb
.vscode/*
*/.vscode/*
# specific to this project
whoosh_index

2
.travis.yml

@ -10,7 +10,6 @@ install:
- pip install -r requirements/test-requirements.txt
before_script:
- npm install -g bower
- cp sapl/.env_test sapl/.env
- psql -c "CREATE USER sapl WITH PASSWORD 'sapl'" -U postgres;
- psql -c "CREATE DATABASE sapl OWNER sapl;" -U postgres
@ -18,7 +17,6 @@ before_script:
script:
- ./manage.py migrate
- ./manage.py bower install
- py.test --create-db
# - ./scripts/django/test_and_check_qa.sh

17
Dockerfile

@ -2,7 +2,8 @@ FROM alpine:3.8
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 npm py3-lxml py3-magic postgresql-client poppler-utils antiword vim openssh-client
nodejs py3-lxml py3-magic postgresql-client poppler-utils antiword \
curl jq openssh-client vim openssh-client bash
RUN apk update --update-cache && apk upgrade
@ -16,9 +17,7 @@ RUN apk add --no-cache python3 nginx tzdata && \
rm -f /etc/nginx/conf.d/*
RUN mkdir -p /var/interlegis/sapl && \
apk add --update --no-cache $BUILD_PACKAGES && \
npm install -g bower && \
npm cache verify
apk add --update --no-cache $BUILD_PACKAGES
WORKDIR /var/interlegis/sapl/
@ -36,13 +35,6 @@ COPY config/env_dockerfile /var/interlegis/sapl/sapl/.env
# Configura timezone para BRT
# RUN cp /usr/share/zoneinfo/America/Sao_Paulo /etc/localtime && echo "America/Sao_Paulo" > /etc/timezone
# manage.py bower install bug: https://github.com/nvbn/django-bower/issues/51
# compilescss - Precompile all occurrences of your SASS/SCSS files for the whole project into css files
RUN python3 manage.py bower_install --allow-root && \
python3 manage.py compilescss
RUN python3 manage.py collectstatic --noinput --clear
# Remove .env(fake) e sapl.db da imagem
@ -52,7 +44,8 @@ RUN rm -rf /var/interlegis/sapl/sapl/.env && \
RUN chmod +x /var/interlegis/sapl/start.sh && \
ln -sf /dev/stdout /var/log/nginx/access.log && \
ln -sf /dev/stderr /var/log/nginx/error.log && \
mkdir /var/log/sapl/
mkdir /var/log/sapl/ && touch /var/interlegis/sapl/sapl.log && \
ln -s /var/interlegis/sapl/sapl.log /var/log/sapl/sapl.log
VOLUME ["/var/interlegis/sapl/data", "/var/interlegis/sapl/media"]

1
MANIFEST.in

@ -1,4 +1,5 @@
include README.rst LICENSE.txt
include sapl/webpack-stats.json
recursive-include sapl *.html *.yaml
recursive-include sapl/static *
recursive-include sapl/relatorios/templates *.py

14
README.rst

@ -1,4 +1,4 @@
.. image:: https://travis-ci.org/interlegis/sapl.svg?branch=master
.. image:: https://travis-ci.org/interlegis/sapl.svg?branch=3.1.x
:target: https://travis-ci.org/interlegis/sapl
@ -17,17 +17,17 @@ 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/instalacao31.rst>`_
`Instalação do Ambiente de Desenvolvimento <https://github.com/interlegis/sapl/blob/3.1.x/docs/instalacao31.rst>`_
Instalação do Solr
======================
`Instalação e configuração do Solr <https://github.com/interlegis/sapl/blob/master/docs/solr.rst>`_
`Instalação e configuração do Solr <https://github.com/interlegis/sapl/blob/3.1.x/docs/solr.rst>`_
Instruções para Deploy
======================
`Deploy SAPL com Nginx + Gunicorn <https://github.com/interlegis/sapl/blob/master/docs/deploy.rst>`_
`Deploy SAPL com Nginx + Gunicorn <https://github.com/interlegis/sapl/blob/3.1.x/docs/deploy.rst>`_
Instruções para Importação da base mysql 2.5
@ -37,19 +37,19 @@ Instruções para Importação da base mysql 2.5
Instruções para Tradução
========================
`Instruções para Tradução <https://github.com/interlegis/sapl/blob/master/docs/traducao.rst>`_
`Instruções para Tradução <https://github.com/interlegis/sapl/blob/3.1.x/docs/traducao.rst>`_
Orientações gerais de implementação
===================================
`Instruções para Implementação <https://github.com/interlegis/sapl/blob/master/docs/implementacoes.rst>`_
`Instruções para Implementação <https://github.com/interlegis/sapl/blob/3.1.x/docs/implementacoes.rst>`_
Orientações gerais sobre o GitHub
===================================
`Instruções para GitHub <https://github.com/interlegis/sapl/blob/master/docs/howtogit.rst>`_
`Instruções para GitHub <https://github.com/interlegis/sapl/blob/3.1.x/docs/howtogit.rst>`_

18
check_solr.sh

@ -0,0 +1,18 @@
#!/bin/bash
# Pass the base SOLR URL as parameter, i.e., bash check_solr http://localhost:8983
SOLR_URL=$1
echo "Waiting for solr connection at $SOLR_URL ..."
while true; do
echo "$SOLR_URL/solr/admin/collections?action=LIST"
RESULT=$(curl -s -o /dev/null -I "$SOLR_URL/solr/admin/collections?action=LIST" -w '%{http_code}')
echo $RESULT
if [ "$RESULT" -eq '200' ]; then
echo "Solr server is up!"
break
else
sleep 3
fi
done

1
config/env-sample

@ -5,4 +5,5 @@ EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST = ''
EMAIL_HOST_USER = ''
EMAIL_SEND_USER = ''
EMAIL_HOST_PASSWORD = ''

1
config/env_dockerfile

@ -5,4 +5,5 @@ EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST = ''
EMAIL_HOST_USER = ''
EMAIL_SEND_USER = ''
EMAIL_HOST_PASSWORD = ''

5
docker-compose.yml

@ -11,16 +11,17 @@ sapldb:
ports:
- "5432:5432"
sapl:
image: interlegis/sapl:3.1.137
image: interlegis/sapl:3.1.145
restart: always
environment:
ADMIN_PASSWORD: interlegis
ADMIN_EMAIL: email@dominio.net
DEBUG: 'False'
USE_TLS: 'False'
EMAIL_PORT: 587
EMAIL_USE_TLS: 'False'
EMAIL_HOST: smtp.dominio.net
EMAIL_HOST_USER: usuariosmtp
EMAIL_SEND_USER: usuariosmtp
EMAIL_HOST_PASSWORD: senhasmtp
TZ: America/Sao_Paulo
volumes:

107
docs/instalacao31.rst

@ -28,15 +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 poppler-utils antiword default-jre python3-venv
sudo -i
curl -sL https://deb.nodesource.com/setup_8.x | bash -
exit
sudo apt-get install nodejs
sudo npm install npm -g
sudo npm install -g bower
python3-pip poppler-utils antiword default-jre python3-venv
Instalar o virtualenv usando python 3 para o projeto.
-----------------------------------------------------
@ -147,26 +139,21 @@ Criação da `SECRET_KEY <https://docs.djangoproject.com/es/1.9/ref/settings/#st
EMAIL_PORT = [Insira este parâmetro]
EMAIL_HOST = [Insira este parâmetro]
EMAIL_HOST_USER = [Insira este parâmetro]
EMAIL_SEND_USER = [Insira este parâmetro]
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]'
FRONTEND_CUSTOM = [True/False]
* 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
EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST =
EMAIL_HOST_USER =
EMAIL_HOST_PASSWORD =
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::
@ -180,14 +167,6 @@ Copie a chave que aparecerá, edite o arquivo .env e altere o valor do parâmetr
cd /var/interlegis/sapl
* Instalar as dependências do ``bower``::
eval $(echo "sudo chown -R $USER:$USER /home/$USER/")
sudo chown -R $USER:$GROUP ~/.npm
sudo chown -R $USER:$GROUP ~/.config
./manage.py bower install
* Atualizar e/ou criar as tabelas da base de dados para refletir o modelo da versão clonada::
./manage.py migrate
@ -196,11 +175,6 @@ Copie a chave que aparecerá, edite o arquivo .env e altere o valor do parâmetr
./manage.py runserver 0.0.0.0:8001
* Compilar os arquivos de estilização::
./manage.py compilescss
./manage.py collectstatic
* Acesse o SAPL em::
http://localhost:8001/
@ -235,3 +209,76 @@ Instruções para criação do super usuário e de usuários de testes
operador_sessao
operador_painel
operador_geral
Sapl-Frontend
=============
* O Sapl foi separado em outro projeto, o SAPL Frontend que está aqui no github, no repositório do Interlegis. Veja Aqui: https://github.com/interlegis/sapl-frontend::
* Se seu objetivo é preparar o ambiente de desenvolvimento para colaborar no backend, você não precisa se preocupar com o tutorial abaixo pois na pasta https://github.com/interlegis/sapl/tree/3.1.x/sapl/static já está o código oficial de produção exportado pelo projeto do Sapl-Frontend
* Para colaborar com o Sapl-Frontend ou fazer seu próprio frontend a partir do oficial, siga os passos abaixo:
Preparação do ambiente::
----------------------
* **Instalação do NodeJs LTS 10.15.x**::
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs
* **Instalação do Yarn**::
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install yarn
* **Instalação do Vue-Cli**::
yarn global add @vue/cli
Ligando os projetos SAPL e Sapl-Frontend para implementação no Sapl-Frontend
-----------------------------------------------------------------------------
**É fundamental que o Sapl-Frontend esteja na mesma pasta que o Sapl**
* Como orientado acima, o Sapl foi clonado na pasta `/var/interlegis`. O mesmo deve ser feito com o sapl-frontend, ficando assim::
/var/interlegis/sapl
/var/interlegis/sapl-frontend
* para tal, execute::
cd /var/interlegis
git clone git://github.com/interlegis/sapl-frontend
**Você pode também criar um Fork do sapl-frontend**
* Para fazer um fork e depois clonar, siga as instruções em https://help.github.com/articles/fork-a-repo que basicamente são:
* Criar uma conta no github - é gratuíto.
* Acessar https://github.com/interlegis/sapl-frontend e clicar em **Fork**.
* Será criado um domínio pelo qual será possível **clonar, corrigir, customizar, melhorar, contribuir, etc**::
cd /var/interlegis
git clone git://github.com/[SEU NOME]/sapl-frontend
Feito isso, e você ativando a variável de ambiente FRONTEND_CUSTOM=True (vide acima criação do .env), o Sapl (backend) desativa a pasta *static* no seu ambiente de desenvolvimento e no seu ambiente de produção e passa a valer para o Sapl (backend) o que você customizar em sapl-frontend. Resumindo:
* Se você está criando um fork do sapl-frontend para ter o sapl com sua cara, ou criando funcionalidades de seu interesse:
FRONTEND_CUSTOM=True
* Se você está colaborando com a evolução oficial do sapl-frontend e enviará seu código para o repositório oficial através de um PR e, por consequência, gerará novos versão de produção a ser colocada na pasta static, então:
FRONTEND_CUSTOM=False
**Deste ponto em diante, é exigido o conhecimento que você pode adquirir em https://cli.vuejs.org/guide/ e em https://vuejs.org/v2/guide/ para colaborar com sapl-frontend**
**OBS: após a separação do sapl para o sapl-frontend, o conteúdo da pasta static é compilado e minificado. É gerado pelo build do sapl-frontend e não deve-se tentar customizar ou criar elementos manipulando diretamente informações na pasta static.**

6
release.sh

@ -14,14 +14,16 @@ function bump_version {
sed -e s/$VERSION/$NEXT_VERSION/g setup.py > tmp2
mv tmp2 setup.py
sed -e s/$VERSION/$NEXT_VERSION/g sapl/templates/base.html > tmp3
mv tmp3 sapl/templates/base.html
sed -e s/$VERSION/$NEXT_VERSION/g sapl/settings.py > tmp4
mv tmp4 sapl/settings.py
}
function commit_and_push {
echo "committing..."
git add docker-compose.yml setup.py sapl/templates/base.html
git add docker-compose.yml setup.py sapl/settings.py sapl/templates/base.html
git commit -m "Release: $NEXT_VERSION"
git tag $NEXT_VERSION

62
requirements/requirements.txt

@ -1,36 +1,36 @@
dj-database-url==0.4.1
django-haystack==2.6.0
django>=1.10,<1.11
git+git://github.com/rubgombar1/django-admin-bootstrapped.git
django-bootstrap3==7.0.1
django-bower==5.2.0
django>=1.11.19,<2.0
django-haystack==2.8.1
django-filter==2.0.0
djangorestframework==3.9.0
dj-database-url==0.5.0
django-braces==1.9.0
django-compressor==2.0
django-crispy-forms==1.6.1
django-extensions==1.9.8
django-extra-views==0.11.0
django-filter==0.15.3
django-floppyforms==1.6.2
django-model-utils==3.1.1
django-sass-processor==0.5.8
djangorestframework==3.4.0
git+git://github.com/jasperlittle/django-rest-framework-docs
easy-thumbnails==2.5
django-crispy-forms==1.7.2
django-floppyforms==1.7.0
django-extra-views==0.12.0
django-model-utils==3.1.2
django-reversion==3.0.2
django-reversion-compare==0.8.6
django-speedinfo==1.4.0
django-extensions==2.1.4
django-image-cropping==1.2
git+git://github.com/interlegis/trml2pdf.git
libsass==0.11.1
psycopg2-binary==2.7.4
python-decouple==3.0
pytz==2016.4
pyyaml==3.11
rtyaml==0.0.3
textract==1.5.0
django-webpack-loader==0.6.0
drf-yasg==1.13.0
easy-thumbnails==2.5
python-decouple==3.1
psycopg2-binary==2.7.6.1
pyyaml==4.2b1
pytz==2018.9
rtyaml==0.0.5
python-magic==0.4.15
unipath==1.1
WeasyPrint==44
gunicorn==19.9.0
textract==1.5.0
pysolr==3.6.0
python-magic==0.4.12
gunicorn==19.6.0
django-reversion==2.0.8
WeasyPrint==0.42
whoosh==2.7.4
django-speedinfo==1.3.5
django-reversion-compare==0.8.4
pyoai==2.5.0
git+git://github.com/interlegis/trml2pdf.git
git+git://github.com/interlegis/django-admin-bootstrapped

1
sapl/.env_test

@ -5,4 +5,5 @@ EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST = ''
EMAIL_HOST_USER = ''
EMAIL_SEND_USER = ''
EMAIL_HOST_PASSWORD = ''

670
sapl/api/deprecated.py

@ -0,0 +1,670 @@
import logging
import logging
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.db.models import Q
from django.forms.fields import CharField, MultiValueField
from django.forms.widgets import MultiWidget, TextInput
from django.http import Http404
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django_filters.filters import CharFilter, ModelChoiceFilter, DateFilter
from django_filters.rest_framework.backends import DjangoFilterBackend
from django_filters.rest_framework.filterset import FilterSet
from rest_framework import serializers
from rest_framework import serializers
from rest_framework.generics import ListAPIView
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.permissions import (IsAuthenticated,
IsAuthenticatedOrReadOnly, AllowAny)
from rest_framework.viewsets import GenericViewSet
from sapl.api.serializers import ModelChoiceSerializer, AutorSerializer,\
ChoiceSerializer
from sapl.base.models import TipoAutor, Autor, CasaLegislativa
from sapl.materia.models import MateriaLegislativa
from sapl.parlamentares.models import Legislatura
from sapl.sessao.models import SessaoPlenaria, OrdemDia
from sapl.utils import SaplGenericRelation
from sapl.utils import generic_relations_for_model
class SaplGenericRelationSearchFilterSet(FilterSet):
q = CharFilter(method='filter_q')
def filter_q(self, queryset, name, value):
query = value.split(' ')
if query:
q = Q()
for qtext in query:
if not qtext:
continue
q_fs = Q(nome__icontains=qtext)
order_by = []
for gr in generic_relations_for_model(self._meta.model):
sgr = gr[1]
for item in sgr:
if item.related_model != self._meta.model:
continue
flag_order_by = True
for field in item.fields_search:
if flag_order_by:
flag_order_by = False
order_by.append('%s__%s' % (
item.related_query_name(),
field[0])
)
# 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
if q:
queryset = queryset.filter(q).order_by(*order_by)
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(CharFilter):
field_class = SearchForFieldField
class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet):
q = CharFilter(method='filter_q')
tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all())
class Meta:
model = Autor
fields = ['q',
'tipo',
'nome', ]
def filter_q(self, queryset, name, value):
return super().filter_q(
queryset, name, value).distinct('nome').order_by('nome')
class AutorSearchForFieldFilterSet(AutorChoiceFilterSet):
q = SearchForFieldFilter(method='filter_q')
class Meta(AutorChoiceFilterSet.Meta):
pass
def filter_q(self, queryset, name, 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')
class AutoresPossiveisFilterSet(FilterSet):
logger = logging.getLogger(__name__)
data_relativa = DateFilter(method='filter_data_relativa')
tipo = CharFilter(method='filter_tipo')
class Meta:
model = Autor
fields = ['data_relativa', 'tipo', ]
def filter_data_relativa(self, queryset, name, value):
return queryset
def filter_tipo(self, queryset, name, value):
try:
self.logger.debug(
"Tentando obter TipoAutor correspondente à pk {}.".format(value))
tipo = TipoAutor.objects.get(pk=value)
except:
self.logger.error("TipoAutor(pk={}) inexistente.".format(value))
raise serializers.ValidationError(_('Tipo de Autor inexistente.'))
qs = queryset.filter(tipo=tipo)
return qs
@property
def qs(self):
qs = super().qs
data_relativa = self.form.cleaned_data['data_relativa'] \
if 'data_relativa' in self.form.cleaned_data else None
tipo = self.form.cleaned_data['tipo'] \
if 'tipo' in self.form.cleaned_data else None
if not tipo:
return qs
tipo = TipoAutor.objects.get(pk=tipo)
if not tipo.content_type:
return qs
filter_for_model = 'filter_%s' % tipo.content_type.model
if not hasattr(self, filter_for_model):
return qs
if not data_relativa:
data_relativa = timezone.now()
return getattr(self, filter_for_model)(qs, data_relativa).distinct()
def filter_parlamentar(self, queryset, data_relativa):
# não leva em conta afastamentos
legislatura_relativa = Legislatura.objects.filter(
data_inicio__lte=data_relativa,
data_fim__gte=data_relativa).first()
q = Q(
parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa,
parlamentar_set__mandato__data_fim_mandato__isnull=True) | Q(
parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa,
parlamentar_set__mandato__data_fim_mandato__gte=data_relativa)
if legislatura_relativa.atual():
q = q & Q(parlamentar_set__ativo=True)
return queryset.filter(q)
def filter_comissao(self, queryset, data_relativa):
return queryset.filter(
Q(comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__gte=data_relativa) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__gte=data_relativa),
comissao_set__data_criacao__lte=data_relativa)
def filter_frente(self, queryset, data_relativa):
return queryset.filter(
Q(frente_set__data_extincao__isnull=True) |
Q(frente_set__data_extincao__gte=data_relativa),
frente_set__data_criacao__lte=data_relativa)
def filter_bancada(self, queryset, data_relativa):
return queryset.filter(
Q(bancada_set__data_extincao__isnull=True) |
Q(bancada_set__data_extincao__gte=data_relativa),
bancada_set__data_criacao__lte=data_relativa)
def filter_bloco(self, queryset, data_relativa):
return queryset.filter(
Q(bloco_set__data_extincao__isnull=True) |
Q(bloco_set__data_extincao__gte=data_relativa),
bloco_set__data_criacao__lte=data_relativa)
def filter_orgao(self, queryset, data_relativa):
# na implementação, não havia regras a implementar para orgao
return queryset
class AutorChoiceSerializer(ModelChoiceSerializer):
def get_text(self, obj):
return obj.nome
class Meta:
model = Autor
fields = ['id', 'nome']
class MateriaLegislativaOldSerializer(serializers.ModelSerializer):
class Meta:
model = MateriaLegislativa
fields = '__all__'
class SessaoPlenariaOldSerializer(serializers.ModelSerializer):
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 = (
'codReuniao',
'codReuniaoPrincipal',
'txtTituloReuniao',
'txtSiglaOrgao',
'txtApelido',
'txtNomeOrgao',
'codEstadoReuniao',
'txtTipoReuniao',
'txtObjeto',
'txtLocal',
'bolReuniaoConjunta',
'bolHabilitarEventoInterativo',
'idYoutube',
'codEstadoTransmissaoYoutube',
'datReuniaoString'
)
def __init__(self, *args, **kwargs):
super(SessaoPlenariaOldSerializer, self).__init__(args, kwargs)
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
class ModelChoiceView(ListAPIView):
"""
Deprecated
TODO Migrar para customização na api automática
"""
# FIXME aplicar permissão correta de usuário
permission_classes = (IsAuthenticated,)
serializer_class = ModelChoiceSerializer
def get(self, request, *args, **kwargs):
self.model = ContentType.objects.get_for_id(
self.kwargs['content_type']).model_class()
pagination = request.GET.get('pagination', '')
if pagination == 'False':
self.pagination_class = None
return ListAPIView.get(self, request, *args, **kwargs)
def get_queryset(self):
return self.model.objects.all()
class AutorListView(ListAPIView):
"""
Deprecated
TODO Migrar para customização na api automática
Listagem de Autores com filtro para autores cadastrados
e/ou possíveis autores.
- tr - tipo do resultado
Prepera Lista de Autores para 2 cenários distintos
- default = 1
= 1 -> para (value, text) usados geralmente
em combobox, radiobox, checkbox, etc com pesquisa básica
de Autores feita pelo django-filter
-> processo usado nas pesquisas, o mais usado.
= 3 -> Devolve instancias da classe Autor filtradas pelo
django-filter
- tipo - chave primária do Tipo de Autor a ser filtrado
- q - busca textual no nome do Autor ou em fields_search
declarados no field SaplGenericRelation das GenericFks
A busca textual acontece via django-filter com a
variável `tr` igual 1 ou 3. Em caso contrário,
o django-filter é desativado e a busca é feita
no model do ContentType associado ao tipo.
- q_0 / q_1 - q_0 é opcional e quando usado, 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
"""
logger = logging.getLogger(__name__)
TR_AUTOR_CHOICE_SERIALIZER = 1
TR_AUTOR_SERIALIZER = 3
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Autor.objects.all()
model = Autor
filter_class = AutorChoiceFilterSet
filter_backends = (DjangoFilterBackend, )
serializer_class = AutorChoiceSerializer
@property
def tr(self):
username = self.request.user.username
try:
tr = int(self.request.GET.get
('tr', AutorListView.TR_AUTOR_CHOICE_SERIALIZER))
if tr not in (AutorListView.TR_AUTOR_CHOICE_SERIALIZER,
AutorListView.TR_AUTOR_SERIALIZER):
return AutorListView.TR_AUTOR_CHOICE_SERIALIZER
except Exception as e:
self.logger.error('user=' + username + '. ' + str(e))
return AutorListView.TR_AUTOR_CHOICE_SERIALIZER
return tr
def get(self, request, *args, **kwargs):
if self.tr == AutorListView.TR_AUTOR_SERIALIZER:
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)
class AutoresProvaveisListView(ListAPIView):
"""
Deprecated
TODO Migrar para customização na api automática
"""
logger = logging.getLogger(__name__)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Autor.objects.all()
model = Autor
filter_class = None
filter_backends = []
serializer_class = ChoiceSerializer
def get_queryset(self):
params = {'content_type__isnull': False}
username = self.request.user.username
tipo = ''
try:
tipo = int(self.request.GET.get('tipo', ''))
if tipo:
params['id'] = tipo
except Exception as e:
self.logger.error('user= ' + username + '. ' + str(e))
pass
tipos = TipoAutor.objects.filter(**params)
if not tipos.exists() and tipo:
raise Http404()
r = []
for tipo in tipos:
q = self.request.GET.get('q', '').strip()
model_class = tipo.content_type.model_class()
fields = list(filter(
lambda field: isinstance(field, SaplGenericRelation) and
field.related_model == Autor,
model_class._meta.get_fields(include_hidden=True)))
"""
fields - é um array de SaplGenericRelation que deve possuir o
atributo fields_search. Verifique na documentação da classe
a estrutura de fields_search.
"""
assert len(fields) >= 1, (_(
'Não foi encontrado em %(model)s um atributo do tipo '
'SaplGenericRelation que use o model %(model_autor)s') % {
'model': model_class._meta.verbose_name,
'model_autor': Autor._meta.verbose_name})
qs = model_class.objects.all()
q_filter = Q()
if q:
for item in fields:
if item.related_model != Autor:
continue
q_fs = Q()
for field in item.fields_search:
q_fs = q_fs | Q(**{'%s%s' % (
field[0],
field[1]): q})
q_filter = q_filter & q_fs
qs = qs.filter(q_filter).distinct(
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.values_list(
'id', fields[0].fields_search[0][0])
r += list(qs)
if tipos.count() > 1:
r.sort(key=lambda x: x[1].upper())
return r
class AutoresPossiveisListView(ListAPIView):
"""
Deprecated
TODO Migrar para customização na api automática
"""
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Autor.objects.all()
model = Autor
pagination_class = None
filter_class = AutoresPossiveisFilterSet
serializer_class = AutorChoiceSerializer
class MateriaLegislativaViewSet(ListModelMixin,
RetrieveModelMixin,
GenericViewSet):
"""
Deprecated
TODO Migrar para customização na api automática
"""
permission_classes = (IsAuthenticated,)
serializer_class = MateriaLegislativaOldSerializer
queryset = MateriaLegislativa.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields = ('numero', 'ano', 'tipo', )
class SessaoPlenariaViewSet(ListModelMixin,
RetrieveModelMixin,
GenericViewSet):
"""
Deprecated
TODO Migrar para customização na api automática
"""
permission_classes = (AllowAny,)
serializer_class = SessaoPlenariaOldSerializer
queryset = SessaoPlenaria.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields = ('data_inicio', 'data_fim', 'interativa')

268
sapl/api/forms.py

@ -1,233 +1,65 @@
import logging
from django.db.models.fields.files import FileField
from django.template.defaultfilters import capfirst
import django_filters
from django_filters.filters import CharFilter, NumberFilter
from django_filters.rest_framework.filterset import FilterSet
from django_filters.utils import resolve_field
from sapl.sessao.models import SessaoPlenaria
from django.db.models import Q
from django.forms.fields import CharField, MultiValueField
from django.forms.widgets import MultiWidget, TextInput
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django_filters.filters import DateFilter, MethodFilter, ModelChoiceFilter
from rest_framework import serializers
from rest_framework.compat import django_filters
from rest_framework.filters import FilterSet
from sapl.base.models import Autor, TipoAutor
from sapl.parlamentares.models import Legislatura
from sapl.utils import generic_relations_for_model
class SaplFilterSetMixin(FilterSet):
class SaplGenericRelationSearchFilterSet(FilterSet):
q = MethodFilter()
def filter_q(self, queryset, value):
query = value.split(' ')
if query:
q = Q()
for qtext in query:
if not qtext:
continue
q_fs = Q(nome__icontains=qtext)
order_by = []
for gr in generic_relations_for_model(self._meta.model):
sgr = gr[1]
for item in sgr:
if item.related_model != self._meta.model:
continue
flag_order_by = True
for field in item.fields_search:
if flag_order_by:
flag_order_by = False
order_by.append('%s__%s' % (
item.related_query_name(),
field[0])
)
# 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
if q:
queryset = queryset.filter(q).order_by(*order_by)
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())
o = CharFilter(method='filter_o')
class Meta:
model = Autor
fields = ['q',
'tipo',
'nome', ]
def filter_q(self, queryset, value):
return SaplGenericRelationSearchFilterSet.filter_q(
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')
class AutoresPossiveisFilterSet(FilterSet):
logger = logging.getLogger(__name__)
data_relativa = DateFilter(method='filter_data_relativa')
tipo = MethodFilter()
class Meta:
model = Autor
fields = ['data_relativa', 'tipo', ]
def filter_data_relativa(self, queryset, name, value):
return queryset
def filter_tipo(self, queryset, value):
fields = '__all__'
filter_overrides = {
FileField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
'lookup_expr': 'exact',
},
},
}
def filter_o(self, queryset, name, value):
try:
self.logger.debug("Tentando obter TipoAutor correspondente à pk {}.".format(value))
tipo = TipoAutor.objects.get(pk=value)
return queryset.order_by(
*map(str.strip, value.split(',')))
except:
self.logger.error("TipoAutor(pk={}) inexistente.".format(value))
raise serializers.ValidationError(_('Tipo de Autor inexistente.'))
qs = queryset.filter(tipo=tipo)
return qs
return queryset
@property
def qs(self):
qs = super().qs
@classmethod
def filter_for_field(cls, f, name, lookup_expr='exact'):
# Redefine método estático para ignorar filtro para
# fields que não possuam lookup_expr informado
f, lookup_type = resolve_field(f, lookup_expr)
default = {
'field_name': name,
'label': capfirst(f.verbose_name),
'lookup_expr': lookup_expr
}
filter_class, params = cls.filter_for_lookup(
f, lookup_type)
default.update(params)
if filter_class is not None:
return filter_class(**default)
return None
data_relativa = self.form.cleaned_data['data_relativa'] \
if 'data_relativa' in self.form.cleaned_data else None
tipo = self.form.cleaned_data['tipo'] \
if 'tipo' in self.form.cleaned_data else None
class SessaoPlenariaFilterSet(SaplFilterSetMixin):
year = NumberFilter(method='filter_year')
month = NumberFilter(method='filter_month')
if not tipo and not data_relativa:
return qs
class Meta(SaplFilterSetMixin.Meta):
model = SessaoPlenaria
if tipo:
# não precisa de try except, já foi validado em filter_tipo
tipo = TipoAutor.objects.get(pk=tipo)
if not tipo.content_type:
def filter_year(self, queryset, name, value):
qs = queryset.filter(data_inicio__year=value)
return qs
filter_for_model = 'filter_%s' % tipo.content_type.model
if not hasattr(self, filter_for_model):
def filter_month(self, queryset, name, value):
qs = queryset.filter(data_inicio__month=value)
return qs
if not data_relativa:
data_relativa = timezone.now()
return getattr(self, filter_for_model)(qs, data_relativa).distinct()
def filter_parlamentar(self, queryset, data_relativa):
# não leva em conta afastamentos
legislatura_relativa = Legislatura.objects.filter(
data_inicio__lte=data_relativa,
data_fim__gte=data_relativa).first()
q = Q(
parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa,
parlamentar_set__mandato__data_fim_mandato__isnull=True) | Q(
parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa,
parlamentar_set__mandato__data_fim_mandato__gte=data_relativa)
if legislatura_relativa.atual():
q = q & Q(parlamentar_set__ativo=True)
return queryset.filter(q)
def filter_comissao(self, queryset, data_relativa):
return queryset.filter(
Q(comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__gte=data_relativa) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__gte=data_relativa),
comissao_set__data_criacao__lte=data_relativa)
def filter_frente(self, queryset, data_relativa):
return queryset.filter(
Q(frente_set__data_extincao__isnull=True) |
Q(frente_set__data_extincao__gte=data_relativa),
frente_set__data_criacao__lte=data_relativa)
def filter_bancada(self, queryset, data_relativa):
return queryset.filter(
Q(bancada_set__data_extincao__isnull=True) |
Q(bancada_set__data_extincao__gte=data_relativa),
bancada_set__data_criacao__lte=data_relativa)
def filter_bloco(self, queryset, data_relativa):
return queryset.filter(
Q(bloco_set__data_extincao__isnull=True) |
Q(bloco_set__data_extincao__gte=data_relativa),
bloco_set__data_criacao__lte=data_relativa)
def filter_orgao(self, queryset, data_relativa):
# na implementação, não havia regras a implementar para orgao
return queryset

39
sapl/api/permissions.py

@ -1,7 +1,8 @@
from rest_framework.permissions import DjangoModelPermissions
from sapl.rules.map_rules import rules_patterns_public
class DjangoModelPermissions(DjangoModelPermissions):
class SaplModelPermissions(DjangoModelPermissions):
perms_map = {
'GET': ['%(app_label)s.list_%(model_name)s',
@ -10,9 +11,43 @@ class DjangoModelPermissions(DjangoModelPermissions):
'%(app_label)s.detail_%(model_name)s'],
'HEAD': ['%(app_label)s.list_%(model_name)s',
'%(app_label)s.detail_%(model_name)s'],
'POST': ['%(app_label)s.list_%(model_name)s'],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
def has_permission(self, request, view):
if getattr(view, '_ignore_model_permissions', False):
return True
if hasattr(view, 'get_queryset'):
queryset = view.get_queryset()
else:
queryset = getattr(view, 'queryset', None)
assert queryset is not None, (
'Cannot apply DjangoModelPermissions on a view that '
'does not set `.queryset` or have a `.get_queryset()` method.'
)
perms = self.get_required_permissions(request.method, queryset.model)
key = '{}:{}'.format(
queryset.model._meta.app_label,
queryset.model._meta.model_name)
if key in rules_patterns_public:
perms = set(perms)
perms_publicas = rules_patterns_public[key]
private_perms = perms - perms_publicas
if not private_perms:
return True
return (
request.user and
(request.user.is_authenticated() or not self.authenticated_users_only) and
request.user.has_perms(perms)
)

148
sapl/api/serializers.py

@ -1,8 +1,13 @@
from django.conf import settings
from rest_framework import serializers
from rest_framework.relations import StringRelatedField
from sapl.base.models import Autor, CasaLegislativa
from sapl.materia.models import MateriaLegislativa
from sapl.sessao.models import OrdemDia, SessaoPlenaria
class IntRelatedField(StringRelatedField):
def to_representation(self, value):
return int(value)
class ChoiceSerializer(serializers.Serializer):
@ -31,17 +36,10 @@ class ModelChoiceObjectRelatedField(serializers.RelatedField):
return ModelChoiceSerializer(value).data
class AutorChoiceSerializer(ModelChoiceSerializer):
def get_text(self, obj):
return obj.nome
class Meta:
model = Autor
fields = ['id', 'nome']
class AutorSerializer(serializers.ModelSerializer):
# AutorSerializer sendo utilizado pelo gerador automático da api devidos aos
# critérios anotados em views.py
autor_related = ModelChoiceObjectRelatedField(read_only=True)
class Meta:
@ -49,126 +47,12 @@ class AutorSerializer(serializers.ModelSerializer):
fields = '__all__'
class MateriaLegislativaSerializer(serializers.ModelSerializer):
class Meta:
model = MateriaLegislativa
fields = '__all__'
class CasaLegislativaSerializer(serializers.ModelSerializer):
version = serializers.SerializerMethodField()
class SessaoPlenariaSerializer(serializers.ModelSerializer):
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
def get_version(self, obj):
return settings.SAPL_VERSION
class Meta:
model = SessaoPlenaria
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)
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
model = CasaLegislativa
fields = '__all__'

58
sapl/api/urls.py

@ -1,23 +1,56 @@
from django.conf import settings
from django.conf.urls import include, url
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions
from rest_framework.routers import DefaultRouter
from sapl.api.views import (AutoresPossiveisListView, AutoresProvaveisListView,
AutorListView, MateriaLegislativaViewSet,
ModelChoiceView, SessaoPlenariaViewSet)
from sapl.api.deprecated import MateriaLegislativaViewSet, SessaoPlenariaViewSet,\
AutoresProvaveisListView, AutoresPossiveisListView, AutorListView,\
ModelChoiceView
from sapl.api.views import SaplSetViews
from .apps import AppConfig
app_name = AppConfig.name
router = DefaultRouter()
router.register(r'materia', MateriaLegislativaViewSet)
router.register(r'materia$', MateriaLegislativaViewSet)
router.register(r'sessao-plenaria', SessaoPlenariaViewSet)
for app, built_sets in SaplSetViews.items():
for view_prefix, viewset in built_sets.items():
router.register(app + '/' + view_prefix, viewset)
urlpatterns_router = router.urls
urlpatterns_api = [
schema_view = get_schema_view(
openapi.Info(
title="Sapl API - docs",
default_version='v1',
description="Sapl API - Docs - Configuração Básica",
),
url=settings.SITE_URL,
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns_api_doc = [
url(r'^docs/swagger(?P<format>\.json|\.yaml)$',
schema_view.without_ui(cache_timeout=0), name='schema-json'),
url(r'^docs/swagger/$',
schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
url(r'^docs/redoc/$',
schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
# TODO: refatorar para customização da api automática
deprecated_urlpatterns_api = [
url(r'^autor/provaveis',
AutoresProvaveisListView.as_view(), name='autores_provaveis_list'),
url(r'^autor/possiveis',
@ -28,13 +61,16 @@ urlpatterns_api = [
url(r'^model/(?P<content_type>\d+)/(?P<pk>\d*)$',
ModelChoiceView.as_view(), name='model_list'),
]
if settings.DEBUG:
urlpatterns_api += [
url(r'^docs', include('rest_framework_docs.urls')), ]
]
urlpatterns = [
url(r'^api/', include(urlpatterns_api)),
url(r'^api/', include(urlpatterns_router))
url(r'^api/', include(deprecated_urlpatterns_api)),
url(r'^api/', include(urlpatterns_api_doc)),
url(r'^api/', include(urlpatterns_router)),
# implementar caminho para autenticação
# https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/
# url(r'^api/auth/', include('rest_framework.urls', namespace='rest_framework')),
]

607
sapl/api/views.py

@ -1,278 +1,437 @@
import logging
from django import apps
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.http import Http404
from django.db.models.fields.files import FileField
from django.utils.decorators import classonlymethod
from django.utils.text import capfirst
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 (AllowAny, IsAuthenticated,
IsAuthenticatedOrReadOnly)
from rest_framework.viewsets import GenericViewSet
import django_filters
from django_filters.filters import CharFilter
from django_filters.rest_framework.backends import DjangoFilterBackend
from django_filters.rest_framework.filterset import FilterSet
from django_filters.utils import resolve_field
from rest_framework import serializers as rest_serializers
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from sapl.api.forms import (AutorChoiceFilterSet, AutoresPossiveisFilterSet,
AutorSearchForFieldFilterSet)
from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer,
ChoiceSerializer,
MateriaLegislativaSerializer,
ModelChoiceSerializer,
SessaoPlenariaSerializer)
from sapl.base.models import Autor, TipoAutor
from sapl.materia.models import MateriaLegislativa
from sapl.sessao.models import SessaoPlenaria
from sapl.utils import SaplGenericRelation
from sapl.api.forms import SaplFilterSetMixin
from sapl.api.permissions import SaplModelPermissions
from sapl.api.serializers import ChoiceSerializer
from sapl.base.models import Autor, AppConfig, DOC_ADM_OSTENSIVO
from sapl.materia.models import Proposicao
from sapl.parlamentares.models import Parlamentar
from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria
class ModelChoiceView(ListAPIView):
class BusinessRulesNotImplementedMixin:
def create(self, request, *args, **kwargs):
raise Exception(_("POST Create não implementado"))
# FIXME aplicar permissão correta de usuário
permission_classes = (IsAuthenticated,)
serializer_class = ModelChoiceSerializer
def put(self, request, *args, **kwargs):
raise Exception(_("PUT Update não implementado"))
def get(self, request, *args, **kwargs):
self.model = ContentType.objects.get_for_id(
self.kwargs['content_type']).model_class()
def patch(self, request, *args, **kwargs):
raise Exception(_("PATCH Partial Update não implementado"))
pagination = request.GET.get('pagination', '')
def delete(self, request, *args, **kwargs):
raise Exception(_("DELETE Delete não implementado"))
if pagination == 'False':
self.pagination_class = None
return ListAPIView.get(self, request, *args, **kwargs)
class SaplApiViewSetConstrutor(ModelViewSet):
def get_queryset(self):
return self.model.objects.all()
filter_backends = (DjangoFilterBackend,)
@classonlymethod
def build_class(cls):
import inspect
from sapl.api import serializers
# Carrega todas as classes de sapl.api.serializers que possuam
# "Serializer" como Sufixo.
serializers_classes = inspect.getmembers(serializers)
serializers_classes = {i[0]: i[1] for i in filter(
lambda x: x[0].endswith('Serializer'),
serializers_classes
)}
# Carrega todas as classes de sapl.api.forms que possuam
# "FilterSet" como Sufixo.
from sapl.api import forms
filters_classes = inspect.getmembers(forms)
filters_classes = {i[0]: i[1] for i in filter(
lambda x: x[0].endswith('FilterSet'),
filters_classes
)}
built_sets = {}
def build(_model):
object_name = _model._meta.object_name
# Caso Exista, pega a classe sapl.api.serializers.{model}Serializer
serializer_name = '{model}Serializer'.format(model=object_name)
_serializer_class = serializers_classes.get(serializer_name, None)
# Caso Exista, pega a classe sapl.api.forms.{model}FilterSet
filter_name = '{model}FilterSet'.format(model=object_name)
_filter_class = filters_classes.get(filter_name, None)
def create_class():
# Define uma classe padrão para serializer caso não tenha sido
# criada a classe sapl.api.serializers.{model}Serializer
class SaplSerializer(rest_serializers.ModelSerializer):
class Meta:
model = _model
fields = '__all__'
# Define uma classe padrão para filtro caso não tenha sido
# criada a classe sapl.api.forms.{model}FilterSet
class SaplFilterSet(SaplFilterSetMixin):
class Meta(SaplFilterSetMixin.Meta):
model = _model
# Define uma classe padrão ModelViewSet de DRF
class ModelSaplViewSet(cls):
queryset = _model.objects.all()
# Utiliza o filtro customizado pela classe
# sapl.api.forms.{model}FilterSet
# ou utiliza o trivial SaplFilterSet definido acima
filter_class = _filter_class \
if _filter_class else SaplFilterSet
# Utiliza o serializer customizado pela classe
# sapl.api.serializers.{model}Serializer
# ou utiliza o trivial SaplSerializer definido acima
serializer_class = _serializer_class \
if _serializer_class else SaplSerializer
return ModelSaplViewSet
viewset = create_class()
viewset.__name__ = '%sModelSaplViewSet' % _model.__name__
return viewset
apps_sapl = [apps.apps.get_app_config(
n[5:]) for n in settings.SAPL_APPS]
for app in apps_sapl:
built_sets[app.label] = {}
for model in app.get_models():
built_sets[app.label][model._meta.model_name] = build(model)
return built_sets
"""
1. Constroi uma rest_framework.viewsets.ModelViewSet para
todos os models de todas as apps do sapl
2. Define DjangoFilterBackend como ferramenta de filtro dos campos
3. Define Serializer como a seguir:
3.1 - Define um Serializer genérico para cada módel
3.2 - Recupera Serializer customizado em sapl.api.serializers
3.3 - Para todo model é opcional a existência de
sapl.api.serializers.{model}Serializer.
Caso não seja definido um Serializer customizado, utiliza-se o trivial
4. Define um FilterSet como a seguir:
4.1 - Define um FilterSet genérico para cada módel
4.2 - Recupera FilterSet customizado em sapl.api.forms
4.3 - Para todo model é opcional a existência de
sapl.api.forms.{model}FilterSet.
Caso não seja definido um FilterSet customizado, utiliza-se o trivial
4.4 - todos os campos que aceitam lookup 'exact'
podem ser filtrados por default
5. SaplApiViewSetConstrutor não cria padrões e/ou exige conhecimento alem dos
exigidos pela DRF.
6. As rotas são criadas seguindo nome da app e nome do model
http://localhost:9000/api/{applabel}/{model_name}/
e seguem as variações definidas em:
https://www.django-rest-framework.org/api-guide/routers/#defaultrouter
7. Todas as viewsets construídas por SaplApiViewSetConstrutor e suas rotas
(paginate list, detail, edit, create, delete)
bem como testes em ambiente de desenvolvimento podem ser conferidas em:
http://localhost:9000/api/
desde que settings.DEBUG=True
**SaplSetViews** é um dict de dicts de models conforme:
{
...
'audiencia': {
'tipoaudienciapublica': TipoAudienciaPublicaViewSet,
'audienciapublica': AudienciaPublicaViewSet,
'anexoaudienciapublica': AnexoAudienciaPublicaViewSet
...
},
...
'base': {
'casalegislativa': CasaLegislativaViewSet,
'appconfig': AppConfigViewSet,
...
}
...
}
"""
SaplSetViews = SaplApiViewSetConstrutor.build_class()
# Toda Classe construida acima, pode ser redefinida e aplicado quaisquer
# das possibilidades para uma classe normal criada a partir de
# rest_framework.viewsets.ModelViewSet conforme exemplo para a classe autor
class AutorListView(ListAPIView):
# Customização para AutorViewSet com implementação de actions específicas
class _AutorViewSet(SaplSetViews['base']['autor']):
"""
Listagem de Autores com filtro para autores cadastrados
e/ou possíveis autores.
- tr - tipo do resultado
Prepera Lista de Autores para 3 cenários distintos
- default = 1
= 1 -> para (value, text) usados geralmente
em combobox, radiobox, checkbox, etc com pesquisa básica
de Autores feita pelo django-filter
-> processo usado nas pesquisas, o mais usado.
= 3 -> Devolve instancias da classe Autor filtradas pelo
django-filter
- tipo - chave primária do Tipo de Autor a ser filtrado
- q - busca textual no nome do Autor ou em fields_search
declarados no field SaplGenericRelation das GenericFks
A busca textual acontece via django-filter com a
variável `tr` igual 1 ou 3. Em caso contrário,
o django-filter é desativado e a busca é feita
no model do ContentType associado ao tipo.
- q_0 / q_1 - q_0 é opcional e quando usado, 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
Neste exemplo de customização do que foi criado em
SaplApiViewSetConstrutor além do ofertado por
rest_framework.viewsets.ModelViewSet, dentre outras customizações
possíveis, foi adicionado as rotas referentes aos relacionamentos genéricos
* padrão de ModelViewSet
/api/base/autor/ POST - create
/api/base/autor/ GET - list
/api/base/autor/{pk}/ GET - detail
/api/base/autor/{pk}/ PUT - update
/api/base/autor/{pk}/ PATCH - partial_update
/api/base/autor/{pk}/ DELETE - destroy
* rotas desta classe local:
/api/base/autor/parlamentar
devolve apenas autores que são parlamentares
/api/base/autor/comissao
devolve apenas autores que são comissões
/api/base/autor/bloco
devolve apenas autores que são blocos parlamentares
/api/base/autor/bancada
devolve apenas autores que são bancadas parlamentares
/api/base/autor/frente
devolve apenas autores que são Frene parlamentares
/api/base/autor/orgao
devolve apenas autores que são Órgãos
"""
logger = logging.getLogger(__name__)
TR_AUTOR_CHOICE_SERIALIZER = 1
TR_AUTOR_SERIALIZER = 3
def list_for_content_type(self, content_type):
qs = self.get_queryset()
qs = qs.filter(content_type=content_type)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Autor.objects.all()
model = Autor
page = self.paginate_queryset(qs)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
filter_class = AutorChoiceFilterSet
filter_backends = (DjangoFilterBackend, )
serializer_class = AutorChoiceSerializer
serializer = self.get_serializer(page, many=True)
return Response(serializer.data)
@property
def tr(self):
username = self.request.user.username
try:
tr = int(self.request.GET.get
('tr', AutorListView.TR_AUTOR_CHOICE_SERIALIZER))
@classonlymethod
def build_class_with_actions(cls):
if tr not in (AutorListView.TR_AUTOR_CHOICE_SERIALIZER,
AutorListView.TR_AUTOR_SERIALIZER):
return AutorListView.TR_AUTOR_CHOICE_SERIALIZER
except Exception as e:
self.logger.error('user=' + username + '. ' + str(e))
return AutorListView.TR_AUTOR_CHOICE_SERIALIZER
return tr
models_with_gr_for_autor = models_with_gr_for_model(Autor)
def get(self, request, *args, **kwargs):
if self.tr == AutorListView.TR_AUTOR_SERIALIZER:
self.serializer_class = AutorSerializer
self.permission_classes = (IsAuthenticated,)
for _model in models_with_gr_for_autor:
if self.filter_class and 'q_0' in request.GET:
self.filter_class = AutorSearchForFieldFilterSet
@action(detail=False, name=_model._meta.model_name)
def actionclass(self, request, *args, **kwargs):
model = getattr(self, self.action)._AutorViewSet__model
return ListAPIView.get(self, request, *args, **kwargs)
content_type = ContentType.objects.get_for_model(model)
return self.list_for_content_type(content_type)
func = actionclass
func.mapping['get'] = func.kwargs['name']
func.url_name = func.kwargs['name']
func.url_path = func.kwargs['name']
func.__model = _model
class AutoresProvaveisListView(ListAPIView):
logger = logging.getLogger(__name__)
setattr(cls, _model._meta.model_name, func)
return cls
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Autor.objects.all()
model = Autor
filter_class = None
filter_backends = []
serializer_class = ChoiceSerializer
class _ParlamentarViewSet(SaplSetViews['parlamentares']['parlamentar']):
@action(detail=True)
def proposicoes(self, request, *args, **kwargs):
"""
Lista de proposições públicas de parlamentar específico
:param int id: - Identificador do parlamentar que se quer recuperar as proposições
:return: uma lista de proposições
"""
# /api/parlamentares/parlamentar/{id}/proposicoes/
# recupera proposições enviadas e incorporadas do parlamentar
# deve coincidir com
# /parlamentar/{pk}/proposicao
content_type = ContentType.objects.get_for_model(Parlamentar)
qs = Proposicao.objects.filter(
data_envio__isnull=False,
data_recebimento__isnull=False,
cancelado=False,
autor__object_id=kwargs['pk'],
autor__content_type=content_type
)
page = self.paginate_queryset(qs)
if page is not None:
serializer = SaplSetViews[
'materia']['proposicao'].serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(page, many=True)
return Response(serializer.data)
class _ProposicaoViewSet(SaplSetViews['materia']['proposicao']):
"""
list:
Retorna lista de Proposições
* Permissões:
* Usuário Dono:
* Pode listar todas suas Proposições
* Usuário Conectado ou Anônimo:
* Pode listar todas as Proposições incorporadas
retrieve:
Retorna uma proposição passada pelo 'id'
* Permissões:
* Usuário Dono:
* Pode recuperar qualquer de suas Proposições
* Usuário Conectado ou Anônimo:
* Pode recuperar qualquer das proposições incorporadas
"""
class ProposicaoPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
return True
# se a solicitação é list ou detail, libera o teste de permissão
# e deixa o get_queryset filtrar de acordo com a regra de
# visibilidade das proposições, ou seja:
# 1. proposição incorporada é proposição pública
# 2. não incorporada só o autor pode ver
else:
perm = super().has_permission(request, view)
return perm
# não é list ou detail, então passa pelas regras de permissão e,
# depois disso ainda passa pelo filtro de get_queryset
permission_classes = (ProposicaoPermission, )
def get_queryset(self):
qs = super().get_queryset()
params = {'content_type__isnull': False}
username = self.request.user.username
tipo = ''
try:
tipo = int(self.request.GET.get('tipo', ''))
if tipo:
params['id'] = tipo
except Exception as e:
self.logger.error('user= ' + username + '. ' + str(e))
pass
q = Q(data_recebimento__isnull=False, object_id__isnull=False)
if not self.request.user.is_anonymous():
q |= Q(autor__user=self.request.user)
tipos = TipoAutor.objects.filter(**params)
qs = qs.filter(q)
return qs
if not tipos.exists() and tipo:
raise Http404()
r = []
for tipo in tipos:
q = self.request.GET.get('q', '').strip()
class _DocumentoAdministrativoViewSet(SaplSetViews['protocoloadm']['documentoadministrativo']):
model_class = tipo.content_type.model_class()
class DocumentoAdministrativoPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
comportamento = AppConfig.attr('documentos_administrativos')
if comportamento == DOC_ADM_OSTENSIVO:
return True
"""
Diante da lógica implementada na manutenção de documentos
administrativos:
- Se o comportamento é doc adm ostensivo, deve passar pelo
teste de permissões sem avaliá-las
- se o comportamento é doc adm restritivo, deve passar pelo
teste de permissões avaliando-as
"""
return super().has_permission(request, view)
fields = list(filter(
lambda field: isinstance(field, SaplGenericRelation) and
field.related_model == Autor,
model_class._meta.get_fields(include_hidden=True)))
permission_classes = (DocumentoAdministrativoPermission, )
def get_queryset(self):
"""
fields - é um array de SaplGenericRelation que deve possuir o
atributo fields_search. Verifique na documentação da classe
a estrutura de fields_search.
mesmo tendo passado pelo teste de permissões, deve ser filtrado,
pelo campo restrito. Sendo este igual a True, disponibilizar apenas
a um usuário conectado. Apenas isso, sem critérios outros de permissão,
conforme implementado em DocumentoAdministrativoCrud
"""
qs = super().get_queryset()
assert len(fields) >= 1, (_(
'Não foi encontrado em %(model)s um atributo do tipo '
'SaplGenericRelation que use o model %(model_autor)s') % {
'model': model_class._meta.verbose_name,
'model_autor': Autor._meta.verbose_name})
qs = model_class.objects.all()
q_filter = Q()
if q:
for item in fields:
if item.related_model != Autor:
continue
q_fs = Q()
for field in item.fields_search:
q_fs = q_fs | Q(**{'%s%s' % (
field[0],
field[1]): q})
q_filter = q_filter & q_fs
qs = qs.filter(q_filter).distinct(
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])
if self.request.user.is_anonymous():
qs = qs.exclude(restrito=True)
return qs
class _DocumentoAcessorioAdministrativoViewSet(
SaplSetViews['protocoloadm']['documentoacessorioadministrativo']):
qs = qs.values_list(
'id', fields[0].fields_search[0][0])
r += list(qs)
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, )
if tipos.count() > 1:
r.sort(key=lambda x: x[1].upper())
return r
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_anonymous():
qs = qs.exclude(documento__restrito=True)
return qs
class AutoresPossiveisListView(ListAPIView):
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Autor.objects.all()
model = Autor
class _TramitacaoAdministrativoViewSet(
SaplSetViews['protocoloadm']['tramitacaoadministrativo'],
BusinessRulesNotImplementedMixin):
# TODO: Implementar regras de manutenção das tramitações de docs adms
pagination_class = None
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, )
filter_class = AutoresPossiveisFilterSet
serializer_class = AutorChoiceSerializer
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_anonymous():
qs = qs.exclude(documento__restrito=True)
return qs
class MateriaLegislativaViewSet(ListModelMixin,
RetrieveModelMixin,
GenericViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = MateriaLegislativaSerializer
queryset = MateriaLegislativa.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields = ('numero', 'ano', 'tipo', )
class _SessaoPlenariaViewSet(
SaplSetViews['sessao']['sessaoplenaria']):
@action(detail=False)
def years(self, request, *args, **kwargs):
years = choice_anos_com_sessaoplenaria()
class SessaoPlenariaViewSet(ListModelMixin,
RetrieveModelMixin,
GenericViewSet):
serializer = ChoiceSerializer(years, many=True)
return Response(serializer.data)
permission_classes = (AllowAny,)
serializer_class = SessaoPlenariaSerializer
queryset = SessaoPlenaria.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields = ('data_inicio', 'data_fim', 'interativa')
SaplSetViews['base']['autor'] = _AutorViewSet.build_class_with_actions()
SaplSetViews['materia']['proposicao'] = _ProposicaoViewSet
SaplSetViews['parlamentares']['parlamentar'] = _ParlamentarViewSet
SaplSetViews['protocoloadm']['documentoadministrativo'] = _DocumentoAdministrativoViewSet
SaplSetViews['protocoloadm']['documentoacessorioadministrativo'] = _DocumentoAcessorioAdministrativoViewSet
SaplSetViews['protocoloadm']['tramitacaoadministrativo'] = _TramitacaoAdministrativoViewSet
SaplSetViews['sessao']['sessaoplenaria'] = _SessaoPlenariaViewSet

8
sapl/audiencia/forms.py

@ -7,12 +7,12 @@ from django.utils.translation import ugettext_lazy as _
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica, AnexoAudienciaPublica
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.utils import timezone
from sapl.utils import timezone, FileFieldCheckMixin
class AudienciaForm(forms.ModelForm):
class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
logger = logging.getLogger(__name__)
data_atual = timezone.now()
@ -134,7 +134,7 @@ class AnexoAudienciaPublicaForm(forms.ModelForm):
row2 = to_row(
[('assunto', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'),
row1, row2))

34
sapl/audiencia/migrations/0010_auto_20190219_1511.py

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-02-19 18:11
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('audiencia', '0009_remove_anexoaudienciapublica_indexacao'),
]
operations = [
migrations.AlterField(
model_name='anexoaudienciapublica',
name='arquivo',
field=models.FileField(default='Assunto não existente.', upload_to=sapl.utils.texto_upload_path, verbose_name='Arquivo'),
preserve_default=False,
),
migrations.AlterField(
model_name='anexoaudienciapublica',
name='assunto',
field=models.TextField(verbose_name='Assunto'),
),
migrations.AlterField(
model_name='anexoaudienciapublica',
name='data',
field=models.DateField(auto_now=True, default=django.utils.timezone.now),
preserve_default=False,
),
]

22
sapl/audiencia/models.py

@ -155,13 +155,12 @@ class AnexoAudienciaPublica(models.Model):
audiencia = models.ForeignKey(AudienciaPublica,
on_delete=models.PROTECT)
arquivo = models.FileField(
blank=True,
null=True,
upload_to=texto_upload_path,
verbose_name=_('Arquivo'))
data = models.DateField(auto_now=timezone.now,blank=True, null=True)
data = models.DateField(
auto_now=timezone.now)
assunto = models.TextField(
blank=True, verbose_name=_('Assunto'))
verbose_name=_('Assunto'))
class Meta:
verbose_name = _('Anexo de Documento Acessório')
@ -174,22 +173,19 @@ class AnexoAudienciaPublica(models.Model):
if self.arquivo:
self.arquivo.delete()
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
return models.Model.delete(self, using=using, keep_parents=keep_parents)
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
if not self.pk and self.arquivo:
arquivo = self.arquivo
self.arquivo = None
models.Model.save(self, force_insert=force_insert,
models.Model.save(
self,
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
self.arquivo = arquivo
return models.Model.save(self, force_insert=force_insert,
force_update=force_update,
using=using,
return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using,
update_fields=update_fields)

5
sapl/audiencia/urls.py

@ -1,11 +1,10 @@
from django.conf.urls import include, url
from sapl.audiencia.views import (index, AudienciaCrud,AnexoAudienciaPublicaCrud)
from sapl.audiencia.views import (index, AudienciaCrud, AnexoAudienciaPublicaCrud)
from .apps import AppConfig
app_name = AppConfig.name
urlpatterns = [
url(r'^audiencia/', include(AudienciaCrud.get_urls() +
AnexoAudienciaPublicaCrud.get_urls())),
url(r'^audiencia/', include(AudienciaCrud.get_urls() + AnexoAudienciaPublicaCrud.get_urls())),
]

5
sapl/audiencia/views.py

@ -86,6 +86,7 @@ class AnexoAudienciaPublicaCrud(MasterDetailCrud):
model = AnexoAudienciaPublica
parent_field = 'audiencia'
help_topic = 'numeracao_docsacess'
public = [RP_LIST, RP_DETAIL, ]
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['assunto']
@ -104,7 +105,5 @@ class AnexoAudienciaPublicaCrud(MasterDetailCrud):
kwargs = {self.crud.parent_field: self.kwargs['pk']}
return qs.filter(**kwargs).order_by('-data', '-id')
class DetailView(AudienciaPublicaMixin,
MasterDetailCrud.DetailView):
class DetailView(AudienciaPublicaMixin, MasterDetailCrud.DetailView):
pass

2
sapl/base/email_utils.py

@ -18,7 +18,7 @@ def load_email_templates(templates, context={}):
emails = []
for t in templates:
tpl = loader.get_template(t)
email = tpl.render(Context(context))
email = tpl.render(context)
if t.endswith(".html"):
email = email.replace('\n', '').replace('\r', '')
emails.append(email)

389
sapl/base/forms.py

@ -1,8 +1,8 @@
import django_filters
import logging
import os
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row
from django import forms
from django.conf import settings
@ -14,25 +14,33 @@ from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.db.models import Q
from django.forms import Form, ModelForm
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Reuniao, Comissao
from sapl.comissoes.models import Reuniao, Comissao
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
to_row)
from sapl.audiencia.models import AudienciaPublica,TipoAudienciaPublica
from sapl.comissoes.models import Reuniao, Comissao
from sapl.materia.models import (MateriaLegislativa, UnidadeTramitacao, StatusTramitacao)
from sapl.parlamentares.models import SessaoLegislativa
from sapl.materia.models import (
MateriaLegislativa, UnidadeTramitacao, StatusTramitacao)
from sapl.norma.models import (NormaJuridica, NormaEstatisticas)
from sapl.parlamentares.models import SessaoLegislativa, Partido
from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MAX_IMAGE_UPLOAD_SIZE
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
ChoiceWithoutValidationField, ImageThumbnailFileInput,
RangeWidgetOverride, autor_label, autor_modal,
models_with_gr_for_model, qs_override_django_filter)
models_with_gr_for_model, qs_override_django_filter,
choice_anos_com_normas, choice_anos_com_materias,
FilterOverridesMetaMixin, FileFieldCheckMixin)
from .models import AppConfig, CasaLegislativa
ACTION_CREATE_USERS_AUTOR_CHOICE = [
('A', _('Associar um usuário existente')),
('N', _('Autor sem Usuário de Acesso ao Sapl')),
@ -83,7 +91,8 @@ class UsuarioCreateForm(ModelForm):
data = self.cleaned_data
if data['password1'] != data['password2']:
self.logger.error('Erro de validação. Senhas informadas ({}, {}) são diferentes.'.format(data['password1'], data['password2']))
self.logger.error('Erro de validação. Senhas informadas ({}, {}) são diferentes.'.format(
data['password1'], data['password2']))
raise ValidationError('Senhas informadas são diferentes')
return data
@ -103,16 +112,38 @@ class UsuarioCreateForm(ModelForm):
[('password1', 6),
('password2', 6)])
row4 = to_row([(form_actions(label='Confirmar'), 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row0,
row1,
row3,
row2,
'roles',
row4)
form_actions(label='Confirmar'))
class UsuarioFilterSet(django_filters.FilterSet):
username = django_filters.CharFilter(
label=_('Nome de Usuário'),
lookup_expr='icontains')
class Meta:
model = User
fields = ['username']
def __init__(self, *args, **kwargs):
super(UsuarioFilterSet, self).__init__(*args, **kwargs)
row0 = to_row([('username', 12)])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Usuário'),
row0,
form_actions(label='Pesquisar'))
)
class UsuarioEditForm(ModelForm):
@ -120,8 +151,10 @@ class UsuarioEditForm(ModelForm):
# ROLES = [(g.id, g.name) for g in Group.objects.all().order_by('name')]
ROLES = []
password1 = forms.CharField(required=False, widget=forms.PasswordInput, label='Senha')
password2 = forms.CharField(required=False, widget=forms.PasswordInput, label='Confirmar senha')
password1 = forms.CharField(
required=False, widget=forms.PasswordInput, label='Senha')
password2 = forms.CharField(
required=False, widget=forms.PasswordInput, label='Confirmar senha')
user_active = forms.ChoiceField(choices=YES_NO_CHOICES, required=True,
label="Usuário ativo?", initial='True')
roles = forms.MultipleChoiceField(
@ -143,12 +176,12 @@ class UsuarioEditForm(ModelForm):
row3 = to_row([(form_actions(label='Salvar Alterações'), 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row1,
row2,
'roles',
row3)
form_actions(label='Salvar Alterações'))
def clean(self):
super(UsuarioEditForm, self).clean()
@ -158,13 +191,16 @@ class UsuarioEditForm(ModelForm):
data = self.cleaned_data
if data['password1'] and data['password1'] != data['password2']:
self.logger.error('Erro de validação. Senhas informadas ({}, {}) são diferentes.'.format(data['password1'], data['password2']))
self.logger.error('Erro de validação. Senhas informadas ({}, {}) são diferentes.'.format(
data['password1'], data['password2']))
raise ValidationError('Senhas informadas são diferentes')
return data
class SessaoLegislativaForm(ModelForm):
class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm):
logger = logging.getLogger(__name__)
class Meta:
model = SessaoLegislativa
exclude = []
@ -185,13 +221,18 @@ class SessaoLegislativaForm(ModelForm):
data_fim_leg = legislatura.data_fim
pk = self.initial['id'] if self.initial else None
# Queries para verificar se existem Sessões Legislativas no período selecionado no form
# Caso onde a data_inicio e data_fim são iguais a de alguma sessão já criada
# Caso onde a data_inicio e data_fim são iguais a de alguma sessão já
# criada
primeiro_caso = Q(data_inicio=data_inicio, data_fim=data_fim)
# Caso onde a data_inicio está entre o início e o fim de uma Sessão já existente
segundo_caso = Q(data_inicio__lt=data_inicio, data_fim__range=(data_inicio, data_fim))
# Caso onde a data_fim está entre o início e o fim de uma Sessão já existente
terceiro_caso = Q(data_inicio__range=(data_inicio, data_fim), data_fim__gt=data_fim)
sessoes_existentes = SessaoLegislativa.objects.filter(primeiro_caso|segundo_caso|terceiro_caso).\
# Caso onde a data_inicio está entre o início e o fim de uma Sessão já
# existente
segundo_caso = Q(data_inicio__lt=data_inicio,
data_fim__range=(data_inicio, data_fim))
# Caso onde a data_fim está entre o início e o fim de uma Sessão já
# existente
terceiro_caso = Q(data_inicio__range=(
data_inicio, data_fim), data_fim__gt=data_fim)
sessoes_existentes = SessaoLegislativa.objects.filter(primeiro_caso | segundo_caso | terceiro_caso).\
exclude(pk=pk)
if sessoes_existentes:
@ -236,8 +277,10 @@ class SessaoLegislativaForm(ModelForm):
'entre a data início e fim da Legislatura selecionada')
if data_inicio > data_fim:
self.logger.error('Data início ({}) superior à data fim ({}).'.format(data_inicio, data_fim))
raise ValidationError('Data início não pode ser superior à data fim')
self.logger.error(
'Data início ({}) superior à data fim ({}).'.format(data_inicio, data_fim))
raise ValidationError(
'Data início não pode ser superior à data fim')
data_inicio_intervalo = cleaned_data['data_inicio_intervalo']
data_fim_intervalo = cleaned_data['data_fim_intervalo']
@ -373,7 +416,7 @@ class AutorForm(ModelForm):
placeholder=_('Pesquisar por possíveis autores para '
'o Tipo de Autor selecionado.')),
StrictButton(
_('Filtrar'), css_class='btn-default btn-filtrar-autor',
_('Filtrar'), css_class='btn-outline-primary btn-filtrar-autor',
type='button')),
css_class='hidden',
data_action='create',
@ -381,9 +424,9 @@ class AutorForm(ModelForm):
data_field='autor_related')
autor_select = Row(to_column(('tipo', 3)),
Div(to_column(('nome', 5)),
to_column(('cargo', 4)),
css_class="div_nome_cargo"),
Div(to_column(('nome', 7)),
to_column(('cargo', 5)),
css_class="div_nome_cargo row col"),
to_column((autor_related, 9)),
to_column((Div(
Field('autor_related'),
@ -411,7 +454,7 @@ class AutorForm(ModelForm):
controle_acesso = Fieldset(_('Controle de Acesso do Autor'),
*controle_acesso)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(autor_select, controle_acesso)
super(AutorForm, self).__init__(*args, **kwargs)
@ -463,7 +506,8 @@ class AutorForm(ModelForm):
def valida_igualdade(self, texto1, texto2, msg):
if texto1 != texto2:
self.logger.error('Textos diferentes. ("{}" e "{}")'.format(texto1, texto2))
self.logger.error(
'Textos diferentes. ("{}" e "{}")'.format(texto1, texto2))
raise ValidationError(msg)
return True
@ -508,7 +552,8 @@ class AutorForm(ModelForm):
if cd['action_user'] == 'A':
param_username = {get_user_model().USERNAME_FIELD: cd['username']}
if not User.objects.filter(**param_username).exists():
self.logger.error('Não existe usuário com username "%s". ' % cd['username'])
self.logger.error(
'Não existe usuário com username "%s". ' % cd['username'])
raise ValidationError(
_('Não existe usuário com username "%s". '
'Para utilizar esse username você deve selecionar '
@ -523,7 +568,8 @@ class AutorForm(ModelForm):
param_username = {
'user__' + get_user_model().USERNAME_FIELD: cd['username']}
if qs_autor.filter(**param_username).exists():
self.logger.error('Já existe um Autor para este usuário ({}).'.format(cd['username']))
self.logger.error(
'Já existe um Autor para este usuário ({}).'.format(cd['username']))
raise ValidationError(
_('Já existe um Autor para este usuário.'))
@ -654,14 +700,7 @@ class AutorFormForAdmin(AutorForm):
class RelatorioAtasFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
class Meta:
class Meta(FilterOverridesMetaMixin):
model = SessaoPlenaria
fields = ['data_inicio']
@ -680,7 +719,7 @@ class RelatorioAtasFilterSet(django_filters.FilterSet):
row1 = to_row([('data_inicio', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Atas das Sessões Plenárias'),
@ -688,16 +727,117 @@ class RelatorioAtasFilterSet(django_filters.FilterSet):
)
class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
def ultimo_ano_com_norma():
anos_normas = choice_anos_com_normas()
if anos_normas:
return anos_normas[0]
return ''
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
class RelatorioNormasMesFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True,
label='Ano da Norma',
choices=choice_anos_com_normas,
initial=ultimo_ano_com_norma)
class Meta:
model = NormaJuridica
fields = ['ano']
def __init__(self, *args, **kwargs):
super(RelatorioNormasMesFilterSet, self).__init__(
*args, **kwargs)
self.filters['ano'].label = 'Ano'
self.form.fields['ano'].required = True
row1 = to_row([('ano', 12)])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Normas por mês do ano.'),
row1, form_actions(label='Pesquisar'))
)
@property
def qs(self):
parent = super(RelatorioNormasMesFilterSet, self).qs
return parent.distinct().order_by('data')
class EstatisticasAcessoNormasForm(Form):
ano = forms.ChoiceField(required=True,
label='Ano de acesso',
choices=RANGE_ANOS,
initial=timezone.now().year)
class Meta:
fields = ['ano']
def __init__(self, *args, **kwargs):
super(EstatisticasAcessoNormasForm, self).__init__(
*args, **kwargs)
row1 = to_row([('ano', 12)])
self.helper = SaplFormHelper()
self.helper.form_method = 'GET'
self.helper.layout = Layout(
Fieldset(_('Normas por acessos nos meses do ano.'),
row1, form_actions(label='Pesquisar'))
)
def clean(self):
super(EstatisticasAcessoNormasForm, self).clean()
return self.cleaned_data
class RelatorioNormasVigenciaFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True,
label='Ano da Norma',
choices=choice_anos_com_normas,
initial=ultimo_ano_com_norma)
vigencia = forms.ChoiceField(
label=_('Vigência'),
choices=[(True, "Vigente"), (False, "Não vigente")],
widget=forms.RadioSelect(),
required=True,
initial=True)
def __init__(self, *args, **kwargs):
super(RelatorioNormasVigenciaFilterSet, self).__init__(
*args, **kwargs)
self.filters['ano'].label = 'Ano'
self.form.fields['ano'].required = True
self.form.fields['vigencia'] = self.vigencia
row1 = to_row([('ano', 12)])
row2 = to_row([('vigencia', 12)])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Normas por vigência.'),
row1, row2,
form_actions(label='Pesquisar'))
)
@property
def qs(self):
return qs_override_django_filter(self)
class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
class Meta(FilterOverridesMetaMixin):
model = SessaoPlenaria
fields = ['data_inicio']
@ -710,7 +850,7 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
row1 = to_row([('data_inicio', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Presença dos parlamentares nas sessões plenárias'),
@ -724,19 +864,12 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
@property
def qs(self):
parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs
return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
class Meta:
class Meta(FilterOverridesMetaMixin):
model = MateriaLegislativa
fields = ['tipo', 'tramitacao__unidade_tramitacao_local',
'tramitacao__status', 'tramitacao__data_tramitacao']
@ -747,13 +880,17 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['tramitacao__unidade_tramitacao_local'
].label = _('Unidade Local')
self.filters['tramitacao__status'].label = _('Status')
row1 = to_row([('tramitacao__data_tramitacao', 12)])
row2 = to_row(
[('tipo', 4),
('tramitacao__unidade_tramitacao_local', 4),
('tramitacao__status', 4)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Histórico de Tramitação'),
@ -764,19 +901,12 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
@property
def qs(self):
parent = super(RelatorioDataFimPrazoTramitacaoFilterSet, self).qs
return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
class Meta:
class Meta(FilterOverridesMetaMixin):
model = MateriaLegislativa
fields = ['tipo', 'tramitacao__unidade_tramitacao_local',
'tramitacao__status', 'tramitacao__data_fim_prazo']
@ -793,7 +923,7 @@ class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
('tramitacao__unidade_tramitacao_local', 4),
('tramitacao__status', 4)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Tramitações por fim de prazo'),
@ -812,7 +942,7 @@ class RelatorioReuniaoFilterSet(django_filters.FilterSet):
class Meta:
model = Reuniao
fields = ['comissao', 'data',
'nome','tema']
'nome', 'tema']
def __init__(self, *args, **kwargs):
super(RelatorioReuniaoFilterSet, self).__init__(
@ -824,7 +954,7 @@ class RelatorioReuniaoFilterSet(django_filters.FilterSet):
('nome', 4),
('tema', 4)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Reunião de Comissão'),
@ -832,6 +962,7 @@ class RelatorioReuniaoFilterSet(django_filters.FilterSet):
form_actions(label='Pesquisar'))
)
class RelatorioAudienciaFilterSet(django_filters.FilterSet):
@property
@ -853,7 +984,7 @@ class RelatorioAudienciaFilterSet(django_filters.FilterSet):
[('tipo', 4),
('nome', 4)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Audiência Pública'),
@ -862,12 +993,11 @@ class RelatorioAudienciaFilterSet(django_filters.FilterSet):
)
class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True,
label='Ano da Matéria',
choices=RANGE_ANOS)
choices=choice_anos_com_materias)
tramitacao__unidade_tramitacao_destino = django_filters.ModelChoiceFilter(
queryset=UnidadeTramitacao.objects.all(),
@ -882,7 +1012,6 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
parent = super(RelatorioMateriasTramitacaoilterSet, self).qs
return parent.distinct().order_by('-ano', 'tipo', '-numero')
class Meta:
model = MateriaLegislativa
fields = ['ano', 'tipo', 'tramitacao__unidade_tramitacao_destino',
@ -899,7 +1028,7 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
row3 = to_row([('tramitacao__unidade_tramitacao_destino', 12)])
row4 = to_row([('tramitacao__status', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Matéria em Tramitação'),
@ -912,7 +1041,7 @@ class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True,
label='Ano da Matéria',
choices=RANGE_ANOS)
choices=choice_anos_com_materias)
class Meta:
model = MateriaLegislativa
@ -925,7 +1054,7 @@ class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
row1 = to_row(
[('ano', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar'),
@ -936,13 +1065,6 @@ class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
@property
@ -951,7 +1073,7 @@ class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
return parent.distinct().filter(autoria__primeiro_autor=True)\
.order_by('autoria__autor', '-autoria__primeiro_autor', 'tipo', '-ano', '-numero')
class Meta:
class Meta(FilterOverridesMetaMixin):
model = MateriaLegislativa
fields = ['tipo', 'data_apresentacao']
@ -974,7 +1096,7 @@ class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
'limpar Autor',
css_class='btn btn-primary btn-sm'), 10)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar'),
@ -986,7 +1108,7 @@ class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
)
class CasaLegislativaForm(ModelForm):
class CasaLegislativaForm(FileFieldCheckMixin, ModelForm):
class Meta:
@ -1016,7 +1138,11 @@ class CasaLegislativaForm(ModelForm):
}
def clean_logotipo(self):
logotipo = self.cleaned_data.get('logotipo', False)
# chama __clean de FileFieldCheckMixin
# por estar em clean de campo
super(CasaLegislativaForm, self)._check()
logotipo = self.cleaned_data.get('logotipo')
if logotipo:
if logotipo.size > MAX_IMAGE_UPLOAD_SIZE:
raise ValidationError("Imagem muito grande. ( > 2MB )")
@ -1055,13 +1181,16 @@ class ConfiguracoesAppForm(ModelForm):
'texto_articulado_materia',
'texto_articulado_norma',
'proposicao_incorporacao_obrigatoria',
'protocolo_manual',
'cronometro_discurso',
'cronometro_aparte',
'cronometro_ordem',
'cronometro_consideracoes',
'mostrar_brasao_painel',
'receber_recibo_proposicao',
'assinatura_ata']
'assinatura_ata',
'estatisticas_acesso_normas',
'escolher_numero_materia_proposicao']
def __init__(self, *args, **kwargs):
super(ConfiguracoesAppForm, self).__init__(*args, **kwargs)
@ -1070,7 +1199,6 @@ class ConfiguracoesAppForm(ModelForm):
self.fields['cronometro_ordem'].widget.attrs['class'] = 'cronometro'
self.fields['cronometro_consideracoes'].widget.attrs['class'] = 'cronometro'
def clean_mostrar_brasao_painel(self):
mostrar_brasao_painel = self.cleaned_data.get(
'mostrar_brasao_painel', False)
@ -1080,7 +1208,7 @@ class ConfiguracoesAppForm(ModelForm):
self.logger.error('Não há casa legislativa relacionada.')
raise ValidationError("Não há casa legislativa relacionada.")
if (not bool(casa.logotipo) and mostrar_brasao_painel):
if not casa.logotipo and mostrar_brasao_painel:
self.logger.error('Não há logitipo configurado para esta '
'CasaLegislativa ({}).'.format(casa))
raise ValidationError("Não há logitipo configurado para esta "
@ -1096,7 +1224,7 @@ class RecuperarSenhaForm(PasswordResetForm):
def __init__(self, *args, **kwargs):
row1 = to_row(
[('email', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Insira o e-mail cadastrado com a sua conta'),
row1,
@ -1133,7 +1261,7 @@ class NovaSenhaForm(SetPasswordForm):
[('new_password1', 6),
('new_password2', 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row1,
form_actions(label='Enviar'))
@ -1166,7 +1294,7 @@ class AlterarSenhaForm(Form):
[('new_password1', 6),
('new_password2', 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row1,
row2,
@ -1184,24 +1312,30 @@ class AlterarSenhaForm(Form):
new_password2 = data['new_password2']
if new_password1 != new_password2:
self.logger.error("'Nova Senha' ({}) diferente de 'Confirmar Senha' ({})".format(new_password1, new_password2))
raise ValidationError("'Nova Senha' diferente de 'Confirmar Senha'")
self.logger.error("'Nova Senha' ({}) diferente de 'Confirmar Senha' ({})".format(
new_password1, new_password2))
raise ValidationError(
"'Nova Senha' diferente de 'Confirmar Senha'")
# TODO: colocar mais regras como: tamanho mínimo,
# TODO: caracteres alfanuméricos, maiúsculas (?),
# TODO: senha atual igual a senha anterior, etc
if len(new_password1) < 6:
self.logger.error('A senha informada ({}) não tem o mínimo de 6 caracteres.'.format(new_password1))
raise ValidationError("A senha informada deve ter no mínimo 6 caracteres")
self.logger.error(
'A senha informada ({}) não tem o mínimo de 6 caracteres.'.format(new_password1))
raise ValidationError(
"A senha informada deve ter no mínimo 6 caracteres")
username = data['username']
old_password = data['old_password']
user = User.objects.get(username=username)
if user.is_anonymous():
self.logger.error('Não é possível alterar senha de usuário anônimo ({}).'.format(username))
raise ValidationError("Não é possível alterar senha de usuário anônimo")
self.logger.error(
'Não é possível alterar senha de usuário anônimo ({}).'.format(username))
raise ValidationError(
"Não é possível alterar senha de usuário anônimo")
if not user.check_password(old_password):
self.logger.error('Senha atual informada ({}) não confere '
@ -1210,7 +1344,52 @@ class AlterarSenhaForm(Form):
"com a senha armazenada")
if user.check_password(new_password1):
self.logger.error('Nova senha ({}) igual à senha anterior.'.format(new_password1))
raise ValidationError("Nova senha não pode ser igual à senha anterior")
self.logger.error(
'Nova senha ({}) igual à senha anterior.'.format(new_password1))
raise ValidationError(
"Nova senha não pode ser igual à senha anterior")
return self.cleaned_data
class PartidoForm(FileFieldCheckMixin, ModelForm):
class Meta:
model = Partido
exclude = []
def __init__(self, *args, **kwargs):
super(PartidoForm, self).__init__(*args, **kwargs)
# TODO Utilizar esses campos na issue #2161 de alteração de nomes de partidos
# if self.instance:
# if self.instance.nome:
# self.fields['nome'].widget.attrs['readonly'] = True
# self.fields['sigla'].widget.attrs['readonly'] = True
row1 = to_row(
[('sigla', 2),
('nome', 6),
('data_criacao', 2),
('data_extincao', 2),])
row2 = to_row([('observacao', 12)])
row3 = to_row([('logo_partido', 12)])
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row1, row2, row3,
form_actions(label='Salvar'))
def clean(self):
cleaned_data = super(PartidoForm, self).clean()
if not self.is_valid():
return cleaned_data
if cleaned_data['data_criacao'] and cleaned_data['data_extincao']:
if cleaned_data['data_criacao'] > cleaned_data['data_extincao']:
raise ValidationError("Certifique-se de que a data de criação seja anterior à data de extinção.")
return cleaned_data

20
sapl/base/migrations/0027_appconfig_relatorios_atos.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-12-11 20:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0026_auto_20181126_1727'),
]
operations = [
migrations.AddField(
model_name='appconfig',
name='relatorios_atos',
field=models.CharField(choices=[('S', 'Sim'), ('N', 'Não')], default='N', max_length=1, verbose_name='Relatórios de atos acessados'),
),
]

20
sapl/base/migrations/0028_appconfig_estatisticas_acesso_normas.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-12-18 17:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0027_appconfig_relatorios_atos'),
]
operations = [
migrations.AddField(
model_name='appconfig',
name='estatisticas_acesso_normas',
field=models.CharField(choices=[('S', 'Sim'), ('N', 'Não')], default='N', max_length=1, verbose_name='Estatísticas de acesso a normas'),
),
]

19
sapl/base/migrations/0029_remove_appconfig_relatorios_atos.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-12-18 18:40
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('base', '0028_appconfig_estatisticas_acesso_normas'),
]
operations = [
migrations.RemoveField(
model_name='appconfig',
name='relatorios_atos',
),
]

20
sapl/base/migrations/0030_appconfig_escolher_numero_materia_proposicao.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-02-19 11:14
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0029_remove_appconfig_relatorios_atos'),
]
operations = [
migrations.AddField(
model_name='appconfig',
name='escolher_numero_materia_proposicao',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Indicar número da matéria a ser gerada na proposição?'),
),
]

20
sapl/base/migrations/0030_appconfig_protocolo_manual.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-02-15 18:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0029_remove_appconfig_relatorios_atos'),
]
operations = [
migrations.AddField(
model_name='appconfig',
name='protocolo_manual',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Protocolar proposição somente com recibo?'),
),
]

20
sapl/base/migrations/0031_auto_20190218_1109.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-02-18 14:09
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0030_appconfig_protocolo_manual'),
]
operations = [
migrations.AlterField(
model_name='appconfig',
name='protocolo_manual',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Informar data e hora de protocolo?'),
),
]

16
sapl/base/migrations/0032_merge_20190219_0941.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-02-19 12:41
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('base', '0031_auto_20190218_1109'),
('base', '0030_appconfig_escolher_numero_materia_proposicao'),
]
operations = [
]

31
sapl/base/models.py

@ -1,16 +1,22 @@
import reversion
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.signals import post_migrate
from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext_lazy as _
#from model_utils import Choices
import reversion
from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES,
get_settings_auth_user_model, models_with_gr_for_model)
TIPO_DOCUMENTO_ADMINISTRATIVO = (('O', _('Ostensiva')),
('R', _('Restritiva')))
DOC_ADM_OSTENSIVO = 'O'
DOC_ADM_RESTRITIVO = 'R'
TIPO_DOCUMENTO_ADMINISTRATIVO = ((DOC_ADM_OSTENSIVO, _('Ostensiva')),
(DOC_ADM_RESTRITIVO, _('Restritiva')))
RELATORIO_ATOS_ACESSADOS = (('S', _('Sim')),
('N', _('Não')))
SEQUENCIA_NUMERACAO = (('A', _('Sequencial por ano')),
('L', _('Sequencial por legislatura')),
@ -19,7 +25,7 @@ SEQUENCIA_NUMERACAO = (('A', _('Sequencial por ano')),
ESFERA_FEDERACAO_CHOICES = (('M', _('Municipal')),
('E', _('Estadual')),
('F', _('Federal')),
)
)
ASSINATURA_ATA_CHOICES = (
('M', _('Mesa Diretora da Sessão')),
@ -84,6 +90,11 @@ class AppConfig(models.Model):
verbose_name=_('Visibilidade dos Documentos Administrativos'),
choices=TIPO_DOCUMENTO_ADMINISTRATIVO, default='O')
estatisticas_acesso_normas = models.CharField(
max_length=1,
verbose_name=_('Estatísticas de acesso a normas'),
choices=RELATORIO_ATOS_ACESSADOS, default='N')
sequencia_numeracao = models.CharField(
max_length=1,
verbose_name=_('Sequência de numeração'),
@ -92,7 +103,7 @@ class AppConfig(models.Model):
esfera_federacao = models.CharField(
max_length=1,
blank=True,
default = "",
default="",
verbose_name=_('Esfera Federação'),
choices=ESFERA_FEDERACAO_CHOICES)
@ -149,6 +160,14 @@ class AppConfig(models.Model):
verbose_name=_('Protocolar proposição somente com recibo?'),
choices=YES_NO_CHOICES, default=True)
protocolo_manual = models.BooleanField(
verbose_name=_('Informar data e hora de protocolo?'),
choices=YES_NO_CHOICES, default=False)
escolher_numero_materia_proposicao = models.BooleanField(
verbose_name=_('Indicar número da matéria a ser gerada na proposição?'),
choices=YES_NO_CHOICES, default=False)
class Meta:
verbose_name = _('Configurações da Aplicação')
verbose_name_plural = _('Configurações da Aplicação')

72
sapl/base/search_indexes.py

@ -1,6 +1,4 @@
import os.path
import re
import string
import textract
import logging
@ -8,6 +6,7 @@ from django.db.models import F, Q, Value
from django.db.models.fields import TextField
from django.db.models.functions import Concat
from django.template import loader
from haystack import connections
from haystack.constants import Indexable
from haystack.fields import CharField
from haystack.indexes import SearchIndex
@ -24,6 +23,7 @@ from sapl.utils import RemoveTag
class TextExtractField(CharField):
backend = None
logger = logging.getLogger(__name__)
def __init__(self, **kwargs):
@ -34,24 +34,20 @@ class TextExtractField(CharField):
self.model_attr = (self.model_attr, )
def solr_extraction(self, arquivo):
extracted_data = self._get_backend(None).extract_file_contents(
arquivo)['contents']
# Remove as tags xml
self.logger.debug("Removendo as tags xml.")
extracted_data = re.sub('<[^>]*>', '', extracted_data)
# Remove tags \t e \n
self.logger.debug("Removendo as \t e \n.")
extracted_data = extracted_data.replace(
'\n', ' ').replace('\t', ' ')
# Remove sinais de pontuação
self.logger.debug("Removendo sinais de pontuação.")
extracted_data = re.sub('[' + string.punctuation + ']',
' ', extracted_data)
# Remove espaços múltiplos
self.logger.debugger("Removendo espaços múltiplos.")
extracted_data = " ".join(extracted_data.split())
return extracted_data
if not self.backend:
self.backend = connections['default'].get_backend()
try:
with open(arquivo.path, 'rb') as f:
content = self.backend.extract_file_contents(f)
if not content or not content['contents']:
return ''
data = content['contents']
except Exception as e:
print('erro processando arquivo: ' % arquivo.path)
self.logger.error(arquivo.path)
self.logger.error('erro processando arquivo: ' % arquivo.path)
data = ''
return data
def whoosh_extraction(self, arquivo):
@ -66,11 +62,11 @@ class TextExtractField(CharField):
language='pt-br').decode('utf-8').replace('\n', ' ').replace(
'\t', ' ')
def print_error(self, arquivo):
self.logger.error("Erro inesperado processando arquivo: {}".format(arquivo.path))
msg = 'Erro inesperado processando arquivo: %s' % (
arquivo.path)
print(msg)
def print_error(self, arquivo, error):
msg = 'Erro inesperado processando arquivo %s erro: %s' % (
arquivo.path, error)
print(msg, error)
self.logger.error(msg, error)
def file_extractor(self, arquivo):
if not os.path.exists(arquivo.path) or \
@ -81,9 +77,9 @@ class TextExtractField(CharField):
if SOLR_URL:
try:
return self.solr_extraction(arquivo)
except Exception as e:
self.logger.error("Erro no arquivo {}. ".format(arquivo.path) + str(e))
self.print_error(arquivo)
except Exception as err:
print(str(err))
self.print_error(arquivo, err)
# Em ambiente de DEV utiliza-se o Whoosh
# Como ele não possui extração, faz-se uso do textract
@ -91,13 +87,13 @@ class TextExtractField(CharField):
try:
self.logger.debug("Tentando whoosh_extraction no arquivo {}".format(arquivo.path))
return self.whoosh_extraction(arquivo)
except ExtensionNotSupported as e:
self.logger.error("Erro no arquivo {}".format(arquivo.path) + str(e))
print(str(e))
except Exception as e2:
self.logger.error(str(e))
print(str(e2))
self.print_error(arquivo)
except ExtensionNotSupported as err:
print(str(err))
self.logger.error(str(err))
except Exception as err:
print(str(err))
self.print_error(arquivo, str(err))
return ''
def ta_extractor(self, value):
@ -133,7 +129,9 @@ class TextExtractField(CharField):
value = getattr(obj, attr)
if not value:
continue
data += getattr(self, func)(value)
data += getattr(self, func)(value) + ' '
data = data.replace('\n', ' ')
return data
@ -159,6 +157,10 @@ class DocumentoAcessorioIndex(SearchIndex, Indexable):
)
)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.text.search_index = self
def get_model(self):
return self.model

25
sapl/base/templatetags/common_tags.py

@ -1,9 +1,7 @@
import logging
from compressor.utils import get_class
from django import template
from django.conf import settings
from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe
from webpack_loader import utils
from sapl.base.models import AppConfig
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, Proposicao
@ -15,6 +13,15 @@ from sapl.utils import filiacao_data, SEPARADOR_HASH_PROPOSICAO
register = template.Library()
def get_class(class_string):
if not hasattr(class_string, '__bases__'):
class_string = str(class_string)
dot = class_string.rindex('.')
mod_name, class_name = class_string[:dot], class_string[dot + 1:]
if class_name:
return getattr(__import__(mod_name, {}, {}, [str('')]), class_name)
@register.simple_tag
def define(arg):
return arg
@ -269,3 +276,13 @@ def filiacao_data_filter(parlamentar, data_inicio):
@register.filter
def filiacao_intervalo_filter(parlamentar, date_range):
return filiacao_data(parlamentar, date_range[0], date_range[1])
@register.simple_tag
def render_chunk_vendors(extension=None):
try:
tags = utils.get_as_tags(
'chunk-vendors', extension=extension, config='DEFAULT', attrs='')
return mark_safe('\n'.join(tags))
except:
return ''

58
sapl/base/templatetags/menus.py

@ -1,3 +1,5 @@
import logging
from django import template
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
@ -6,6 +8,8 @@ import yaml
register = template.Library()
logger = logging.getLogger(__name__)
@register.inclusion_tag('menus/menu.html', takes_context=True)
def menu(context, path=None):
@ -84,7 +88,7 @@ def nav_run(context, path=None):
menu = yaml.load(rendered)
resolve_urls_inplace(menu, root_pk, rm, context)
except Exception as e:
print(_("""Erro na conversão do yaml %s. App: %s.
raise Exception(_("""Erro na conversão do yaml %s. App: %s.
Erro:
%s
""") % (
@ -113,25 +117,61 @@ def resolve_urls_inplace(menu, pk, rm, context):
menu['url'] = ''
menu['active'] = ''
else:
if ':' in url_name:
if '/' in url_name:
pass
elif ':' in url_name:
try:
menu['url'] = reverse('%s' % menu['url'],
kwargs={'pk': pk})
menu['url'] = reverse('%s' % menu['url'])
except:
try:
menu['url'] = reverse('%s' % menu['url'])
menu['url'] = reverse('%s' % menu['url'],
kwargs={'pk': pk})
except:
pass
# tem que ser root_pk pois quando está sendo
# renderizado um detail, update, delete
# e ainda sim é necessário colocar o menu,
# nestes, casos o pk da url é do detail, e não
# do master, porém, os menus do subnav, apontam para
# outras áreas que as urls destas são construídas
# com pk do master, e não do detail... por isso
# no contexto deve ter, ou root_pk, ou object
# sendo que qualquer um dos dois,deverá ser o
# master.
# Estes detalhes são relevantes quando usa-se
# o menu isolado. Por outro lado, quando usado
# conjuntamente com o crud, este configura o contexto
# como se deve para o menus.py
log = """
Erro na construção do Menu:
menu: {}
url: {}
1) Verifique se a url existe
2) Se existe no contexto um desses itens:
- context['root_pk'] pk do master
- context['object'] objeto do master
""".format(menu['title'], menu['url'])
logger.error(log)
raise Exception(log)
else:
try:
menu['url'] = reverse('%s:%s' % (
rm.app_name, menu['url']), kwargs={'pk': pk})
rm.app_name, menu['url']))
except:
try:
menu['url'] = reverse('%s:%s' % (
rm.app_name, menu['url']))
rm.app_name, menu['url']), kwargs={'pk': pk})
except:
pass
log = """Erro na construção do Menu:
menu: {}
url: {}
1) Verifique se a url existe
2) Se existe no contexto um desses itens:
- context['root_pk'] pk do master
- context['object'] objeto do master
""".format(menu['title'], menu['url'])
logger.error(log)
raise Exception(log)
menu['active'] = 'active'\
if context['request'].path == menu['url'] else ''

8
sapl/base/tests/test_login.py

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import pytest
from django.contrib.auth import get_user_model
import pytest
pytestmark = pytest.mark.django_db
@ -12,14 +13,15 @@ def user():
def test_login_aparece_na_barra_para_usuario_nao_logado(client):
response = client.get('/')
assert '<a href="/login/"><img src="/static/img/user.png"></a>' in str(
assert '<a class="nav-link" href="/login/"><img src="/static/sapl/img/user.png"></a>' in str(
response.content)
def test_username_do_usuario_logado_aparece_na_barra(client, user):
assert client.login(username='jfirmino', password='123')
response = client.get('/')
assert '<a href="/login/">Login</a>' not in str(response.content)
assert '<a class="nav-link" href="/login/">Login</a>' not in str(
response.content)
assert 'jfirmino' in str(response.content)
assert '<a href="/logout/">Sair</a>' in str(response.content)

56
sapl/base/urls.py

@ -15,7 +15,7 @@ from .apps import AppConfig
from .forms import LoginForm, NovaSenhaForm, RecuperarSenhaForm
from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud,
CreateUsuarioView, DeleteUsuarioView, EditUsuarioView,
HelpTopicView, ListarUsuarioView, LogotipoView,
HelpTopicView, PesquisarUsuarioView, LogotipoView,
RelatorioAtasView, RelatorioAudienciaView,
RelatorioDataFimPrazoTramitacaoView,
RelatorioHistoricoTramitacaoView,
@ -23,12 +23,25 @@ from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud,
RelatorioMateriasPorAutorView,
RelatorioMateriasTramitacaoView,
RelatorioPresencaSessaoView,
RelatorioReuniaoView, SaplSearchView)
RelatorioReuniaoView, SaplSearchView,
RelatorioNormasPublicadasMesView,
RelatorioNormasVigenciaView,
EstatisticasAcessoNormas,
RelatoriosListView,
ListarInconsistenciasView, ListarProtocolosDuplicadosView,
ListarProtocolosComMateriasView,
ListarMatProtocoloInexistenteView,
ListarParlMandatosIntersecaoView,
ListarAutoresDuplicadosView,
ListarBancadaComissaoAutorExternoView,
ListarLegislaturaInfindavelView,
ListarMandatoSemDataInicioView)
app_name = AppConfig.name
admin_user = [
url(r'^sistema/usuario/$', ListarUsuarioView.as_view(), name='user_list'),
url(r'^sistema/usuario/$', PesquisarUsuarioView.as_view(), name='usuario'),
url(r'^sistema/usuario/create$', CreateUsuarioView.as_view(), name='user_create'),
url(r'^sistema/usuario/(?P<pk>\d+)/edit$', EditUsuarioView.as_view(), name='user_edit'),
url(r'^sistema/usuario/(?P<pk>\d+)/delete$', DeleteUsuarioView.as_view(), name='user_delete')
@ -84,10 +97,16 @@ urlpatterns = [
url(r'^sistema/app-config/', include(AppConfigCrud.get_urls())),
# TODO mover estas telas para a app 'relatorios'
url(r'^sistema/relatorios/$', TemplateView.as_view(
template_name='base/relatorios_list.html'), name='relatorios_list'),
url(r'^sistema/relatorios/$',
RelatoriosListView.as_view(), name='relatorios_list'),
url(r'^sistema/relatorios/materia-por-autor$',
RelatorioMateriasPorAutorView.as_view(), name='materia_por_autor'),
url(r'^sistema/relatorios/relatorio-por-mes$',
RelatorioNormasPublicadasMesView.as_view(), name='normas_por_mes'),
url(r'^sistema/relatorios/relatorio-por-vigencia$',
RelatorioNormasVigenciaView.as_view(), name='normas_por_vigencia'),
url(r'^sistema/relatorios/estatisticas-acesso$',
EstatisticasAcessoNormas.as_view(), name='estatisticas_acesso'),
url(r'^sistema/relatorios/materia-por-ano-autor-tipo$',
RelatorioMateriasPorAnoAutorTipoView.as_view(),
name='materia_por_ano_autor_tipo'),
@ -117,6 +136,33 @@ urlpatterns = [
'(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})$',
ConfirmarEmailView.as_view(), name='confirmar_email'),
url(r'^sistema/inconsistencias/$',
ListarInconsistenciasView.as_view(),
name='lista_inconsistencias'),
url(r'^sistema/inconsistencias/protocolos_duplicados$',
ListarProtocolosDuplicadosView.as_view(),
name='lista_protocolos_duplicados'),
url(r'^sistema/inconsistencias/protocolos_com_materias$',
ListarProtocolosComMateriasView.as_view(),
name='lista_protocolos_com_materias'),
url(r'^sistema/inconsistencias/materias_protocolo_inexistente$',
ListarMatProtocoloInexistenteView.as_view(),
name='lista_materias_protocolo_inexistente'),
url(r'^sistema/inconsistencias/mandato_sem_data_inicio',
ListarMandatoSemDataInicioView.as_view(),
name='lista_mandato_sem_data_inicio'),
url(r'^sistema/inconsistencias/parlamentares_mandatos_intersecao$',
ListarParlMandatosIntersecaoView.as_view(),
name='lista_parlamentares_mandatos_intersecao'),
url(r'^sistema/inconsistencias/autores_duplicados$',
ListarAutoresDuplicadosView.as_view(),
name='lista_autores_duplicados'),
url(r'^sistema/inconsistencias/bancada_comissao_autor_externo$',
ListarBancadaComissaoAutorExternoView.as_view(),
name='lista_bancada_comissao_autor_externo'),
url(r'^sistema/inconsistencias/legislatura_infindavel$',
ListarLegislaturaInfindavelView.as_view(),
name='lista_legislatura_infindavel'),
# todos os sublinks de sistema devem vir acima deste
url(r'^sistema/$', permission_required('base.view_tabelas_auxiliares')

658
sapl/base/views.py

@ -1,17 +1,23 @@
import collections
import itertools
import datetime
import logging
import os
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Group
from django.contrib.auth.models import Group, User
from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.db.models import Count
from django.core.urlresolvers import reverse, reverse_lazy
from django.db import connection
from django.db.models import Count, Q, ProtectedError
from django.http import Http404, HttpResponseRedirect
from django.template import TemplateDoesNotExist
from django.template.loader import get_template
from django.utils import timezone
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.translation import string_concat
@ -28,12 +34,16 @@ from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Reuniao, Comissao
from sapl.crud.base import CrudAux, make_pagination
from sapl.materia.models import (Autoria, MateriaLegislativa,
from sapl.materia.models import (Autoria, MateriaLegislativa, Proposicao,
TipoMateriaLegislativa, StatusTramitacao, UnidadeTramitacao)
from sapl.norma.models import (NormaJuridica, NormaEstatisticas)
from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato
from sapl.protocoloadm.models import Protocolo
from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca)
from sapl.utils import (parlamentares_ativos,
show_results_filter_set, mail_service_configured)
SessaoPlenariaPresenca, Bancada)
from sapl.utils import (parlamentares_ativos, gerar_hash_arquivo, SEPARADOR_HASH_PROPOSICAO,
show_results_filter_set, mail_service_configured,
intervalos_tem_intersecao,)
from .forms import (AlterarSenhaForm, CasaLegislativaForm,
ConfiguracoesAppForm, RelatorioAtasFilterSet,
@ -45,7 +55,9 @@ from .forms import (AlterarSenhaForm, CasaLegislativaForm,
RelatorioMateriasTramitacaoilterSet,
RelatorioPresencaSessaoFilterSet,
RelatorioReuniaoFilterSet, UsuarioCreateForm,
UsuarioEditForm)
UsuarioEditForm, RelatorioNormasMesFilterSet,
RelatorioNormasVigenciaFilterSet,
EstatisticasAcessoNormasForm, UsuarioFilterSet)
from .models import AppConfig, CasaLegislativa
@ -276,6 +288,20 @@ class AutorCrud(CrudAux):
return url_reverse
class RelatoriosListView(TemplateView):
template_name='base/relatorios_list.html'
def get_context_data(self, **kwargs):
context = super(TemplateView, self).get_context_data(**kwargs)
estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas
if estatisticas_acesso_normas == 'S':
context['estatisticas_acesso_normas'] = True
else:
context['estatisticas_acesso_normas'] = False
return context
class RelatorioAtasView(FilterView):
model = SessaoPlenaria
filterset_class = RelatorioAtasFilterSet
@ -355,8 +381,16 @@ class RelatorioPresencaSessaoView(FilterView):
# Completa o dicionario as informacoes parlamentar/sessao/ordem
parlamentares_presencas = []
for i, p in enumerate(parlamentares_qs):
m = p.mandato_set.filter(Q(data_inicio_mandato__lte=_range[0], data_fim_mandato__gte=_range[1]) |
Q(data_inicio_mandato__lte=_range[0], data_fim_mandato__isnull=True) |
Q(data_inicio_mandato__gte=_range[0], data_fim_mandato__lte=_range[1]) |
# mandato suplente
Q(data_inicio_mandato__gte=_range[0], data_fim_mandato__lte=_range[1]))
m = m.last()
parlamentares_presencas.append({
'parlamentar': p,
'titular': m.titular if m else False,
'sessao_porc': 0,
'ordemdia_porc': 0
})
@ -744,35 +778,561 @@ class RelatorioMateriasPorAutorView(FilterView):
return context
class ListarUsuarioView(PermissionRequiredMixin, ListView):
class RelatorioNormasPublicadasMesView(FilterView):
model = NormaJuridica
filterset_class = RelatorioNormasMesFilterSet
template_name = 'base/RelatorioNormaMes_filter.html'
def get_context_data(self, **kwargs):
context = super(RelatorioNormasPublicadasMesView,
self).get_context_data(**kwargs)
context['title'] = _('Normas')
# Verifica se os campos foram preenchidos
if not self.filterset.form.is_valid():
return context
qr = self.request.GET.copy()
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
context['ano'] = self.request.GET['ano']
normas_mes = collections.OrderedDict()
meses = {1: 'Janeiro', 2: 'Fevereiro', 3:'Março', 4: 'Abril', 5: 'Maio', 6:'Junho',
7: 'Julho', 8: 'Agosto', 9:'Setembro', 10:'Outubro', 11:'Novembro', 12:'Dezembro'}
for norma in context['object_list']:
if not meses[norma.data.month] in normas_mes:
normas_mes[meses[norma.data.month]] = []
normas_mes[meses[norma.data.month]].append(norma)
context['normas_mes'] = normas_mes
quant_normas_mes = {}
for key in normas_mes.keys():
quant_normas_mes[key] = len(normas_mes[key])
context['quant_normas_mes'] = quant_normas_mes
return context
class RelatorioNormasVigenciaView(FilterView):
model = NormaJuridica
filterset_class = RelatorioNormasVigenciaFilterSet
template_name = 'base/RelatorioNormasVigencia_filter.html'
def get_filterset_kwargs(self, filterset_class):
super(RelatorioNormasVigenciaView,
self).get_filterset_kwargs(filterset_class)
kwargs = {'data': self.request.GET or None}
qs = self.get_queryset().order_by('data').distinct()
if kwargs['data']:
ano = kwargs['data']['ano']
vigencia = kwargs['data']['vigencia']
if ano:
qs = qs.filter(ano=ano)
if vigencia == 'True':
qs_dt_not_null = qs.filter(data_vigencia__isnull=True)
qs = (qs_dt_not_null | qs.filter(data_vigencia__gte=datetime.datetime.now().date())).distinct()
else:
qs = qs.filter(data_vigencia__lt=datetime.datetime.now().date())
kwargs.update({
'queryset': qs
})
return kwargs
def get_context_data(self, **kwargs):
context = super(RelatorioNormasVigenciaView,
self).get_context_data(**kwargs)
context['title'] = _('Normas por vigência')
# Verifica se os campos foram preenchidos
if not self.filterset.form.is_valid():
return context
normas_totais = NormaJuridica.objects.filter(ano=self.request.GET['ano'])
context['quant_total'] = len(normas_totais)
if self.request.GET['vigencia'] == 'True':
context['vigencia'] = 'Vigente'
context['quant_vigente'] = len(context['object_list'])
context['quant_nao_vigente'] = context['quant_total'] - context['quant_vigente']
else:
context['vigencia'] = 'Não vigente'
context['quant_nao_vigente'] = len(context['object_list'])
context['quant_vigente'] = context['quant_total'] - context['quant_nao_vigente']
qr = self.request.GET.copy()
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
context['ano'] = self.request.GET['ano']
return context
class EstatisticasAcessoNormas(TemplateView):
template_name = 'base/EstatisticasAcessoNormas_filter.html'
def get(self, request, *args, **kwargs):
context = super(EstatisticasAcessoNormas,
self).get_context_data(**kwargs)
context['title'] = _('Normas')
form = EstatisticasAcessoNormasForm(request.GET or None)
context['form'] = form
if not form.is_valid():
return self.render_to_response(context)
context['ano'] = self.request.GET['ano']
query = '''
select norma_id, ano, extract(month from horario_acesso) as mes, count(*)
from norma_normaestatisticas
where ano = {}
group by mes, ano, norma_id
order by mes desc;
'''.format(context['ano'])
cursor = connection.cursor()
cursor.execute(query)
rows = cursor.fetchall()
normas_mes = collections.OrderedDict()
meses = {1: 'Janeiro', 2: 'Fevereiro', 3:'Março', 4: 'Abril', 5: 'Maio', 6:'Junho',
7: 'Julho', 8: 'Agosto', 9:'Setembro', 10:'Outubro', 11:'Novembro', 12:'Dezembro'}
for row in rows:
if not meses[int(row[2])] in normas_mes:
normas_mes[meses[int(row[2])]] = []
norma_est = [NormaJuridica.objects.get(id=row[0]), row[3]]
normas_mes[meses[int(row[2])]].append(norma_est)
# Ordena por acesso e limita em 5
for n in normas_mes:
sorted_by_value = sorted(normas_mes[n], key=lambda kv: kv[1], reverse=True)
normas_mes[n] = sorted_by_value[0:5]
context['normas_mes'] = normas_mes
return self.render_to_response(context)
class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/lista_inconsistencias.html'
context_object_name = 'tabela_inconsistencias'
permission_required = ('base.list_appconfig',)
def get_queryset(self):
tabela = []
tabela.append(
('protocolos_duplicados',
'Protocolos duplicados',
len(protocolos_duplicados())
)
)
tabela.append(
('protocolos_com_materias',
'Protocolos que excedem o limite de matérias vinculadas',
len(protocolos_com_materias())
)
)
tabela.append(
('materias_protocolo_inexistente',
'Matérias Legislativas com protocolo inexistente',
len(materias_protocolo_inexistente())
)
)
tabela.append(
('mandato_sem_data_inicio',
'Mandatos sem data inicial',
len(mandato_sem_data_inicio())
)
)
tabela.append(
('parlamentares_mandatos_intersecao',
'Parlamentares com mandatos com interseção',
len(parlamentares_mandatos_intersecao())
)
)
tabela.append(
('autores_duplicados',
'Autores duplicados',
len(autores_duplicados())
)
)
tabela.append(
('bancada_comissao_autor_externo',
'Bancadas e Comissões com autor externo',
len(bancada_comissao_autor_externo())
)
)
tabela.append(
('legislatura_infindavel',
'Legislaturas sem data fim',
len(legislatura_infindavel())
)
)
return tabela
def legislatura_infindavel():
return Legislatura.objects.filter(data_fim__isnull=True).order_by('-numero')
class ListarLegislaturaInfindavelView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/legislatura_infindavel.html'
context_object_name = 'legislatura_infindavel'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return legislatura_infindavel()
def get_context_data(self, **kwargs):
context = super(
ListarLegislaturaInfindavelView, self
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhuma encontrada.'
return context
def bancada_comissao_autor_externo():
tipo_autor_externo = TipoAutor.objects.filter(descricao='Externo')
lista_bancada_autor_externo = []
for bancada in Bancada.objects.all().order_by('nome'):
autor_externo = bancada.autor.filter(tipo=tipo_autor_externo)
if autor_externo:
q_autor_externo = bancada.autor.get(tipo=tipo_autor_externo)
lista_bancada_autor_externo.append(
(q_autor_externo, bancada, 'Bancada', 'sistema/bancada')
)
lista_comissao_autor_externo = []
for comissao in Comissao.objects.all().order_by('nome'):
autor_externo = comissao.autor.filter(tipo=tipo_autor_externo)
if autor_externo:
q_autor_externo = comissao.autor.get(tipo=tipo_autor_externo)
lista_comissao_autor_externo.append(
(q_autor_externo, comissao, 'Comissão', 'comissao')
)
return lista_bancada_autor_externo + lista_comissao_autor_externo
class ListarBancadaComissaoAutorExternoView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/bancada_comissao_autor_externo.html'
context_object_name = 'bancada_comissao_autor_externo'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return bancada_comissao_autor_externo()
def get_context_data(self, **kwargs):
context = super(
ListarBancadaComissaoAutorExternoView, self
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
return context
def autores_duplicados():
return [autor.values() for autor in Autor.objects.values('nome', 'tipo__descricao').annotate(count=Count('nome')).filter(count__gt=1)]
class ListarAutoresDuplicadosView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/autores_duplicados.html'
context_object_name = 'autores_duplicados'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return autores_duplicados()
def get_context_data(self, **kwargs):
context = super(
ListarAutoresDuplicadosView, self).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
return context
def parlamentares_mandatos_intersecao():
intersecoes = []
for parlamentar in Parlamentar.objects.all().order_by('nome_completo'):
mandatos = parlamentar.mandato_set.all()
combinacoes = itertools.combinations(mandatos, 2)
for c in combinacoes:
data_inicio_mandato1 = c[0].data_inicio_mandato
data_fim_mandato1 = c[0].data_fim_mandato if c[0].data_fim_mandato else timezone.now().date()
data_inicio_mandato2 = c[1].data_inicio_mandato
data_fim_mandato2 = c[1].data_fim_mandato if c[1].data_fim_mandato else timezone.now().date()
if data_inicio_mandato1 and data_inicio_mandato2:
exists = intervalos_tem_intersecao(
data_inicio_mandato1, data_fim_mandato1,
data_inicio_mandato2, data_fim_mandato2)
if exists:
intersecoes.append((parlamentar, c[0], c[1]))
return intersecoes
class ListarParlMandatosIntersecaoView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/parlamentares_mandatos_intersecao.html'
context_object_name = 'parlamentares_mandatos_intersecao'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return parlamentares_mandatos_intersecao()
def get_context_data(self, **kwargs):
context = super(
ListarParlMandatosIntersecaoView, self).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
return context
def mandato_sem_data_inicio():
return Mandato.objects.filter(data_inicio_mandato__isnull=True).order_by('parlamentar')
class ListarMandatoSemDataInicioView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/mandato_sem_data_inicio.html'
context_object_name = 'mandato_sem_data_inicio'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return mandato_sem_data_inicio()
def get_context_data(self, **kwargs):
context = super(
ListarMandatoSemDataInicioView, self
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrada.'
return context
def materias_protocolo_inexistente():
materias = []
for materia in MateriaLegislativa.objects.filter(numero_protocolo__isnull=False).order_by('-ano', 'numero'):
exists = Protocolo.objects.filter(
ano=materia.ano, numero=materia.numero_protocolo).exists()
if not exists:
materias.append(
(materia, materia.ano, materia.numero_protocolo))
return materias
class ListarMatProtocoloInexistenteView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'auth/user_list.html'
context_object_name = 'user_list'
template_name = 'base/materias_protocolo_inexistente.html'
context_object_name = 'materias_protocolo_inexistente'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
qs = super(ListarUsuarioView, self).get_queryset()
return qs.order_by('username')
return materias_protocolo_inexistente()
def get_context_data(self, **kwargs):
context = super(ListarUsuarioView, self).get_context_data(**kwargs)
context = super(
ListarMatProtocoloInexistenteView, self
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context['NO_ENTRIES_MSG'] = 'Nenhum usuário cadastrado.'
context[
'NO_ENTRIES_MSG'
] = 'Nenhuma encontrada.'
return context
def protocolos_com_materias():
protocolos = {}
for m in MateriaLegislativa.objects.filter(numero_protocolo__isnull=False).order_by('-ano', 'numero_protocolo'):
if Protocolo.objects.filter(numero=m.numero_protocolo, ano=m.ano).exists():
key = "{}/{}".format(m.numero_protocolo, m.ano)
val = protocolos.get(key, list())
val.append(m)
protocolos[key] = val
return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1]
class ListarProtocolosComMateriasView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/protocolos_com_materias.html'
context_object_name = 'protocolos_com_materias'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return protocolos_com_materias()
def get_context_data(self, **kwargs):
context = super(
ListarProtocolosComMateriasView, self).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
return context
def protocolos_duplicados():
protocolos = {}
for p in Protocolo.objects.order_by('-ano', 'numero'):
key = "{}/{}".format(p.numero, p.ano)
val = protocolos.get(key, list())
val.append(p)
protocolos[key] = val
return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1]
class ListarProtocolosDuplicadosView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/protocolos_duplicados.html'
context_object_name = 'protocolos_duplicados'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return protocolos_duplicados()
def get_context_data(self, **kwargs):
context = super(
ListarProtocolosDuplicadosView, self).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
return context
class PesquisarUsuarioView(PermissionRequiredMixin, FilterView):
model = User
filterset_class = UsuarioFilterSet
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_filterset_kwargs(self, filterset_class):
super(PesquisarUsuarioView,
self).get_filterset_kwargs(filterset_class)
kwargs = {'data': self.request.GET or None}
qs = self.get_queryset().order_by('username').distinct()
kwargs.update({
'queryset': qs,
})
return kwargs
def get_context_data(self, **kwargs):
context = super(PesquisarUsuarioView,
self).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context['NO_ENTRIES_MSG'] = 'Nenhum usuário encontrado!'
context['title'] = _('Usuários')
return context
def get(self, request, *args, **kwargs):
super(PesquisarUsuarioView, self).get(request)
data = self.filterset.data
url = ''
if data:
url = "&" + str(self.request.environ['QUERY_STRING'])
if url.startswith("&page"):
ponto_comeco = url.find('username=') - 1
url = url[ponto_comeco:]
context = self.get_context_data(filter=self.filterset,
object_list=self.object_list,
filter_url=url,
numero_res=len(self.object_list)
)
context['show_results'] = show_results_filter_set(
self.request.GET.copy())
return self.render_to_response(context)
class CreateUsuarioView(PermissionRequiredMixin, CreateView):
model = get_user_model()
form_class = UsuarioCreateForm
success_message = 'Usuário criado com sucesso'
success_message = 'Usuário criado com sucesso!'
permission_required = ('base.add_appconfig',)
def get_success_url(self):
return reverse('sapl.base:user_list')
return reverse('sapl.base:usuario')
def form_valid(self, form):
@ -791,33 +1351,49 @@ class CreateUsuarioView(PermissionRequiredMixin, CreateView):
for g in groups:
g.user_set.add(new_user)
messages.success(self.request, self.success_message)
return HttpResponseRedirect(self.get_success_url())
class DeleteUsuarioView(PermissionRequiredMixin, DeleteView):
model = get_user_model()
template_name = "crud/confirm_delete.html"
permission_required = ('base.delete_appconfig',)
success_url = reverse_lazy('sapl.base:usuario')
success_message = "Usuário removido com sucesso!"
def get_success_url(self):
return reverse('sapl.base:user_list')
def get(self, request, *args, **kwargs):
return self.post(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
try:
super(DeleteUsuarioView, self).delete(request, *args, **kwargs)
except ProtectedError as exception:
error_url = reverse_lazy('sapl.base:user_delete', kwargs={'pk': self.kwargs['pk']})
error_message = "O usuário não pode ser removido, pois é referenciado por:<br><ul>"
for e in exception.protected_objects:
error_message += '<li>{} - {}</li>'.format(
e._meta.verbose_name, e
)
error_message += '</ul>'
messages.error(self.request, error_message)
return HttpResponseRedirect(error_url)
messages.success(self.request, self.success_message)
return HttpResponseRedirect(self.success_url)
def get_queryset(self):
qs = super(DeleteUsuarioView, self).get_queryset()
return qs.filter(id=self.kwargs['pk'])
@property
def cancel_url(self):
return reverse('sapl.base:user_edit',
kwargs={'pk': self.kwargs['pk']})
class EditUsuarioView(PermissionRequiredMixin, UpdateView):
model = get_user_model()
form_class = UsuarioEditForm
success_message = 'Usuário editado com sucesso'
success_message = 'Usuário editado com sucesso!'
permission_required = ('base.change_appconfig',)
def get_success_url(self):
return reverse('sapl.base:user_list')
return reverse('sapl.base:usuario')
def get_initial(self):
initial = super(EditUsuarioView, self).get_initial()
@ -854,6 +1430,7 @@ class EditUsuarioView(PermissionRequiredMixin, UpdateView):
for g in groups:
g.user_set.add(user)
messages.success(self.request, self.success_message)
return super(EditUsuarioView, self).form_valid(form)
@ -907,10 +1484,31 @@ class AppConfigCrud(CrudAux):
class BaseMixin(CrudAux.BaseMixin):
form_class = ConfiguracoesAppForm
list_url = ''
create_url = ''
def form_valid(self, form):
recibo_prop_atual = AppConfig.objects.last().receber_recibo_proposicao
recibo_prop_novo = self.request.POST['receber_recibo_proposicao']
if recibo_prop_novo == 'False' and recibo_prop_atual:
props = Proposicao.objects.filter(hash_code='')
for prop in props:
self.gerar_hash(prop)
return super().form_valid(form)
def gerar_hash(self, inst):
inst.save()
if inst.texto_original:
try:
inst.hash_code = gerar_hash_arquivo(
inst.texto_original.path, str(inst.pk))
except IOError:
raise ValidationError("Existem proposicoes com arquivos inexistentes.")
elif inst.texto_articulado.exists():
ta = inst.texto_articulado.first()
inst.hash_code = 'P' + ta.hash() + SEPARADOR_HASH_PROPOSICAO + str(inst.pk)
inst.save()
class CreateView(CrudAux.CreateView):
def get(self, request, *args, **kwargs):

43
sapl/comissoes/forms.py

@ -12,11 +12,15 @@ from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import (Comissao, Composicao, DocumentoAcessorio,
Participacao, Reuniao, Periodo)
from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar
from sapl.utils import FileFieldCheckMixin
class ComposicaoForm(forms.ModelForm):
comissao = forms.CharField(required=False, label='Comissao', widget=forms.HiddenInput())
comissao = forms.CharField(
required=False, label='Comissao', widget=forms.HiddenInput())
logger = logging.getLogger(__name__)
class Meta:
model = Composicao
exclude = []
@ -55,6 +59,7 @@ class ComposicaoForm(forms.ModelForm):
class PeriodoForm(forms.ModelForm):
logger = logging.getLogger(__name__)
class Meta:
model = Periodo
exclude = []
@ -90,11 +95,9 @@ class PeriodoForm(forms.ModelForm):
'deve estar contido em uma única '
'legislatura existente')
return cleaned_data
class ParticipacaoCreateForm(forms.ModelForm):
logger = logging.getLogger(__name__)
@ -137,7 +140,6 @@ class ParticipacaoCreateForm(forms.ModelForm):
qs = Parlamentar.objects.filter(id__in=ids)
self.fields['parlamentar'].queryset = qs
def clean(self):
cleaned_data = super(ParticipacaoCreateForm, self).clean()
@ -155,15 +157,16 @@ class ParticipacaoCreateForm(forms.ModelForm):
'à data de desligamento'))
composicao = Composicao.objects.get(id=self.initial['parent_pk'])
cargos_unicos = [c.cargo.nome for c in composicao.participacao_set.filter(cargo__unico=True)]
cargos_unicos = [
c.cargo.nome for c in composicao.participacao_set.filter(cargo__unico=True)]
if cleaned_data['cargo'].nome in cargos_unicos:
msg = _('Este cargo é único para esta Comissão.')
self.logger.error('Este cargo ({}) é único para esta Comissão.'.format(cleaned_data['cargo'].nome))
self.logger.error('Este cargo ({}) é único para esta Comissão.'.format(
cleaned_data['cargo'].nome))
raise ValidationError(msg)
return cleaned_data
def create_participacao(self):
composicao = Composicao.objects.get(id=self.initial['parent_pk'])
data_inicio_comissao = composicao.periodo.data_inicio
@ -259,6 +262,7 @@ class ParticipacaoEditForm(forms.ModelForm):
class ComissaoForm(forms.ModelForm):
logger = logging.getLogger(__name__)
class Meta:
model = Comissao
fields = '__all__'
@ -274,8 +278,6 @@ class ComissaoForm(forms.ModelForm):
self.fields['data_prorrogada_temp'].widget.attrs['disabled'] = 'disabled'
self.fields['data_fim_comissao'].widget.attrs['disabled'] = 'disabled'
def clean(self):
super(ComissaoForm, self).clean()
@ -283,15 +285,17 @@ class ComissaoForm(forms.ModelForm):
return self.cleaned_data
if len(self.cleaned_data['nome']) > 100:
msg = _('Nome da Comissão informado ({}) tem mais de 50 caracteres.'.format(self.cleaned_data['nome']))
self.logger.error('Nome da Comissão deve ter no máximo 50 caracteres.')
msg = _('Nome da Comissão informado ({}) tem mais de 50 caracteres.'.format(
self.cleaned_data['nome']))
self.logger.error(
'Nome da Comissão deve ter no máximo 50 caracteres.')
raise ValidationError(msg)
if (self.cleaned_data['data_extincao'] and
self.cleaned_data['data_extincao'] <
self.cleaned_data['data_criacao']):
msg = _('Data de extinção não pode ser menor que a de criação')
self.logger.error('Data de extinção ({}) não pode ser menor que a de criação ({}).'
.format(self.cleaned_data['data_extincao'],self.cleaned_data['data_criacao']))
.format(self.cleaned_data['data_extincao'], self.cleaned_data['data_criacao']))
raise ValidationError(msg)
if (self.cleaned_data['data_final_prevista_temp'] and
self.cleaned_data['data_final_prevista_temp'] <
@ -317,7 +321,8 @@ class ComissaoForm(forms.ModelForm):
if (self.cleaned_data['data_final_prevista_temp'] and self.cleaned_data['data_instalacao_temp'] and
self.cleaned_data['data_final_prevista_temp'] <
self.cleaned_data['data_instalacao_temp']):
msg = _('Data Prevista para Término não pode ser menor que a de Instalação.')
msg = _(
'Data Prevista para Término não pode ser menor que a de Instalação.')
self.logger.error('Data Prevista para Término ({}) não pode ser menor que a de Instalação ({}).'
.format(self.cleaned_data['data_final_prevista_temp'], self.cleaned_data['data_instalacao_temp']))
raise ValidationError(msg)
@ -337,7 +342,7 @@ class ComissaoForm(forms.ModelForm):
comissao = super(ComissaoForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Comissao)
object_id = comissao.pk
tipo = TipoAutor.objects.get(descricao__icontains='Comiss')
tipo = TipoAutor.objects.get(content_type=content_type)
nome = comissao.sigla + ' - ' + comissao.nome
Autor.objects.create(
content_type=content_type,
@ -364,20 +369,21 @@ class ReuniaoForm(ModelForm):
def clean(self):
super(ReuniaoForm, self).clean()
if not self.is_valid():
return self.cleaned_data
if self.cleaned_data['hora_fim']:
if (self.cleaned_data['hora_fim'] <
self.cleaned_data['hora_inicio']):
msg = _('A hora de término da reunião não pode ser menor que a de início')
msg = _(
'A hora de término da reunião não pode ser menor que a de início')
self.logger.error("A hora de término da reunião ({}) não pode ser menor que a de início ({})."
.format(self.cleaned_data['hora_fim'], self.cleaned_data['hora_inicio']))
raise ValidationError(msg)
return self.cleaned_data
class DocumentoAcessorioCreateForm(forms.ModelForm):
class DocumentoAcessorioCreateForm(FileFieldCheckMixin, forms.ModelForm):
parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput())
@ -395,12 +401,11 @@ class DocumentoAcessorioCreateForm(forms.ModelForm):
documentos = reuniao.documentoacessorio_set.all()
return self.create_documentoacessorio()
def create_documentoacessorio(self):
reuniao = Reuniao.objects.get(id=self.initial['parent_pk'])
class DocumentoAcessorioEditForm(forms.ModelForm):
class DocumentoAcessorioEditForm(FileFieldCheckMixin, forms.ModelForm):
parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput())

20
sapl/comissoes/migrations/0019_auto_20181214_1023.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-12-14 12:23
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comissoes', '0018_auto_20180924_1724'),
]
operations = [
migrations.AlterField(
model_name='reuniao',
name='hora_fim',
field=models.TimeField(blank=True, null=True, verbose_name='Horário de Término (hh:mm)'),
),
]

1
sapl/comissoes/models.py

@ -221,6 +221,7 @@ class Reuniao(models.Model):
null=True,
verbose_name=_('Horário de Início (hh:mm)'))
hora_fim = models.TimeField(
blank=True,
null=True,
verbose_name=_('Horário de Término (hh:mm)'))
local_reuniao = models.CharField(

3
sapl/comissoes/tests/test_comissoes.py

@ -139,7 +139,6 @@ def test_valida_campos_obrigatorios_reuniao_form():
assert errors['nome'] == [_('Este campo é obrigatório.')]
assert errors['data'] == [_('Este campo é obrigatório.')]
assert errors['hora_inicio'] == [_('Este campo é obrigatório.')]
assert errors['hora_fim'] == [_('Este campo é obrigatório.')]
assert len(errors) == 7
assert len(errors) == 6

99
sapl/compilacao/forms.py

@ -3,7 +3,7 @@ from datetime import timedelta
from crispy_forms.bootstrap import (Alert, FieldWithButtons, FormActions,
InlineCheckboxes, InlineRadios,
StrictButton)
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset,
Layout, Row, Submit)
from django import forms
@ -23,7 +23,8 @@ from sapl.compilacao.models import (NOTAS_PUBLICIDADE_CHOICES,
TipoTextoArticulado, TipoVide,
VeiculoPublicacao, Vide)
from sapl.compilacao.utils import DISPOSITIVO_SELECT_RELATED
from sapl.crispy_layout_mixin import SaplFormLayout, to_column, to_row
from sapl.crispy_layout_mixin import SaplFormLayout, to_column, to_row,\
form_actions
from sapl.utils import YES_NO_CHOICES
error_messages = {
@ -83,7 +84,7 @@ class TipoTaForm(ModelForm):
('perfis', 12),
])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'),
row1, css_class="col-md-12"),
@ -152,7 +153,7 @@ class TaForm(ModelForm):
('participacao_social', 3),
])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'), row1, css_class="col-md-12"),
Fieldset(
@ -251,22 +252,27 @@ class NotaForm(ModelForm):
('publicidade', 6),
('publicacao', 3),
('efetividade', 3),
('dispositivo', 0),
('pk', 0),
])
buttons = FormActions(
HTML('<a class="btn btn-inverse btn-close-container">'
'%s</a>' % _('Cancelar')),
*[
HTML('<a href="" class="btn btn-dark '
'btn-close-container">%s</a>' % _('Cancelar'))
],
Button(
'submit-form',
'Salvar',
css_class='btn btn-primary pull-right')
css_class='btn btn-primary float-right'),
css_class='form-group row justify-content-between mr-1 ml-1'
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Div(
Div(HTML(_('Notas')), css_class='panel-heading'),
Div(HTML(_('Notas')), css_class='card-header bg-light'),
Div(
row1,
to_row([(Field(
@ -277,9 +283,9 @@ class NotaForm(ModelForm):
placeholder=_('URL Externa (opcional)')), 12)]),
row3,
to_row([(buttons, 12)]),
css_class="panel-body"
css_class="card-body"
),
css_class="panel panel-primary"
css_class="card"
)
)
@ -326,12 +332,15 @@ class VideForm(ModelForm):
def __init__(self, *args, **kwargs):
buttons = FormActions(
HTML('<a class="btn btn-inverse btn-close-container">'
'%s</a>' % _('Cancelar')),
*[
HTML('<a href="" class="btn btn-dark '
'btn-close-container">%s</a>' % _('Cancelar'))
],
Button(
'submit-form',
'Salvar',
css_class='btn-primary pull-right')
css_class='btn btn-primary float-right'),
css_class='form-group row justify-content-between mr-1 ml-1'
)
dispositivo_ref = Field(
@ -354,16 +363,18 @@ class VideForm(ModelForm):
'texto',
placeholder=_('Texto Adicional ao Vide')), 12)))))
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Div(
Div(HTML(_('Vides')), css_class='panel-heading'),
Div(HTML(_('Vides')), css_class='card-header bg-light'),
Div(
to_column((fields_form[0], 6)),
to_column((fields_form[1], 6)),
css_class="panel-body"
to_column(('dispositivo_base', 0)),
to_column(('pk', 0)),
css_class="card-body row"
),
css_class="panel panel-primary"
css_class="card"
)
)
@ -460,7 +471,7 @@ class PublicacaoForm(ModelForm):
('url_externa', 8),
])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(Publicacao._meta.verbose_name,
row1, row2, row3, css_class="col-md-12"))
@ -648,7 +659,7 @@ class DispositivoEdicaoBasicaForm(ModelForm):
for f in fields:
self.base_fields.update({f: getattr(self, f)})
self.helper = FormHelper()
self.helper = SaplFormHelper()
if not editor_type:
cancel_label = _('Ir para o Editor Sequencial')
@ -667,7 +678,7 @@ class DispositivoEdicaoBasicaForm(ModelForm):
cancel_label = _('Fechar')
more = [
HTML('<a class="btn btn-inverse btn-fechar">%s</a>' %
HTML('<a class="btn btn-dark btn-fechar" href="">%s</a>' %
cancel_label),
]
@ -676,7 +687,7 @@ class DispositivoEdicaoBasicaForm(ModelForm):
if not (inst.tipo_dispositivo.dispositivo_de_alteracao and
inst.tipo_dispositivo.dispositivo_de_articulacao):
btns_excluir = [
HTML('<a class="btn btn-danger btn-excluir" '
HTML('<a class="btn btn-danger btn-outline-danger" '
'action="json_delete_item_dispositivo" '
'title="%s" '
'pk="%s" '
@ -692,7 +703,7 @@ class DispositivoEdicaoBasicaForm(ModelForm):
if texto_articulado_do_editor else 0):
btns_excluir.append(
HTML(
'<a class="btn btn-danger btn-excluir" '
'<a class="btn btn-danger btn-outline-danger" '
'action="json_delete_bloco_dispositivo" '
'title="%s" '
'pk="%s" '
@ -702,15 +713,15 @@ class DispositivoEdicaoBasicaForm(ModelForm):
_('Excluir Bloco Completo.'))))
if btns_excluir and (not inst.auto_inserido or inst.ta_publicado):
css_class = 'btn-group pull-right btns-excluir'
css_class = 'btn-group float-right btns-excluir'
more.append(Div(*btns_excluir, css_class=css_class))
if not inst.tipo_dispositivo.dispositivo_de_articulacao:
more.append(Submit('salvar', _('Salvar'), css_class='pull-right'))
more.append(Submit('salvar', _('Salvar'), css_class='float-right'))
buttons = FormActions(*more, css_class='form-group')
_fields = [Div(*layout, css_class="row-fluid")] + \
_fields = [Div(*layout, css_class="row")] + \
[to_row([(buttons, 12)])]
self.helper.layout = Layout(*_fields)
@ -779,7 +790,7 @@ class DispositivoSearchModalForm(Form):
)
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
fields_search,
Row(to_column((Div(css_class='result-busca-dispositivo'), 12))))
@ -892,7 +903,7 @@ class DispositivoEdicaoVigenciaForm(ModelForm):
row_vigencia,
css_class="col-md-12"))
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
*layout,
cancel_label=_('Ir para o Editor Sequencial'))
@ -1012,7 +1023,7 @@ class DispositivoDefinidorVigenciaForm(Form):
row_vigencia,
css_class="col-md-12"))
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
*layout,
cancel_label=_('Ir para o Editor Sequencial'))
@ -1151,7 +1162,7 @@ class DispositivoEdicaoAlteracaoForm(ModelForm):
if hasattr(self, f):
self.base_fields.update({f: getattr(self, f)})
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
*layout,
cancel_label=_('Ir para o Editor Sequencial'))
@ -1315,9 +1326,9 @@ class TextNotificacoesForm(Form):
field_type_notificacoes = to_row([(InlineCheckboxes(
'type_notificacoes'), 10),
(Submit('submit-form', _('Filtrar'),
css_class='btn btn-primary pull-right'), 2)])
css_class='btn btn-primary float-right'), 2)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(field_type_notificacoes)
super(TextNotificacoesForm, self).__init__(*args, **kwargs)
@ -1353,17 +1364,17 @@ class DispositivoRegistroAlteracaoForm(Form):
layout.append(Field('dispositivo_search_form'))
more = [
HTML('<a class="btn btn-inverse btn-fechar">%s</a>' %
HTML('<a class="btn btn-dark btn-fechar" href="">%s</a>' %
_('Cancelar')),
]
more.append(Submit('salvar', _('Salvar'), css_class='pull-right'))
more.append(Submit('salvar', _('Salvar'), css_class='float-right'))
buttons = FormActions(*more, css_class='form-group')
_fields = [Div(*layout, css_class="row-fluid")] + \
_fields = [Div(*layout, css_class="row")] + \
[to_row([(buttons, 12)])]
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(*_fields)
super(DispositivoRegistroAlteracaoForm, self).__init__(*args, **kwargs)
@ -1410,17 +1421,17 @@ class DispositivoRegistroRevogacaoForm(Form):
layout.append(Field('dispositivo_search_form'))
more = [
HTML('<a class="btn btn-inverse btn-fechar">%s</a>' %
HTML('<a class="btn btn-dark btn-fechar" href="">%s</a>' %
_('Cancelar')),
]
more.append(Submit('salvar', _('Salvar'), css_class='pull-right'))
more.append(Submit('salvar', _('Salvar'), css_class='float-right'))
buttons = FormActions(*more, css_class='form-group')
_fields = [Div(*layout, css_class="row-fluid")] + \
_fields = [Div(*layout, css_class="row")] + \
[to_row([(buttons, 12)])]
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(*_fields)
super(DispositivoRegistroRevogacaoForm, self).__init__(*args, **kwargs)
@ -1460,17 +1471,17 @@ class DispositivoRegistroInclusaoForm(Form):
layout.append(Div(css_class="allowed_inserts col-md-12"))
more = [
HTML('<a class="btn btn-inverse btn-fechar">%s</a>' %
HTML('<a class="btn btn-dark btn-fechar" href="">%s</a>' %
_('Cancelar')),
]
# more.append(Submit('salvar', _('Salvar'), css_class='pull-right'))
# more.append(Submit('salvar', _('Salvar'), css_class='float-right'))
buttons = FormActions(*more, css_class='form-group')
_fields = [Div(*layout, css_class="row-fluid")] + \
_fields = [Div(*layout, css_class="row")] + \
[to_row([(buttons, 12)])]
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(*_fields)
super(DispositivoRegistroInclusaoForm, self).__init__(*args, **kwargs)

10
sapl/compilacao/models.py

@ -1,4 +1,5 @@
from bs4 import BeautifulSoup
from django.contrib import messages
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
@ -1105,6 +1106,15 @@ class Dispositivo(BaseModel, TimestampedMixin):
self.contagem_continua = self.tipo_dispositivo.contagem_continua
try:
if self.texto:
self.texto = str(BeautifulSoup(self.texto, "html.parser"))
if self.texto_atualizador:
self.texto_atualizador = str(BeautifulSoup(
self.texto_atualizador, "html.parser"))
except:
pass
return super().save(
force_insert=force_insert, force_update=force_update, using=using,
update_fields=update_fields, clean=clean)

3
sapl/compilacao/templatetags/compilacao_filters.py

@ -83,6 +83,9 @@ def nota_automatica(dispositivo, ta_pub_list):
if dispositivo.ta_publicado:
d = dispositivo.dispositivo_atualizador.dispositivo_pai
if d.auto_inserido:
d = d.dispositivo_pai
ta_publicado = ta_pub_list[dispositivo.ta_publicado_id] if\
ta_pub_list else dispositivo.ta_publicado

16
sapl/compilacao/urls.py

@ -3,7 +3,8 @@ from django.conf.urls import include, url
from sapl.compilacao import views
from sapl.compilacao.views import (TipoDispositivoCrud, TipoNotaCrud,
TipoPublicacaoCrud, TipoVideCrud,
VeiculoPublicacaoCrud)
VeiculoPublicacaoCrud,
TipoTextoArticuladoCrud)
from .apps import AppConfig
@ -113,14 +114,7 @@ urlpatterns = [
include(TipoPublicacaoCrud.get_urls())),
url(r'^sistema/ta/config/veiculo-publicacao/',
include(VeiculoPublicacaoCrud.get_urls())),
url(r'^sistema/ta/config/tipo-textoarticulado$',
views.TipoTaListView.as_view(), name='tipo_ta_list'),
url(r'^sistema/ta/config/tipo-textoarticulado/create$',
views.TipoTaCreateView.as_view(), name='tipo_ta_create'),
url(r'^sistema/ta/config/tipo-textoarticulado/(?P<pk>[0-9]+)$',
views.TipoTaDetailView.as_view(), name='tipo_ta_detail'),
url(r'^sistema/ta/config/tipo-textoarticulado/(?P<pk>[0-9]+)/edit$',
views.TipoTaUpdateView.as_view(), name='tipo_ta_edit'),
url(r'^sistema/ta/config/tipo-textoarticulado/(?P<pk>[0-9]+)/delete$',
views.TipoTaDeleteView.as_view(), name='tipo_ta_delete'),
url(r'^sistema/ta/config/tipo/',
include(TipoTextoArticuladoCrud.get_urls())),
]

76
sapl/compilacao/views.py

@ -4,6 +4,7 @@ import logging
import sys
from braces.views import FormMessagesMixin
from bs4 import BeautifulSoup
from django import forms
from django.conf import settings
from django.contrib import messages
@ -49,7 +50,8 @@ from sapl.compilacao.models import (STATUS_TA_EDITION, STATUS_TA_PRIVATE,
from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED,
DISPOSITIVO_SELECT_RELATED_EDIT,
get_integrations_view_names)
from sapl.crud.base import Crud, CrudAux, CrudListView, make_pagination
from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, CrudListView,\
make_pagination
from sapl.settings import BASE_DIR
@ -430,28 +432,12 @@ class CompMixin(PermissionRequiredMixin):
return rr
class TipoTaListView(CompMixin, ListView):
class TipoTextoArticuladoCrud(CrudAux):
model = TipoTextoArticulado
paginate_by = 10
verbose_name = model._meta.verbose_name
permission_required = 'compilacao.list_tipotextoarticulado'
@property
def title(self):
return self.model._meta.verbose_name_plural
public = [RP_LIST, RP_DETAIL, ]
@property
def create_url(self):
return reverse_lazy('sapl.compilacao:tipo_ta_create')
class TipoTaCreateView(CompMixin, FormMessagesMixin, CreateView):
model = TipoTextoArticulado
class CreateView(CrudAux.CreateView):
form_class = TipoTaForm
template_name = "crud/form.html"
form_valid_message = _('Registro criado com sucesso!')
form_invalid_message = _('O registro não foi criado.')
permission_required = 'compilacao.add_tipotextoarticulado'
def get(self, request, *args, **kwargs):
self.object = None
@ -462,25 +448,8 @@ class TipoTaCreateView(CompMixin, FormMessagesMixin, CreateView):
return self.render_to_response(self.get_context_data(form=form))
def get_success_url(self):
return reverse_lazy('sapl.compilacao:tipo_ta_detail',
kwargs={'pk': self.object.id})
@property
def cancel_url(self):
return reverse_lazy('sapl.compilacao:tipo_ta_list')
class TipoTaDetailView(CompMixin, DetailView):
model = TipoTextoArticulado
permission_required = 'compilacao.detail_tipotextoarticulado'
class TipoTaUpdateView(CompMixin, UpdateView):
model = TipoTextoArticulado
class UpdateView(CrudAux.UpdateView):
form_class = TipoTaForm
template_name = "crud/form.html"
permission_required = 'compilacao.change_tipotextoarticulado'
def get(self, request, *args, **kwargs):
self.object = self.get_object()
@ -490,29 +459,6 @@ class TipoTaUpdateView(CompMixin, UpdateView):
label=_('Modelo Integrado'), required=False)
return self.render_to_response(self.get_context_data(form=form))
def get_success_url(self):
return reverse_lazy('sapl.compilacao:tipo_ta_detail',
kwargs={'pk': self.kwargs['pk']})
@property
def cancel_url(self):
return reverse_lazy('sapl.compilacao:tipo_ta_detail',
kwargs={'pk': self.kwargs['pk']})
class TipoTaDeleteView(CompMixin, DeleteView):
model = TipoTextoArticulado
template_name = "crud/confirm_delete.html"
permission_required = 'compilacao.delete_tipotextoarticulado'
@property
def detail_url(self):
return reverse_lazy('sapl.compilacao:tipo_ta_detail',
kwargs={'pk': self.kwargs['pk']})
def get_success_url(self):
return reverse_lazy('sapl.compilacao:tipo_ta_list')
class TaListView(CompMixin, ListView):
model = TextoArticulado
@ -1319,6 +1265,9 @@ class TextEditView(CompMixin, TemplateView):
if dispositivo.ta_publicado_id:
d = dispositivo.dispositivo_atualizador.dispositivo_pai
if d.auto_inserido:
d = d.dispositivo_pai
ta_publicado = lista_ta_publicado[dispositivo.ta_publicado_id] if\
lista_ta_publicado else dispositivo.ta_publicado
@ -2937,13 +2886,10 @@ class DispositivoDinamicEditView(
if texto != texto_atualizador else ''
visibilidade = request.POST['visibilidade']
# if d.texto != '':
# d.texto = texto
# d.save()
# return self.get(request, *args, **kwargs)
d_texto = d.texto
d.texto = texto.strip()
d.texto_atualizador = texto_atualizador.strip()
d.visibilidade = not visibilidade or visibilidade == 'True'
d.save()

49
sapl/crispy_layout_mixin.py

@ -1,6 +1,5 @@
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
@ -8,6 +7,7 @@ from django import template
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils import formats
from django.utils.translation import ugettext as _
import rtyaml
def heads_and_tails(list_of_lists):
@ -21,7 +21,7 @@ def to_column(name_span):
def to_row(names_spans):
return Div(*map(to_column, names_spans), css_class='row-fluid')
return Div(*map(to_column, names_spans), css_class='row')
def to_fieldsets(fields):
@ -35,7 +35,8 @@ def to_fieldsets(fields):
def form_actions(more=[Div(css_class='clearfix')],
label=_('Salvar'), name='salvar', css_class='pull-right', disabled=True):
label=_('Salvar'), name='salvar',
css_class='float-right', disabled=True):
if disabled:
doubleclick = 'this.form.submit();this.disabled=true;'
@ -43,10 +44,41 @@ def form_actions(more=[Div(css_class='clearfix')],
doubleclick = 'return true;'
return FormActions(
*more,
Submit(name, label, css_class=css_class,
# para impedir resubmissão do form
onclick=doubleclick),
*more)
css_class='form-group row justify-content-between'
)
class SaplFormHelper(FormHelper):
render_hidden_fields = True # default = False
"""
até a release 1.6.1 do django-crispy-forms, os fields em Meta.Fields eram
renderizados mesmo se não mencionados no helper.
Com esta mudança (https://github.com/django-crispy-forms/django-crispy-forms/commit/6b93e8a362422db8fe54aa731319c7cbc39990ba)
render_hidden_fields foi adicionado uma condição em que a cada
instância do Helper, fosse decidido se os fields não mencionados serião ou
não renderizados...
O Sapl até este commit: https://github.com/interlegis/sapl/commit/22b87f36ebc8659a6ecaf8831ab0f425206b0993
utilizou o django-crispy-forms na versão 1.6.1, ou seja,
sem a condição render_hidden_fields o que fazia o FormHelper, na 1.6.1
set comportar como se, agora, na 1.7.2 o default fosse True.
Como todos os Forms do Sapl foram construídos assumindo que fields
não incluídos explicitamente no Helper, o helper o incluiria implicitamente,
e assim o era, de acordo com commit acima do django-crispy-forms, então
cria-se essa classe:
class SaplFormHelper(FormHelper):
render_hidden_fields = True
onde torna o default, antes False, agora = True, o esperado pelos forms do sapl,
e substituí-se todos os FormHelper por SaplFormHelper dentro do projeto Sapl
esta explicação ficará aqui dentro do código, via commit, e na issue #2456.
"""
class SaplFormLayout(Layout):
@ -58,7 +90,7 @@ class SaplFormLayout(Layout):
if not buttons:
buttons = form_actions(label=save_label, more=[
HTML('<a href="{{ view.cancel_url }}"'
' class="btn btn-inverse">%s</a>' % cancel_label)
' class="btn btn-dark">%s</a>' % cancel_label)
if cancel_label else None])
_fields = list(to_fieldsets(fields))
@ -185,8 +217,11 @@ class CrispyLayoutFormMixin:
pass
else:
if self.layout_key:
form.helper = FormHelper()
form.helper.layout = SaplFormLayout(*self.get_layout())
form.helper = SaplFormHelper()
layout = self.get_layout()
form.helper.layout = SaplFormLayout(*layout)
return form
@property

15
sapl/crud/base.py

@ -1,8 +1,8 @@
import logging
from braces.views import FormMessagesMixin
from compressor.utils.decorators import cached_property
from crispy_forms.bootstrap import FieldWithButtons, StrictButton
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import Field, Layout
from django import forms
from django.conf.urls import url
@ -16,6 +16,7 @@ from django.http.response import Http404
from django.shortcuts import redirect
from django.utils.decorators import classonlymethod
from django.utils.encoding import force_text
from django.utils.functional import cached_property
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
@ -29,6 +30,7 @@ from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL,
from sapl.settings import BASE_DIR
from sapl.utils import normalize
ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
'list', 'create', 'detail', 'update', 'delete'
@ -148,7 +150,7 @@ class ListWithSearchForm(forms.Form):
def __init__(self, *args, **kwargs):
super(ListWithSearchForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.form_class = 'form-inline'
self.helper.form_method = 'GET'
self.helper.layout = Layout(
@ -158,7 +160,7 @@ class ListWithSearchForm(forms.Form):
placeholder=_('Filtrar Lista'),
css_class='input-lg'),
StrictButton(
_('Filtrar'), css_class='btn-default btn-lg',
_('Filtrar'), css_class='btn-outline-primary btn-lg',
type='submit'))
)
@ -558,7 +560,8 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
fm = model._meta.get_field(fo)
except Exception as e:
username = self.request.user.username
self.logger.error("user=" + username + ". " + str(e))
self.logger.error(
"user=" + username + ". " + str(e))
pass
if fm and hasattr(fm, 'related_model')\
@ -882,7 +885,7 @@ class CrudDeleteView(PermissionRequiredContainerCrudMixin,
error_msg2 += '{} - {}, '.format(
i._meta.verbose_name, i
)
error_msg2 = error_msg2[:len(error_msg2)-2] + '.'
error_msg2 = error_msg2[:len(error_msg2) - 2] + '.'
error_msg += '</ul>'
username = request.user.username

4
sapl/crud/tests/stub_app/templates/base.html

@ -12,9 +12,9 @@
{# Feedback messages #}
{% for message in messages %}
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}{{ message.tags }}{% endif %} alert-dismissible fade in" role="alert">
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}{{ message.tags }}{% endif %} alert-dismissible " role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
<span aria-hidden="true">&times;</span>
</button>
{{ message|safe }}
</div>

1
sapl/env-backup

@ -5,4 +5,5 @@ EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST = ''
EMAIL_HOST_USER = ''
EMAIL_SEND_USER = ''
EMAIL_HOST_PASSWORD = ''

285
sapl/lexml/OAIServer.py

@ -0,0 +1,285 @@
from datetime import datetime
import oaipmh
import oaipmh.error
import oaipmh.metadata
import oaipmh.server
from django.urls import reverse
from lxml import etree
from lxml.builder import ElementMaker
from sapl.base.models import AppConfig, CasaLegislativa
from sapl.lexml.models import LexmlPublicador
from sapl.norma.models import NormaJuridica
class OAILEXML:
"""
Padrao OAI do LeXML
Esta registrado sobre o nome 'oai_lexml'
"""
def __init__(self, prefix):
self.prefix = prefix
self.ns = {'oai_lexml': 'http://www.lexml.gov.br/oai_lexml', }
self.schemas = {'oai_lexml': 'http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd'}
def __call__(self, element, metadata):
data = metadata.record
value = etree.XML(data['metadata'])
element.append(value)
class OAIServer:
"""
An OAI-2.0 compliant oai server.
Underlying code is based on pyoai's oaipmh.server'
"""
XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance'
ns = {'lexml': 'http://www.lexml.gov.br/oai_lexml'}
schema = {'oai_lexml': 'http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd'}
def __init__(self, config={}):
self.config = config
def identify(self):
result = oaipmh.common.Identify(
repositoryName=self.config['titulo'],
baseURL=self.config['base_url'],
protocolVersion='2.0',
adminEmails=self.config['email'],
earliestDatestamp=datetime(2001, 1, 1, 10, 00),
deletedRecord='transient',
granularity='YYYY-MM-DDThh:mm:ssZ',
compression=['identity'],
toolkit_description=False)
if not self.config['descricao']:
result.add_description(self.config['descricao'])
return result
def create_header_and_metadata(self, record):
header = self.create_header(record)
metadata = oaipmh.common.Metadata(None, record['metadata'])
metadata.record = record
return header, metadata
def list_query(self, from_date=None, until_date=None, offset=0, batch_size=10, identifier=None):
if identifier:
identifier = int(identifier.split('/')[-1]) # Get internal id
else:
identifier = ''
until_date = datetime.now() if not until_date or until_date > datetime.now() else until_date
return self.oai_query(offset=offset, batch_size=batch_size, from_date=from_date, until_date=until_date,
identifier=identifier)
def check_metadata_prefix(self, metadata_prefix):
if not metadata_prefix in self.config['metadata_prefixes']:
raise oaipmh.error.CannotDisseminateFormatError
def listRecords(self, metadataPrefix, from_date=None, until_date=None, cursor=0, batch_size=10):
self.check_metadata_prefix(metadataPrefix)
for record in self.list_query(from_date, until_date, cursor, batch_size):
header, metadata = self.create_header_and_metadata(record)
yield header, metadata, None # None?
def get_oai_id(self, internal_id):
return "oai:{}".format(internal_id)
def create_header(self, record):
oai_id = self.get_oai_id(record['record']['id'])
timestamp = record['record']['when_modified']
timestamp = timestamp.replace(tzinfo=None)
sets = []
deleted = record['record']['deleted']
return oaipmh.common.Header(None, oai_id, timestamp, sets, deleted)
def get_esfera_federacao(self):
appconfig = AppConfig.objects.first()
return appconfig.esfera_federacao
def recupera_norma(self, offset, batch_size, from_date, until_date, identifier, esfera):
kwargs = {'data__lte': until_date}
if from_date:
kwargs['data__gte'] = from_date
if identifier:
kwargs['numero'] = identifier
if esfera:
kwargs['esfera_federacao'] = esfera
return NormaJuridica.objects.select_related('tipo').filter(**kwargs)[offset:offset + batch_size]
def monta_id(self, norma):
if norma:
num = len(casa.endereco_web.split('.'))
dominio = '.'.join(casa.endereco_web.split('.')[1:num])
prefixo_oai = '{}.{}:sapl/'.format(casa.sigla.lower(), dominio)
numero_interno = norma.numero
tipo_norma = norma.tipo.equivalente_lexml
ano_norma = norma.ano
identificador = '{}{};{};{}'.format(prefixo_oai, tipo_norma, ano_norma, numero_interno)
return identificador
else:
return None
def monta_urn(self, norma, esfera):
if norma:
urn = 'urn:lex:br;'
esferas = {'M': 'municipal', 'E': 'estadual'}
municipio = casa.municipio.lower()
uf = casa.uf.lower()
for x in [' ', '.de.', '.da.', '.das.', '.do.', '.dos.']:
municipio = municipio.replace(x, '.')
uf = uf.replace(x, '.')
if esfera == 'M':
urn += '{};{}:'.format(uf, municipio)
if norma.tipo.equivalente_lexml == 'regimento.interno' or norma.tipo.equivalente_lexml == 'resolucao':
urn += 'camara.'
urn += esferas[esfera] + ':'
elif esfera == 'E':
urn += '{}:{}:'.format(uf, esferas[esfera])
else:
urn += ':'
if norma.tipo.equivalente_lexml:
urn += '{}:{};'.format(norma.tipo.equivalente_lexml, norma.data.isoformat())
else:
urn += '{};'.format(norma.data.isoformat())
if norma.tipo.equivalente_lexml == 'lei.organica' or norma.tipo.equivalente_lexml == 'constituicao':
urn += norma.ano
else:
urn += norma.numero
if norma.data_vigencia and norma.data_publicacao:
urn += '@{};publicacao;{}'.format(norma.data_vigencia.isoformat(), norma.data_publicacao.isoformat())
elif norma.data_publicacao:
urn += '@inicio.vigencia;publicacao;{}'.format(norma.data_publicacao.isoformat())
return urn
else:
return None
def data_por_extenso(self, data):
data = data.strftime('%d-%m-%Y')
if data != '':
meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho', 7: 'Julho',
8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'}
return '{} de {} de {}'.format(data[0:2], meses[int(data[3:5])], data[6:])
else:
return ''
def monta_xml(self, urn, norma):
publicador = LexmlPublicador.objects.first()
if norma and publicador:
LEXML = ElementMaker(namespace=self.ns['lexml'], nsmap=self.ns)
oai_lexml = LEXML.LexML()
oai_lexml.attrib['{{}}schemaLocation'.format(self.XSI_NS)] = '{} {}'.format(
'http://www.lexml.gov.br/oai_lexml', 'http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd')
texto_integral = norma.texto_integral
mime_types = {'doc': 'application/msword',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'odt': 'application/vnd.oasis.opendocument.text',
'pdf': 'application/pdf',
'rtf': 'application/rtf'}
if texto_integral:
url_conteudo = self.config['base_url'] + texto_integral.url
extensao = texto_integral.url.split('.')[-1]
formato = mime_types.get(extensao, 'application/octet-stream')
else:
formato = 'text/html'
url_conteudo = self.config['base_url'] + reverse('sapl.norma:normajuridica_detail',
kwargs={'pk': norma.numero})
element_maker = ElementMaker()
id_publicador = str(publicador.id_publicador)
item_conteudo = element_maker.Item(url_conteudo, formato=formato, idPublicador=id_publicador,
tipo='conteudo')
oai_lexml.append(item_conteudo)
url = self.config['base_url'] + reverse('sapl.norma:normajuridica_detail', kwargs={'pk': norma.numero})
item_metadado = element_maker.Item(url, formato='text/html', idPublicador=id_publicador, tipo='metadado')
oai_lexml.append(item_metadado)
documento_individual = element_maker.DocumentoIndividual(urn)
oai_lexml.append(documento_individual)
if norma.tipo.equivalente_lexml == 'lei.organica':
epigrafe = '{} de {} - {}, de {}'.format(norma.tipo.descricao, casa.municipio,
casa.uf, norma.ano)
elif norma.tipo.equivalente_lexml == 'constituicao':
epigrafe = '{} do Estado de {}, de {}'.format(norma.tipo.descricao, casa.municipio,
norma.ano)
else:
epigrafe = '{}{}, de {}'.format(norma.tipo.descricao, norma.numero,
self.data_por_extenso(norma.data))
oai_lexml.append(element_maker.Epigrafe(epigrafe))
oai_lexml.append(element_maker.Ementa(norma.ementa))
indexacao = norma.indexacao
if indexacao:
oai_lexml.append(element_maker.Indexacao(indexacao))
return etree.tostring(oai_lexml)
else:
return None
def oai_query(self, offset=0, batch_size=10, from_date=None, until_date=None, identifier=None):
esfera = self.get_esfera_federacao()
offset = 0 if offset < 0 else offset
batch_size = 10 if batch_size < 0 else batch_size
until_date = datetime.now() if not until_date or until_date > datetime.now() else until_date
normas = self.recupera_norma(offset, batch_size, from_date, until_date, identifier, esfera)
for norma in normas:
resultado = {}
identificador = self.monta_id(norma)
urn = self.monta_urn(norma, esfera)
xml_lexml = self.monta_xml(urn, norma)
resultado['tx_metadado_xml'] = xml_lexml
resultado['cd_status'] = 'N'
resultado['id'] = identificador
resultado['when_modified'] = norma.timestamp
resultado['deleted'] = 0
yield {'record': resultado,
'metadata': resultado['tx_metadado_xml']}
def OAIServerFactory(config={}):
"""
Create a new OAI batching OAI Server given a config and a database
"""
for prefix in config['metadata_prefixes']:
metadata_registry = oaipmh.metadata.MetadataRegistry()
metadata_registry.registerWriter(prefix, OAILEXML(prefix))
return oaipmh.server.BatchingServer(
OAIServer(config),
metadata_registry=metadata_registry,
resumption_batch_size=config['batch_size']
)
casa = None
def casa_legislativa():
global casa
if not casa:
casa = CasaLegislativa.objects.first()
return casa if casa else CasaLegislativa() # retorna objeto dummy
def get_config(url, batch_size):
config = {'content_type': None,
'delay': 0,
'base_asset_path': None,
'metadata_prefixes': ['oai_lexml']}
config.update({'titulo': casa_legislativa().nome, # Inicializa variável global casa
'email': casa.email,
'base_url': url[:url.find('/', 8)],
'descricao': casa.informacao_geral,
'batch_size': batch_size})
return config
if __name__ == '__main__':
"""
Para executar localmente (estando no diretório raiz):
$ ./manage.py shell_plus
Executar comando
%run sapl/lexml/OAIServer.py
"""
oai_server = OAIServerFactory(get_config('http://127.0.0.1:8000/', 10))
r = oai_server.handleRequest({'verb': 'ListRecords',
'metadataPrefix': 'oai_lexml'})
print(r.decode('UTF-8'))

3
sapl/lexml/urls.py

@ -1,6 +1,6 @@
from django.conf.urls import include, url
from sapl.lexml.views import LexmlProvedorCrud, LexmlPublicadorCrud
from sapl.lexml.views import LexmlProvedorCrud, LexmlPublicadorCrud, lexml_request
from .apps import AppConfig
@ -11,4 +11,5 @@ urlpatterns = [
include(LexmlProvedorCrud.get_urls())),
url(r'^sistema/lexml/publicador/',
include(LexmlPublicadorCrud.get_urls())),
url(r'^sistema/lexml', lexml_request, name='lexml_endpoint')
]

12
sapl/lexml/views.py

@ -1,6 +1,18 @@
from django.http import HttpResponse
from sapl.crud.base import CrudAux
from sapl.lexml.OAIServer import OAIServerFactory, get_config
from .models import LexmlProvedor, LexmlPublicador
LexmlProvedorCrud = CrudAux.build(LexmlProvedor, 'lexml_provedor')
LexmlPublicadorCrud = CrudAux.build(LexmlPublicador, 'lexml_publicador')
def lexml_request(request):
config = get_config(request.get_raw_uri(), int(request.GET.get('batch_size', 10)))
oai_server = OAIServerFactory(config)
r = oai_server.handleRequest({'verb': request.GET.get('verb', 'ListRecords'),
'metadataPrefix': request.GET.get('metadataPrefix', 'oai_lexml')})
response = r.decode('UTF-8')
return HttpResponse(response, content_type='text/xml')

501
sapl/materia/forms.py

@ -1,18 +1,18 @@
import os
import logging
import django_filters
from crispy_forms.bootstrap import Alert, FormActions, InlineRadios
from crispy_forms.helper import FormHelper
import os
from crispy_forms.bootstrap import Alert, InlineRadios
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset,
Layout, Submit)
Layout, Row)
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.files.base import File
from django.core.urlresolvers import reverse
from django.db import models, transaction
from django.db.models import Max
from django.db.models import Max, Q, F
from django.forms import ModelChoiceField, ModelForm, widgets
from django.forms.forms import Form
from django.forms.models import ModelMultipleChoiceField
@ -22,6 +22,7 @@ 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 AppConfig, Autor, TipoAutor
@ -36,30 +37,34 @@ from sapl.materia.models import (AssuntoMateria, Autoria, MateriaAssunto,
UnidadeTramitacao)
from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica)
from sapl.parlamentares.models import Legislatura
from sapl.parlamentares.models import Legislatura, Partido
from sapl.protocoloadm.models import Protocolo, DocumentoAdministrativo
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SEPARADOR_HASH_PROPOSICAO,
from sapl.utils import (YES_NO_CHOICES, SEPARADOR_HASH_PROPOSICAO,
ChoiceWithoutValidationField,
MateriaPesquisaOrderingFilter, RangeWidgetOverride,
autor_label, autor_modal, gerar_hash_arquivo,
models_with_gr_for_model, qs_override_django_filter)
models_with_gr_for_model, qs_override_django_filter,
choice_anos_com_materias, FilterOverridesMetaMixin, FileFieldCheckMixin)
from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial,
DocumentoAcessorio, Numeracao, Proposicao, Relatoria,
TipoMateriaLegislativa, Tramitacao, UnidadeTramitacao)
def ANO_CHOICES():
return [('', '---------')] + RANGE_ANOS
def em_tramitacao():
def CHOICE_TRAMITACAO():
return [('', 'Tanto Faz'),
(1, 'Sim'),
(0, 'Não')]
def CHOICE_TIPO_LISTAGEM():
return [
(1, _('Detalhada')),
(2, _('Simplificada')),
]
class AdicionarVariasAutoriasFilterSet(django_filters.FilterSet):
class Meta:
@ -71,7 +76,7 @@ class AdicionarVariasAutoriasFilterSet(django_filters.FilterSet):
row1 = to_row([('nome', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Filtrar Autores'),
@ -91,7 +96,7 @@ class OrgaoForm(ModelForm):
orgao = super(OrgaoForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Orgao)
object_id = orgao.pk
tipo = TipoAutor.objects.get(descricao='Órgão')
tipo = TipoAutor.objects.get(content_type=content_type)
nome = orgao.nome + ' - ' + orgao.sigla
Autor.objects.create(
content_type=content_type,
@ -107,7 +112,7 @@ class ReceberProposicaoForm(Form):
def __init__(self, *args, **kwargs):
row1 = to_row([('cod_hash', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
_('Incorporar Proposição'), row1,
@ -117,7 +122,7 @@ class ReceberProposicaoForm(Form):
super(ReceberProposicaoForm, self).__init__(*args, **kwargs)
class MateriaSimplificadaForm(ModelForm):
class MateriaSimplificadaForm(FileFieldCheckMixin, ModelForm):
logger = logging.getLogger(__name__)
@ -127,16 +132,20 @@ class MateriaSimplificadaForm(ModelForm):
'numero_protocolo', 'regime_tramitacao',
'em_tramitacao', 'ementa', 'tipo_apresentacao',
'texto_original']
widgets = {
'numero_protocolo': forms.TextInput(attrs={'readonly': True}),
}
def __init__(self, *args, **kwargs):
row1 = to_row([('tipo', 6), ('numero', 3), ('ano', 3)])
row2 = to_row([('data_apresentacao', 6), ('numero_protocolo', 6)])
row3 = to_row([('regime_tramitacao', 6), ('em_tramitacao', 3), ('tipo_apresentacao', 3)])
row3 = to_row([('regime_tramitacao', 6),
('em_tramitacao', 3), ('tipo_apresentacao', 3)])
row4 = to_row([('ementa', 12)])
row5 = to_row([('texto_original', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
_('Formulário Simplificado'),
@ -166,7 +175,7 @@ class MateriaSimplificadaForm(ModelForm):
return cleaned_data
class MateriaLegislativaForm(ModelForm):
class MateriaLegislativaForm(FileFieldCheckMixin, ModelForm):
logger = logging.getLogger(__name__)
@ -188,11 +197,14 @@ class MateriaLegislativaForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MateriaLegislativaForm, self).__init__(*args, **kwargs)
self.fields['ementa'].widget.attrs['maxlength'] = 1000
if self.instance and self.instance.pk:
self.fields['tipo_autor'] = forms.CharField(required=False,
widget=forms.HiddenInput())
self.fields['autor'] = forms.CharField(required=False,
widget=forms.HiddenInput())
if kwargs['instance'].numero_protocolo:
self.fields['numero_protocolo'].widget.attrs['readonly'] = True
def clean(self):
@ -209,7 +221,7 @@ class MateriaLegislativaForm(ModelForm):
protocolo_antigo = self.instance.numero_protocolo
if protocolo:
if not Protocolo.objects.filter(numero=protocolo,ano=ano).exists():
if not Protocolo.objects.filter(numero=protocolo, ano=ano).exists():
self.logger.error("Protocolo %s/%s não"
" existe" % (protocolo, ano))
raise ValidationError(_('Protocolo %s/%s não'
@ -232,11 +244,12 @@ class MateriaLegislativaForm(ModelForm):
' documento vinculado'
% (protocolo, ano)))
p = Protocolo.objects.get(numero=protocolo,ano=ano)
p = Protocolo.objects.get(numero=protocolo, ano=ano)
if p.tipo_materia != cleaned_data['tipo']:
self.logger.error("Tipo do Protocolo ({}) deve ser o mesmo do Tipo Matéria ({})."
.format(cleaned_data['tipo'], p.tipo_materia))
raise ValidationError(_('Tipo do Protocolo deve ser o mesmo do Tipo Matéria'))
raise ValidationError(
_('Tipo do Protocolo deve ser o mesmo do Tipo Matéria'))
if data_apresentacao.year != ano:
self.logger.error("O ano da matéria ({}) é diferente "
@ -277,6 +290,7 @@ class MateriaLegislativaForm(ModelForm):
return materia
class UnidadeTramitacaoForm(ModelForm):
logger = logging.getLogger(__name__)
@ -332,7 +346,7 @@ class AcompanhamentoMateriaForm(ModelForm):
Column(form_actions(label='Cadastrar'), css_class='col-md-2')
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
_('Acompanhamento de Matéria por e-mail'), row1
@ -341,7 +355,7 @@ class AcompanhamentoMateriaForm(ModelForm):
super(AcompanhamentoMateriaForm, self).__init__(*args, **kwargs)
class DocumentoAcessorioForm(ModelForm):
class DocumentoAcessorioForm(FileFieldCheckMixin, ModelForm):
data = forms.DateField(required=True)
class Meta:
@ -410,6 +424,11 @@ class TramitacaoForm(ModelForm):
def __init__(self, *args, **kwargs):
super(TramitacaoForm, self).__init__(*args, **kwargs)
self.fields['data_tramitacao'].initial = timezone.now().date()
ust = UnidadeTramitacao.objects.select_related().all()
unidade_tramitacao_destino = [(ut.pk, ut) for ut in ust if ut.comissao and ut.comissao.ativa]
unidade_tramitacao_destino.extend([(ut.pk, ut) for ut in ust if ut.orgao])
unidade_tramitacao_destino.extend([(ut.pk, ut) for ut in ust if ut.parlamentar])
self.fields['unidade_tramitacao_destino'].choices = unidade_tramitacao_destino
def clean(self):
super(TramitacaoForm, self).clean()
@ -481,6 +500,20 @@ class TramitacaoForm(ModelForm):
return cleaned_data
@transaction.atomic
def save(self, commit=True):
tramitacao = super(TramitacaoForm, self).save(commit)
materia = tramitacao.materia
for ma in materia.anexadas.all():
if not ma.tramitacao_set.all() \
or ma.tramitacao_set.last().unidade_tramitacao_destino == tramitacao.unidade_tramitacao_local:
tramitacao_nova = tramitacao
tramitacao_nova.pk = None
tramitacao_nova.materia = ma
tramitacao_nova.save()
return tramitacao
class TramitacaoUpdateForm(TramitacaoForm):
unidade_tramitacao_local = forms.ModelChoiceField(
@ -640,6 +673,7 @@ class LegislacaoCitadaForm(ModelForm):
class NumeracaoForm(ModelForm):
logger = logging.getLogger(__name__)
class Meta:
model = Numeracao
fields = ['tipo_materia',
@ -754,43 +788,47 @@ class AnexadaForm(ModelForm):
class MateriaLegislativaFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial Final')),
'widget': RangeWidgetOverride}
}}
ano = django_filters.ChoiceFilter(required=False,
label='Ano da Matéria',
choices=ANO_CHOICES)
choices=choice_anos_com_materias)
autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
autoria__primeiro_autor = django_filters.BooleanFilter(
required=False,
label='Primeiro Autor',
widget=forms.HiddenInput())
label=_('Primeiro Autor'))
autoria__autor__parlamentar_set__filiacao__partido = django_filters.ModelChoiceFilter(
queryset=Partido.objects.all(),
label=_('Matérias por Partido'))
ementa = django_filters.CharFilter(lookup_expr='icontains')
ementa = django_filters.CharFilter(
label=_('Pesquisar expressões na ementa'),
method='filter_ementa'
)
indexacao = django_filters.CharFilter(lookup_expr='icontains',
label=_('Indexação'))
em_tramitacao = django_filters.ChoiceFilter(required=False,
label='Em tramitação',
choices=em_tramitacao)
choices=CHOICE_TRAMITACAO)
materiaassunto__assunto = django_filters.ModelChoiceFilter(
queryset=AssuntoMateria.objects.all(),
label=_('Assunto da Matéria'))
label=_('Assunto'))
numeracao__numero_materia = django_filters.NumberFilter(
required=False,
label=_('Número do Processo'))
label=_('Número do processo'))
o = MateriaPesquisaOrderingFilter()
o = MateriaPesquisaOrderingFilter(help_text='')
class Meta:
tipo_listagem = forms.ChoiceField(
required=False,
choices=CHOICE_TIPO_LISTAGEM,
label=_('Tipo da Listagem do Resultado da Pesquisa'))
class Meta(FilterOverridesMetaMixin):
model = MateriaLegislativa
fields = ['numero',
'numero_protocolo',
@ -801,7 +839,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
'data_publicacao',
'autoria__autor__tipo',
'autoria__primeiro_autor',
# FIXME 'autoria__autor__partido',
'autoria__autor__parlamentar_set__filiacao__partido',
'relatoria__parlamentar_id',
'local_origem_externa',
'tramitacao__unidade_tramitacao_destino',
@ -810,16 +848,36 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
'em_tramitacao',
]
def filter_ementa(self, queryset, name, value):
texto = value.split()
q = Q()
for t in texto:
q &= Q(ementa__icontains=t)
return queryset.filter(q)
def __init__(self, *args, **kwargs):
super(MateriaLegislativaFilterSet, self).__init__(*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['autoria__autor__tipo'].label = 'Tipo de Autor'
# self.filters['autoria__autor__partido'].label = 'Partido do Autor'
self.filters['relatoria__parlamentar_id'].label = 'Relatoria'
# self.filters['tipo'].label = 'Tipo de Matéria'
self.filters[
'autoria__autor__parlamentar_set__filiacao__partido'
].label = 'Partido do Autor'
self.filters['autoria__autor__tipo'].label = _('Tipo de Autor')
self.filters['relatoria__parlamentar_id'].label = _('Relatoria')
self.filters['tramitacao__unidade_tramitacao_destino'].label = _(
'Unidade de tramitação atual')
self.filters['tramitacao__status'].label = _(
'Status da tramitação atual')
self.filters['tramitacao__status'].label = _(
'Status da tramitação atual')
self.filters['o'].label = _('Ordenação')
self.form.fields['tipo_listagem'] = self.tipo_listagem
row1 = to_row(
[('tipo', 12)])
[('tipo', 5), ('ementa', 7)])
row2 = to_row(
[('numero', 3),
('numeracao__numero_materia', 3),
@ -828,47 +886,78 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
row3 = to_row(
[('data_apresentacao', 6),
('data_publicacao', 6)])
row4 = to_row(
[('autoria__autor', 0),
('autoria__primeiro_autor', 0),
row4 = to_row([
('autoria__autor', 0),
(Button('pesquisar',
'Pesquisar Autor',
css_class='btn btn-primary btn-sm'), 2),
(Button('limpar',
'limpar Autor',
css_class='btn btn-primary btn-sm'), 10)])
row5 = to_row(
[('autoria__autor__tipo', 12),
# ('autoria__autor__partido', 6)
css_class='btn btn-primary btn-sm'), 2),
('autoria__primeiro_autor', 2),
('autoria__autor__tipo', 3),
('autoria__autor__parlamentar_set__filiacao__partido', 3)
])
row6 = to_row(
[('relatoria__parlamentar_id', 6),
('local_origem_externa', 6)])
row7 = to_row(
[('tramitacao__unidade_tramitacao_destino', 6),
('tramitacao__status', 6)])
row8 = to_row(
[('em_tramitacao', 6),
('o', 6)])
[('tramitacao__unidade_tramitacao_destino', 5),
('tramitacao__status', 5),
('em_tramitacao', 2)
])
row9 = to_row(
[('materiaassunto__assunto', 6), ('indexacao', 6)])
row10 = to_row(
[('ementa', 12)])
self.form.helper = FormHelper()
row8 = to_row(
[
('o', 8),
('tipo_listagem', 4)
])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Matéria'),
row1, row2, row3,
Fieldset(_('Pesquisa Básica'),
row1, row2),
Fieldset(_('Como listar os resultados da pesquisa'),
row8
),
Fieldset(_('Pesquisa Avançada'),
row3,
HTML(autor_label),
HTML(autor_modal),
row4, row5, row6, row7, row8, row9, row10,
form_actions(label='Pesquisar'))
row4, row6, row7, row9,
form_actions(label=_('Pesquisar')))
)
@property
def qs(self):
return qs_override_django_filter(self)
qs = qs_override_django_filter(self)
if hasattr(self.form, 'cleaned_data') and self.form.cleaned_data[
'autoria__autor__parlamentar_set__filiacao__partido']:
q_data_inicio_e_fim = Q(data_apresentacao__gte=F(
'autoria__autor__parlamentar_set__filiacao__data'),
data_apresentacao__lte=F(
'autoria__autor__parlamentar_set__filiacao__data_desfiliacao'))
q_data_inicio = Q(
data_apresentacao__gte=F(
'autoria__autor__parlamentar_set__filiacao__data'),
autoria__autor__parlamentar_set__filiacao__data_desfiliacao__isnull=True
)
qs = qs.filter(
q_data_inicio_e_fim | q_data_inicio
)
return qs
def pega_ultima_tramitacao():
@ -919,8 +1008,9 @@ class DespachoInicialForm(ModelForm):
if DespachoInicial.objects.filter(
materia=self.instance.materia,
comissao=self.cleaned_data['comissao'],
).exists():
msg = _('Esse Despacho já foi cadastrado.')
).exclude(pk=self.instance.pk).exists():
msg = _('Já existe um Despacho cadastrado para %s' %
self.cleaned_data['comissao'])
raise ValidationError(msg)
return self.cleaned_data
@ -945,7 +1035,7 @@ class AutoriaForm(ModelForm):
('autor', 4),
('primeiro_autor', 4)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Autoria'),
row1, 'data_relativa', form_actions(label='Salvar')))
@ -969,7 +1059,8 @@ class AutoriaForm(ModelForm):
if ((not pk and autorias.exists()) or
(pk and autorias.exclude(pk=pk).exists())):
self.logger.error("Esse Autor (pk={}) já foi cadastrado.".format(pk))
self.logger.error(
"Esse Autor (pk={}) já foi cadastrado.".format(pk))
raise ValidationError(_('Esse Autor já foi cadastrado.'))
return cd
@ -1005,7 +1096,7 @@ class AutoriaMultiCreateForm(Form):
row2 = to_row([('autor', 12), ])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
_('Autorias'), row1, row2, 'data_relativa', 'autores',
@ -1020,7 +1111,8 @@ class AutoriaMultiCreateForm(Form):
del self.errors['autores']
if 'autor' not in cd or not cd['autor'].exists():
self.logger.error("Ao menos um autor deve ser selecionado para inclusão")
self.logger.error(
"Ao menos um autor deve ser selecionado para inclusão")
raise ValidationError(
_('Ao menos um autor deve ser selecionado para inclusão'))
@ -1029,14 +1121,7 @@ class AutoriaMultiCreateForm(Form):
class AcessorioEmLoteFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
class Meta:
class Meta(FilterOverridesMetaMixin):
model = MateriaLegislativa
fields = ['tipo', 'data_apresentacao']
@ -1051,7 +1136,7 @@ class AcessorioEmLoteFilterSet(django_filters.FilterSet):
row1 = to_row([('tipo', 12)])
row2 = to_row([('data_apresentacao', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Documentos Acessórios em Lote'),
@ -1060,14 +1145,7 @@ class AcessorioEmLoteFilterSet(django_filters.FilterSet):
class PrimeiraTramitacaoEmLoteFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
class Meta:
class Meta(FilterOverridesMetaMixin):
model = MateriaLegislativa
fields = ['tipo', 'data_apresentacao']
@ -1083,7 +1161,7 @@ class PrimeiraTramitacaoEmLoteFilterSet(django_filters.FilterSet):
row1 = to_row([('tipo', 12)])
row2 = to_row([('data_apresentacao', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Primeira Tramitação'),
@ -1092,14 +1170,7 @@ class PrimeiraTramitacaoEmLoteFilterSet(django_filters.FilterSet):
class TramitacaoEmLoteFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
class Meta:
class Meta(FilterOverridesMetaMixin):
model = MateriaLegislativa
fields = ['tipo', 'data_apresentacao', 'tramitacao__status',
'tramitacao__unidade_tramitacao_destino']
@ -1108,10 +1179,11 @@ class TramitacaoEmLoteFilterSet(django_filters.FilterSet):
super(TramitacaoEmLoteFilterSet, self).__init__(
*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['data_apresentacao'].label = 'Data (Inicial - Final)'
self.filters['tipo'].label = _('Tipo de Matéria')
self.filters['data_apresentacao'].label = _('Data (Inicial - Final)')
self.filters['tramitacao__unidade_tramitacao_destino'
].label = 'Unidade Destino (Último Destino)'
].label = _('Unidade Destino (Último Destino)')
self.filters['tramitacao__status'].label = _('Status')
self.form.fields['tipo'].required = True
self.form.fields['data_apresentacao'].required = False
self.form.fields['tramitacao__status'].required = True
@ -1124,11 +1196,11 @@ class TramitacaoEmLoteFilterSet(django_filters.FilterSet):
('tramitacao__status', 4)])
row2 = to_row([('data_apresentacao', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Tramitação em Lote'),
row1, row2, form_actions(label='Pesquisar')))
row1, row2, form_actions(label=_('Pesquisar'))))
class TipoProposicaoForm(ModelForm):
@ -1138,7 +1210,8 @@ class TipoProposicaoForm(ModelForm):
content_type = forms.ModelChoiceField(
queryset=ContentType.objects.all(),
label=TipoProposicao._meta.get_field('content_type').verbose_name,
required=True)
required=True,
help_text=TipoProposicao._meta.get_field('content_type').help_text)
tipo_conteudo_related_radio = ChoiceWithoutValidationField(
label="Seleção de Tipo",
@ -1162,15 +1235,32 @@ class TipoProposicaoForm(ModelForm):
def __init__(self, *args, **kwargs):
tipo_select = Fieldset(TipoProposicao._meta.verbose_name,
Div(to_column(('descricao', 5)),
to_column(('content_type', 7)),
css_class='clearfix'),
to_column(('tipo_conteudo_related_radio', 6)),
to_column(('perfis', 6)))
tipo_select = Fieldset(
TipoProposicao._meta.verbose_name,
Row(
to_column(
(
Row(
to_column(('descricao', 12)),
to_column(('perfis', 12)),
),
5
)
),
to_column(
(
Row(
to_column(('content_type', 12)),
to_column(('tipo_conteudo_related_radio', 12)),
to_column(('tipo_conteudo_related', 12)),
),
7
)
),
)
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(tipo_select)
super(TipoProposicaoForm, self).__init__(*args, **kwargs)
@ -1250,44 +1340,17 @@ class TipoProposicaoForm(ModelForm):
class TipoProposicaoSelect(Select):
def render_tipo_option(self, selected_choices, option_value, option_label,
data_has_perfil=False):
if option_value is None:
option_value = ''
option_value = force_text(option_value)
if option_value in selected_choices:
selected_html = mark_safe(' selected="selected"')
if not self.allow_multiple_selected:
# Only allow for a single selection.
selected_choices.remove(option_value)
else:
selected_html = ''
return format_html(
'<option value="{}"{} data-has-perfil={}>{}</option>',
option_value,
selected_html,
str(data_has_perfil),
force_text(option_label))
def render_options(self, selected_choices):
# Normalize to strings.
selected_choices = set(force_text(v) for v in selected_choices)
output = []
output.append(
self.render_tipo_option(
selected_choices, '', self.choices.field.empty_label))
for tipo in self.choices.queryset.all():
output.append(
self.render_tipo_option(
selected_choices,
str(tipo.pk),
str(tipo),
data_has_perfil=tipo.perfis.exists()))
return '\n'.join(output)
class ProposicaoForm(forms.ModelForm):
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(name, value, label, selected,
index, subindex=subindex, attrs=attrs)
if value:
tipo = TipoProposicao.objects.get(id=value)
option['attrs']['data-has-perfil'] = str(tipo.perfis.exists())
return option
class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
logger = logging.getLogger(__name__)
@ -1324,6 +1387,9 @@ class ProposicaoForm(forms.ModelForm):
widget=widgets.HiddenInput(),
required=False)
numero_materia_futuro = forms.IntegerField(
label='Número (Opcional)', required=False)
class Meta:
model = Proposicao
fields = ['tipo',
@ -1337,7 +1403,8 @@ class ProposicaoForm(forms.ModelForm):
'numero_materia',
'ano_materia',
'tipo_texto',
'hash_code']
'hash_code',
'numero_materia_futuro']
widgets = {
'descricao': widgets.Textarea(attrs={'rows': 4}),
@ -1370,6 +1437,12 @@ class ProposicaoForm(forms.ModelForm):
]
if sapl.base.models.AppConfig.objects.last().escolher_numero_materia_proposicao:
fields.append(to_column(('numero_materia_futuro', 12)),)
else:
if 'numero_materia_futuro' in self._meta.fields:
self._meta.fields.remove('numero_materia_futuro')
if self.texto_articulado_proposicao:
fields.append(
to_column((InlineRadios('tipo_texto'), 5)),)
@ -1377,13 +1450,17 @@ class ProposicaoForm(forms.ModelForm):
fields.append(to_column((
'texto_original', 7 if self.texto_articulado_proposicao else 12)))
fields.append(to_column((Fieldset(_('Outras informações - Vincular a Matéria Legislativa Existente'),
to_column(('tipo_materia', 12)),
to_column(('numero_materia', 6)),
to_column(('ano_materia', 6))
fields.append(
to_column(
(
Fieldset(
_('Outras informações - Vincular a Matéria Legislativa Existente'),
to_row([('tipo_materia', 12), ]),
to_row([('numero_materia', 6),
('ano_materia', 6)]),
), 12)),
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(*fields)
super(ProposicaoForm, self).__init__(*args, **kwargs)
@ -1411,7 +1488,8 @@ class ProposicaoForm(forms.ModelForm):
texto_original = self.cleaned_data.get('texto_original', False)
if texto_original and texto_original.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
self.logger.error("- Arquivo muito grande. ( > {0}MB )".format(max_size))
self.logger.error(
"- Arquivo muito grande. ( > {0}MB )".format(max_size))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))
return texto_original
@ -1441,6 +1519,15 @@ class ProposicaoForm(forms.ModelForm):
cd.get('ano_materia', ''),
cd.get('numero_materia', ''))
if cd['numero_materia_futuro'] and \
'tipo' in cd and \
MateriaLegislativa.objects.filter(tipo=cd['tipo'].tipo_conteudo_related,
ano=timezone.now().year,
numero=cd['numero_materia_futuro']):
raise ValidationError(_("A matéria {} {}/{} já existe.".format(cd['tipo'].tipo_conteudo_related.descricao,
cd['numero_materia_futuro'],
timezone.now().year)))
if tm and am and nm:
try:
self.logger.debug("Tentando obter objeto MateriaLegislativa (tipo_id={}, ano={}, numero={})."
@ -1480,7 +1567,6 @@ class ProposicaoForm(forms.ModelForm):
inst.texto_original.delete()
self.gerar_hash(inst, receber_recibo)
return super().save(commit)
inst.ano = timezone.now().year
@ -1526,12 +1612,12 @@ class DevolverProposicaoForm(forms.ModelForm):
to_column(
(form_actions(label=_('Devolver'),
name='devolver',
css_class='btn-danger pull-right'), 12)
css_class='btn-danger float-right'), 12)
)
)
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(*fields)
def clean(self):
@ -1606,13 +1692,16 @@ class ConfirmarProposicaoForm(ProposicaoForm):
'descricao',
'observacao',
'gerar_protocolo',
'numero_de_paginas'
'numero_de_paginas',
'numero_materia_futuro'
]
widgets = {
'descricao': widgets.Textarea(
attrs={'readonly': 'readonly', 'rows': 4}),
'data_envio': widgets.DateTimeInput(
attrs={'readonly': 'readonly'}),
'numero_materia_futuro': widgets.NumberInput(
attrs={'readonly': 'readonly', 'rows': 1}),
}
@ -1652,21 +1741,37 @@ class ConfirmarProposicaoForm(ProposicaoForm):
# esta chamada isola o __init__ de ProposicaoForm
super(ProposicaoForm, self).__init__(*args, **kwargs)
self.fields['numero_materia_futuro'].widget.attrs['readonly'] = True
fields = [
Fieldset(
_('Dados Básicos'),
to_column(('tipo_readonly', 4)),
to_column(('data_envio', 3)),
to_column(('autor_readonly', 5)),
to_column(('descricao', 12)),
to_column(('observacao', 12)))]
to_row(
[
('tipo_readonly', 3),
('data_envio', 3),
('autor_readonly', 3),
('numero_materia_futuro', 3),
('descricao', 12),
('observacao', 12)
]
)
)
]
if not sapl.base.models.AppConfig.objects.last().escolher_numero_materia_proposicao or \
not self.instance.numero_materia_futuro:
if 'numero_materia_futuro' in self._meta.fields:
del fields[0][0][3]
fields.append(
Fieldset(_('Vinculado a Matéria Legislativa'),
to_column(('tipo_materia', 3)),
to_column(('numero_materia', 2)),
to_column(('ano_materia', 2)),
to_column(
Fieldset(
_('Vinculado a Matéria Legislativa'),
to_row(
[
('tipo_materia', 3),
('numero_materia', 2),
('ano_materia', 2),
(Alert(_('O responsável pela incorporação pode '
'alterar a anexação. Limpar os campos '
'de Vinculação gera um %s independente '
@ -1675,11 +1780,14 @@ class ConfirmarProposicaoForm(ProposicaoForm):
'não permitirá estes campos serem vazios.'
) % self.instance.tipo.content_type,
css_class="alert-info",
dismiss=False), 5)),
to_column(
dismiss=False), 5),
(Alert('',
css_class="ementa_materia hidden alert-info",
dismiss=False), 12))))
dismiss=False), 12),
]
)
)
)
itens_incorporacao = []
if self.instance.tipo.content_type.model_class() == \
@ -1701,13 +1809,15 @@ class ConfirmarProposicaoForm(ProposicaoForm):
)
fields.append(
Fieldset(_('Registro de Incorporação'), *itens_incorporacao))
Fieldset(_('Registro de Incorporação'), Row(*itens_incorporacao)))
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(*fields)
self.fields['tipo_readonly'].initial = self.instance.tipo.descricao
self.fields['autor_readonly'].initial = str(self.instance.autor)
if self.instance.numero_materia_futuro:
self.fields['numero_materia_futuro'].initial = self.instance.numero_materia_futuro
if self.instance.materia_de_vinculo:
self.fields[
@ -1804,7 +1914,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
numeracao = None
try:
self.logger.debug("Tentando obter modelo de sequência de numeração.")
self.logger.debug(
"Tentando obter modelo de sequência de numeração.")
numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao
except AttributeError as e:
@ -1830,13 +1941,21 @@ class ConfirmarProposicaoForm(ProposicaoForm):
tipo=tipo).aggregate(
Max('numero'))
elif numeracao == 'U':
numero = MateriaLegislativa.objects.filter(tipo=tipo).aggregate(Max('numero'))
numero = MateriaLegislativa.objects.filter(
tipo=tipo).aggregate(Max('numero'))
if numeracao is None:
numero['numero__max'] = 0
if cd['numero_materia_futuro'] and not MateriaLegislativa.objects.filter(tipo=tipo,
ano=ano,
numero=cd['numero_materia_futuro']):
max_numero = cd['numero_materia_futuro']
else:
max_numero = numero['numero__max'] + 1 if numero['numero__max'] else 1
# dados básicos
materia = MateriaLegislativa()
materia.numero = max_numero
@ -1975,7 +2094,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
protocolo.tipo_protocolo = '1'
protocolo.interessado = str(proposicao.autor)[:200] # tamanho máximo 200
protocolo.interessado = str(proposicao.autor)[
:200] # tamanho máximo 200
protocolo.autor = proposicao.autor
protocolo.assunto_ementa = proposicao.descricao
protocolo.numero_paginas = cd['numero_de_paginas']
@ -1994,15 +2114,10 @@ class ConfirmarProposicaoForm(ProposicaoForm):
self.instance.results['messages']['success'].append(_(
'Protocolo realizado com sucesso'))
# FIXME qdo protocoloadm estiver homologado, verifique a necessidade
# de redirecionamento para o protocolo.
# complete e libere código abaixo para tal.
"""
self.instance.results['url'] = reverse(
'sapl.protocoloadm:...',
'sapl.protocoloadm:protocolo_mostrar',
kwargs={'pk': protocolo.pk})
"""
conteudo_gerado.numero_protocolo = protocolo.numero
conteudo_gerado.save()
@ -2060,7 +2175,7 @@ class EtiquetaPesquisaForm(forms.Form):
[('processo_inicial', 6),
('processo_final', 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Formulário de Etiqueta'),
@ -2145,7 +2260,7 @@ class FichaPesquisaForm(forms.Form):
('data_inicial', 3),
('data_final', 3)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Formulário de Ficha'),
@ -2186,7 +2301,7 @@ class FichaSelecionaForm(forms.Form):
row1 = to_row(
[('materia', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Selecione a ficha que deseja imprimir'),
@ -2251,12 +2366,12 @@ class ExcluirTramitacaoEmLote(forms.Form):
row1 = to_row(
[('data_tramitacao', 6),
('status', 6),])
('status', 6), ])
row2 = to_row(
[('unidade_tramitacao_local', 6),
('unidade_tramitacao_destino', 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Dados das Tramitações'),
row1,

35
sapl/materia/migrations/0034_auto_20190101_1618.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-01 18:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0033_auto_20181030_1039'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='materialegislativa',
name='ano_origem_externa',
field=models.PositiveSmallIntegerField(blank=True, choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], null=True, verbose_name='Ano'),
),
migrations.AlterField(
model_name='numeracao',
name='ano_materia',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='proposicao',
name='ano',
field=models.PositiveSmallIntegerField(blank=True, choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=None, null=True, verbose_name='Ano'),
),
]

35
sapl/materia/migrations/0034_auto_20190107_1402.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-07 16:02
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0033_auto_20181030_1039'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='materialegislativa',
name='ano_origem_externa',
field=models.PositiveSmallIntegerField(blank=True, choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], null=True, verbose_name='Ano'),
),
migrations.AlterField(
model_name='numeracao',
name='ano_materia',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='proposicao',
name='ano',
field=models.PositiveSmallIntegerField(blank=True, choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=None, null=True, verbose_name='Ano'),
),
]

35
sapl/materia/migrations/0035_auto_20190104_1021.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-04 12:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0034_auto_20190101_1618'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='materialegislativa',
name='ano_origem_externa',
field=models.PositiveSmallIntegerField(blank=True, choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], null=True, verbose_name='Ano'),
),
migrations.AlterField(
model_name='numeracao',
name='ano_materia',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='proposicao',
name='ano',
field=models.PositiveSmallIntegerField(blank=True, choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=None, null=True, verbose_name='Ano'),
),
]

41
sapl/materia/migrations/0036_auto_20190106_0330.py

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-06 05:30
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('materia', '0035_auto_20190104_1021'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='data_apresentacao',
field=models.DateField(verbose_name='Data de Apresentação'),
),
migrations.AlterField(
model_name='materialegislativa',
name='data_publicacao',
field=models.DateField(blank=True, null=True, verbose_name='Data de Publicação'),
),
migrations.AlterField(
model_name='materialegislativa',
name='local_origem_externa',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='materia.Origem', verbose_name='Local de Origem'),
),
migrations.AlterField(
model_name='materialegislativa',
name='numero_protocolo',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Número do Protocolo'),
),
migrations.AlterField(
model_name='materialegislativa',
name='tipo',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='materia.TipoMateriaLegislativa', verbose_name='Tipo de Matéria Legislativa'),
),
]

16
sapl/materia/migrations/0037_merge_20190108_1538.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2019-01-08 17:38
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0036_auto_20190106_0330'),
('materia', '0034_auto_20190107_1402'),
]
operations = [
]

35
sapl/materia/migrations/0038_auto_20190108_1606.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2019-01-08 18:06
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0037_merge_20190108_1538'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='materialegislativa',
name='ano_origem_externa',
field=models.PositiveSmallIntegerField(blank=True, choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], null=True, verbose_name='Ano'),
),
migrations.AlterField(
model_name='numeracao',
name='ano_materia',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='proposicao',
name='ano',
field=models.PositiveSmallIntegerField(blank=True, choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=None, null=True, verbose_name='Ano'),
),
]

21
sapl/materia/migrations/0039_auto_20190209_2346.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.18 on 2019-02-10 01:46
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('materia', '0038_auto_20190108_1606'),
]
operations = [
migrations.AlterField(
model_name='tipoproposicao',
name='content_type',
field=models.ForeignKey(default=None, help_text='\n Quando uma proposição é incorporada, ela é convertida de proposição\n para outro elemento dentro do Sapl. Existem alguns elementos que\n uma proposição pode se tornar. Defina este meta-tipo e em seguida\n escolha um Tipo Correspondente!\n ', on_delete=django.db.models.deletion.PROTECT, to='contenttypes.ContentType', verbose_name='Conversão de Meta-Tipos'),
),
]

20
sapl/materia/migrations/0040_auto_20190211_1602.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.18 on 2019-02-11 18:02
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0039_auto_20190209_2346'),
]
operations = [
migrations.AlterField(
model_name='tramitacao',
name='turno',
field=models.CharField(blank=True, choices=[('P', 'Primeiro'), ('S', 'Segundo'), ('U', 'Único'), ('L', 'Suplementar'), ('F', 'Final'), ('A', 'Votação única em Regime de Urgência'), ('B', '1ª Votação'), ('C', '2ª e 3ª Votação'), ('D', 'Deliberação'), ('E', '1ª e 2ª votações em regime de urgência')], max_length=1, verbose_name='Turno'),
),
]

20
sapl/materia/migrations/0041_proposicao_numero_materia_futuro.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-02-15 11:10
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0040_auto_20190211_1602'),
]
operations = [
migrations.AddField(
model_name='proposicao',
name='numero_materia_futuro',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Número Matéria'),
),
]

38
sapl/materia/models.py

@ -2,7 +2,6 @@
from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist,MultipleObjectsReturned
from django.db import models
from django.db.models.functions import Concat
from django.template import defaultfilters
@ -16,6 +15,7 @@ from sapl.comissoes.models import Comissao
from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
TextoArticulado)
from sapl.parlamentares.models import Parlamentar
#from sapl.protocoloadm.models import Protocolo
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey,
SaplGenericRelation, restringe_tipos_de_arquivo_txt,
texto_upload_path)
@ -42,13 +42,21 @@ class TipoProposicao(models.Model):
error_messages={
'unique': _('Já existe um Tipo de Proposição com esta descrição.')
})
content_type = models.ForeignKey(ContentType, default=None,
content_type = models.ForeignKey(
ContentType, default=None,
on_delete=models.PROTECT,
verbose_name=_('Definição de Tipo'))
verbose_name=_('Conversão de Meta-Tipos'),
help_text=_("""
Quando uma proposição é incorporada, ela é convertida de proposição
para outro elemento dentro do Sapl. Existem alguns elementos que
uma proposição pode se tornar. Defina este meta-tipo e em seguida
escolha um Tipo Correspondente!
""")
)
object_id = models.PositiveIntegerField(
blank=True, null=True, default=None)
tipo_conteudo_related = SaplGenericForeignKey(
'content_type', 'object_id', verbose_name=_('Seleção de Tipo'))
'content_type', 'object_id', verbose_name=_('Tipo Correspondente'))
perfis = models.ManyToManyField(
PerfilEstruturalTextoArticulado,
@ -142,15 +150,17 @@ def anexo_upload_path(instance, filename):
@reversion.register()
class MateriaLegislativa(models.Model):
tipo = models.ForeignKey(TipoMateriaLegislativa,
tipo = models.ForeignKey(
TipoMateriaLegislativa,
on_delete=models.PROTECT,
verbose_name=_('Tipo'))
verbose_name=TipoMateriaLegislativa._meta.verbose_name)
numero = models.PositiveIntegerField(verbose_name=_('Número'))
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'),
choices=RANGE_ANOS)
numero_protocolo = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Núm. Protocolo'))
data_apresentacao = models.DateField(verbose_name=_('Data Apresentação'))
blank=True, null=True, verbose_name=_('Número do Protocolo'))
data_apresentacao = models.DateField(
verbose_name=_('Data de Apresentação'))
tipo_apresentacao = models.CharField(
max_length=1, blank=True,
verbose_name=_('Tipo de Apresentação'),
@ -160,7 +170,7 @@ class MateriaLegislativa(models.Model):
on_delete=models.PROTECT,
verbose_name=_('Regime Tramitação'))
data_publicacao = models.DateField(
blank=True, null=True, verbose_name=_('Data Publicação'))
blank=True, null=True, verbose_name=_('Data de Publicação'))
tipo_origem_externa = models.ForeignKey(
TipoMateriaLegislativa,
blank=True,
@ -176,7 +186,7 @@ class MateriaLegislativa(models.Model):
blank=True, null=True, verbose_name=_('Data'))
local_origem_externa = models.ForeignKey(
Origem, blank=True, null=True,
on_delete=models.PROTECT, verbose_name=_('Local Origem'))
on_delete=models.PROTECT, verbose_name=_('Local de Origem'))
apelido = models.CharField(
max_length=50, blank=True, verbose_name=_('Apelido'))
dias_prazo = models.PositiveIntegerField(
@ -267,6 +277,8 @@ class MateriaLegislativa(models.Model):
if protocolo:
if protocolo.timestamp:
return protocolo.timestamp.date()
elif protocolo.timestamp_data_hora_manual:
return protocolo.timestamp_data_hora_manual.date()
elif protocolo.data:
return protocolo.data
@ -675,6 +687,9 @@ class Proposicao(models.Model):
numero_proposicao = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Número'))
numero_materia_futuro = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Número Matéria'))
hash_code = models.CharField(verbose_name=_('Código do Documento'),
max_length=200,
blank=True)
@ -904,6 +919,9 @@ class Tramitacao(models.Model):
('A', 'votacao_unica', _('Votação única em Regime de Urgência')),
('B', 'primeira_votacao', _('1ª Votação')),
('C', 'segunda_terceira_votacao', _('2ª e 3ª Votação')),
('D', 'deliberacao', _('Deliberação')),
('E', 'primeira_segunda_votacao_urgencia', _('1ª e 2ª votações em regime de urgência'))
)
status = models.ForeignKey(StatusTramitacao, on_delete=models.PROTECT,

11
sapl/materia/tests/test_materia.py

@ -1,10 +1,10 @@
import pytest
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.urlresolvers import reverse
from django.db.models import Max
from model_mommy import mommy
import pytest
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Comissao, TipoComissao
@ -528,11 +528,13 @@ def test_numeracao_materia_legislativa_por_legislatura(admin_client):
)
# Cria uma materia na legislatura1
tipo_materia = mommy.make(TipoMateriaLegislativa, id=1, sequencia_numeracao='L')
tipo_materia = mommy.make(TipoMateriaLegislativa,
id=1, sequencia_numeracao='L')
materia = mommy.make(MateriaLegislativa,
tipo=tipo_materia,
ano=2017,
numero=1
numero=1,
data_apresentacao='2017-03-05'
)
url = reverse('sapl.materia:recuperar_materia')
@ -556,7 +558,8 @@ def test_numeracao_materia_legislativa_por_legislatura(admin_client):
def test_numeracao_materia_legislativa_por_ano(admin_client):
# Cria uma materia
tipo_materia = mommy.make(TipoMateriaLegislativa, id=1, sequencia_numeracao='A')
tipo_materia = mommy.make(TipoMateriaLegislativa,
id=1, sequencia_numeracao='A')
materia = mommy.make(MateriaLegislativa,
tipo=tipo_materia,
ano=2017,

7
sapl/materia/urls.py

@ -26,6 +26,7 @@ from sapl.materia.views import (AcompanhamentoConfirmarView,
proposicao_texto, recuperar_materia,
ExcluirTramitacaoEmLoteView, RetornarProposicao)
from sapl.norma.views import NormaPesquisaSimplesView
from sapl.protocoloadm.views import (FichaPesquisaAdmView, FichaSelecionaAdmView)
from .apps import AppConfig
@ -47,6 +48,12 @@ urlpatterns_impressos = [
url(r'^materia/impressos/norma-pesquisa/$',
NormaPesquisaSimplesView.as_view(),
name='impressos_norma_pesquisa'),
url(r'^materia/impressos/ficha-pesquisa-adm/$',
FichaPesquisaAdmView.as_view(),
name= 'impressos_ficha_pesquisa_adm'),
url(r'^materia/impressos/ficha-seleciona-adm/$',
FichaSelecionaAdmView.as_view(),
name= 'impressos_ficha_seleciona_adm'),
]
urlpatterns_materia = [

85
sapl/materia/views.py

@ -3,7 +3,7 @@ import logging
from random import choice
from string import ascii_letters, digits
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML
from django.conf import settings
from django.contrib import messages
@ -26,7 +26,7 @@ import weasyprint
import sapl
from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa
from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig
from sapl.base.signals import tramitacao_signal
from sapl.comissoes.models import Comissao, Participacao
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT,
@ -211,7 +211,13 @@ class CriarProtocoloMateriaView(CreateView):
context['form'].fields['tipo'].initial = protocolo.tipo_materia
context['form'].fields['numero'].initial = numero
context['form'].fields['ano'].initial = protocolo.ano
if protocolo:
if protocolo.timestamp:
context['form'].fields['data_apresentacao'].initial = protocolo.timestamp.date()
elif protocolo.timestamp_data_hora_manual:
context['form'].fields['data_apresentacao'].initial = protocolo.timestamp_data_hora_manual.date()
elif protocolo.data:
context['form'].fields['data_apresentacao'].initial = protocolo.data
context['form'].fields['numero_protocolo'].initial = protocolo.numero
context['form'].fields['ementa'].initial = protocolo.assunto_ementa
@ -783,22 +789,33 @@ class ProposicaoCrud(Crud):
msg_error = _('Proposição não possui nenhum tipo de '
'Texto associado.')
else:
p.data_devolucao = None
p.data_envio = timezone.now()
p.save()
if p.texto_articulado.exists():
ta = p.texto_articulado.first()
ta.privacidade = STATUS_TA_IMMUTABLE_RESTRICT
ta.editing_locked = True
ta.save()
receber_recibo = BaseAppConfig.attr(
'receber_recibo_proposicao')
if not receber_recibo:
ta = p.texto_articulado.first()
p.hash_code = 'P' + ta.hash() + SEPARADOR_HASH_PROPOSICAO + str(p.pk)
p.data_devolucao = None
p.data_envio = timezone.now()
p.save()
messages.success(request, _(
'Proposição enviada com sucesso.'))
try:
self.logger.debug("user=" + username + ". Tentando obter número do objeto MateriaLegislativa com "
"atributos tipo={} e ano={}."
.format(p.tipo.tipo_conteudo_related, p.ano))
if p.numero_materia_futuro:
numero = p.numero_materia_futuro
else:
numero = MateriaLegislativa.objects.filter(tipo=p.tipo.tipo_conteudo_related,
ano=p.ano).last().numero + 1
messages.success(request, _(
@ -926,6 +943,7 @@ class ProposicaoCrud(Crud):
username = request.user.username
if proposicao:
msg = ''
if proposicao[0][0] and proposicao[0][1]:
self.logger.error('user=' + username + '. Proposição (id={}) já foi enviada e recebida.'
'Não pode mais ser editada'.format(kwargs['pk']))
@ -1336,7 +1354,7 @@ class TramitacaoCrud(MasterDetailCrud):
def montar_helper_documento_acessorio(self):
autor_row = montar_row_autor('autor')
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(*self.get_layout())
# Adiciona o novo campo 'autor' e mecanismo de busca
@ -1350,7 +1368,7 @@ def montar_helper_documento_acessorio(self):
# Adiciona novos botões dentro do form
self.helper.layout[0][3][0].insert(1, form_actions(more=[
HTML('<a href="{{ view.cancel_url }}"'
' class="btn btn-inverse">Cancelar</a>')]))
' class="btn btn-dark">Cancelar</a>')]))
class DocumentoAcessorioCrud(MasterDetailCrud):
@ -1747,12 +1765,16 @@ class MateriaLegislativaPesquisaView(FilterView):
kwargs = {'data': self.request.GET or None}
tipo_listagem = self.request.GET.get('tipo_listagem', '1')
tipo_listagem = '1' if not tipo_listagem else tipo_listagem
qs = self.get_queryset().distinct()
if tipo_listagem == '1':
status_tramitacao = self.request.GET.get('tramitacao__status')
unidade_destino = self.request.GET.get(
'tramitacao__unidade_tramitacao_destino')
qs = self.get_queryset().distinct()
if status_tramitacao and unidade_destino:
lista = filtra_tramitacao_destino_and_status(status_tramitacao,
unidade_destino)
@ -1766,9 +1788,6 @@ class MateriaLegislativaPesquisaView(FilterView):
lista = filtra_tramitacao_destino(unidade_destino)
qs = qs.filter(id__in=lista).distinct()
if 'o' in self.request.GET and not self.request.GET['o']:
qs = qs.order_by('-ano', 'tipo__sigla', '-numero')
qs = qs.prefetch_related("autoria_set",
"autoria_set__autor",
"numeracao_set",
@ -1782,6 +1801,15 @@ class MateriaLegislativaPesquisaView(FilterView):
"normajuridica_set",
"registrovotacao_set",
"documentoacessorio_set")
else:
qs = qs.prefetch_related("autoria_set",
"numeracao_set",
"autoria_set__autor",
"tipo",)
if 'o' in self.request.GET and not self.request.GET['o']:
qs = qs.order_by('-ano', 'tipo__sigla', '-numero')
kwargs.update({
'queryset': qs,
@ -1794,7 +1822,10 @@ class MateriaLegislativaPesquisaView(FilterView):
context['title'] = _('Pesquisar Matéria Legislativa')
self.filterset.form.fields['o'].label = _('Ordenação')
tipo_listagem = self.request.GET.get('tipo_listagem', '1')
tipo_listagem = '1' if not tipo_listagem else tipo_listagem
context['tipo_listagem'] = tipo_listagem
qr = self.request.GET.copy()
if 'page' in qr:
@ -1810,6 +1841,9 @@ class MateriaLegislativaPesquisaView(FilterView):
context['show_results'] = show_results_filter_set(qr)
context['USE_SOLR'] = settings.USE_SOLR if hasattr(
settings, 'USE_SOLR') else False
return context
@ -2039,24 +2073,41 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
if not request.POST['data_encaminhamento']:
data_encaminhamento = None
else:
try:
data_encaminhamento = tz.localize(datetime.strptime(
request.POST['data_encaminhamento'], "%d/%m/%Y"))
except ValueError:
msg = _('Formato da data de encaminhamento incorreto.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
if request.POST['data_fim_prazo'] == '':
data_fim_prazo = None
else:
try:
data_fim_prazo = tz.localize(datetime.strptime(
request.POST['data_fim_prazo'], "%d/%m/%Y"))
except ValueError:
msg = _('Formato da data fim do prazo incorreto.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
# issue https://github.com/interlegis/sapl/issues/1123
# TODO: usar Form
urgente = request.POST['urgente'] == 'True'
flag_error = False
for materia_id in marcadas:
try:
data_tramitacao = tz.localize(datetime.strptime(
request.POST['data_tramitacao'], "%d/%m/%Y"))
except ValueError:
msg = _('Formato da data da tramitação incorreto.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
t = Tramitacao(
materia_id=materia_id,
data_tramitacao=tz.localize(datetime.strptime(
request.POST['data_tramitacao'], "%d/%m/%Y")),
data_tramitacao=data_tramitacao,
data_encaminhamento=data_encaminhamento,
data_fim_prazo=data_fim_prazo,
unidade_tramitacao_local_id=request.POST[
@ -2135,7 +2186,7 @@ class ImpressosView(PermissionRequiredMixin, TemplateView):
def gerar_pdf_impressos(request, context, template_name):
template = loader.get_template(template_name)
html = template.render(RequestContext(request, context))
html = template.render(context, request)
pdf = weasyprint.HTML(string=html, base_url=request.build_absolute_uri()
).write_pdf()

92
sapl/norma/forms.py

@ -1,29 +1,29 @@
import django_filters
import logging
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import Fieldset, Layout
from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import models
from django.db.models import Q
from django.forms import ModelForm, widgets, ModelChoiceField
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.materia.forms import choice_anos_com_materias
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import NormaPesquisaOrderingFilter, RANGE_ANOS, RangeWidgetOverride
from sapl.utils import NormaPesquisaOrderingFilter, RangeWidgetOverride, \
choice_anos_com_normas, FilterOverridesMetaMixin, FileFieldCheckMixin
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, AutoriaNorma)
def ANO_CHOICES():
return [('', '---------')] + RANGE_ANOS
def get_esferas():
return [('E', 'Estadual'),
('F', 'Federal'),
@ -41,18 +41,13 @@ ORDENACAO_CHOICES = [('', '---------'),
class NormaFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
ano = django_filters.ChoiceFilter(required=False,
label='Ano',
choices=ANO_CHOICES)
choices=choice_anos_com_normas)
ementa = django_filters.CharFilter(lookup_expr='icontains')
ementa = django_filters.CharFilter(
method='filter_ementa',
label=_('Pesquisar expressões na ementa da norma'))
indexacao = django_filters.CharFilter(lookup_expr='icontains',
label=_('Indexação'))
@ -60,9 +55,9 @@ class NormaFilterSet(django_filters.FilterSet):
assuntos = django_filters.ModelChoiceFilter(
queryset=AssuntoNorma.objects.all())
o = NormaPesquisaOrderingFilter()
o = NormaPesquisaOrderingFilter(help_text='')
class Meta:
class Meta(FilterOverridesMetaMixin):
model = NormaJuridica
fields = ['tipo', 'numero', 'ano', 'data', 'data_vigencia',
'data_publicacao', 'ementa', 'assuntos']
@ -74,9 +69,9 @@ class NormaFilterSet(django_filters.FilterSet):
row2 = to_row([('data', 6), ('data_publicacao', 6)])
row3 = to_row([('ementa', 6), ('assuntos', 6)])
row4 = to_row([('data_vigencia', 12)])
row5 = to_row([('o',6), ('indexacao', 6)])
row5 = to_row([('o', 6), ('indexacao', 6)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Norma'),
@ -84,8 +79,16 @@ class NormaFilterSet(django_filters.FilterSet):
form_actions(label='Pesquisar'))
)
def filter_ementa(self, queryset, name, value):
texto = value.split()
q = Q()
for t in texto:
q &= Q(ementa__icontains=t)
return queryset.filter(q)
class NormaJuridicaForm(ModelForm):
class NormaJuridicaForm(FileFieldCheckMixin, ModelForm):
# Campos de MateriaLegislativa
tipo_materia = forms.ModelChoiceField(
@ -103,7 +106,7 @@ class NormaJuridicaForm(ModelForm):
ano_materia = forms.ChoiceField(
label='Ano Matéria',
required=False,
choices=ANO_CHOICES,
choices=choice_anos_com_materias,
widget=forms.Select(attrs={'autocomplete': 'off'})
)
@ -132,7 +135,6 @@ class NormaJuridicaForm(ModelForm):
'assuntos']
widgets = {'assuntos': widgets.CheckboxSelectMultiple}
def clean(self):
cleaned_data = super(NormaJuridicaForm, self).clean()
@ -143,8 +145,10 @@ class NormaJuridicaForm(ModelForm):
import re
has_digits = re.sub('[^0-9]', '', cleaned_data['numero'])
if not has_digits:
self.logger.error("Número de norma ({}) não pode conter somente letras.".format(cleaned_data['numero']))
raise ValidationError('Número de norma não pode conter somente letras')
self.logger.error("Número de norma ({}) não pode conter somente letras.".format(
cleaned_data['numero']))
raise ValidationError(
'Número de norma não pode conter somente letras')
if self.instance.numero != cleaned_data['numero']:
norma = NormaJuridica.objects.filter(ano=cleaned_data['ano'],
@ -196,11 +200,14 @@ class NormaJuridicaForm(ModelForm):
return cleaned_data
def clean_texto_integral(self):
super(NormaJuridicaForm, self).clean()
texto_integral = self.cleaned_data.get('texto_integral', False)
if texto_integral and texto_integral.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
tam_fornecido = str( texto_integral.size / (1024*1024) )
self.logger.error("Arquivo muito grande ({}MB). ( Tamanho máximo permitido: {}MB )".format(tam_fornecido, max_size))
tam_fornecido = str(texto_integral.size / (1024 * 1024))
self.logger.error("Arquivo muito grande ({}MB). ( Tamanho máximo permitido: {}MB )".format(
tam_fornecido, max_size))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))
return texto_integral
@ -233,7 +240,7 @@ class AutoriaNormaForm(ModelForm):
('autor', 4),
('primeiro_autor', 4)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Autoria'),
row1, 'data_relativa', form_actions(label='Salvar')))
@ -257,12 +264,14 @@ class AutoriaNormaForm(ModelForm):
if ((not pk and autorias.exists()) or
(pk and autorias.exclude(pk=pk).exists())):
self.logger.error("Autor ({}) já foi cadastrado.".format(cd['autor']))
self.logger.error(
"Autor ({}) já foi cadastrado.".format(cd['autor']))
raise ValidationError(_('Esse Autor já foi cadastrado.'))
return cd
class AnexoNormaJuridicaForm(ModelForm):
class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm):
class Meta:
model = AnexoNormaJuridica
fields = ['norma', 'anexo_arquivo', 'assunto_anexo']
@ -271,6 +280,7 @@ class AnexoNormaJuridicaForm(ModelForm):
}
logger = logging.getLogger(__name__)
def clean(self):
cleaned_data = super(AnexoNormaJuridicaForm, self).clean()
if not self.is_valid():
@ -278,8 +288,9 @@ class AnexoNormaJuridicaForm(ModelForm):
anexo_arquivo = self.cleaned_data.get('anexo_arquivo', False)
if anexo_arquivo and anexo_arquivo.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
tam_fornecido = str( anexo_arquivo.size / (1024*1024) )
self.logger.error("Arquivo muito grande ({}MB). ( Tamanho máximo permitido: {}MB )".format(tam_fornecido, max_size))
tam_fornecido = str(anexo_arquivo.size / (1024 * 1024))
self.logger.error("Arquivo muito grande ({}MB). ( Tamanho máximo permitido: {}MB )".format(
tam_fornecido, max_size))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))
return cleaned_data
@ -295,7 +306,6 @@ class AnexoNormaJuridicaForm(ModelForm):
return anexo
class NormaRelacionadaForm(ModelForm):
tipo = forms.ModelChoiceField(
@ -311,6 +321,7 @@ class NormaRelacionadaForm(ModelForm):
widget=forms.Textarea(attrs={'disabled': 'disabled'}))
logger = logging.getLogger(__name__)
class Meta:
model = NormaRelacionada
fields = ['tipo', 'numero', 'ano', 'ementa', 'tipo_vinculo']
@ -326,17 +337,20 @@ class NormaRelacionadaForm(ModelForm):
cleaned_data = self.cleaned_data
try:
self.logger.debug("Tentando obter objeto NormaJuridica com numero={}, ano={}, tipo={}.".format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
self.logger.debug("Tentando obter objeto NormaJuridica com numero={}, ano={}, tipo={}.".format(
cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
norma_relacionada = NormaJuridica.objects.get(
numero=cleaned_data['numero'],
ano=cleaned_data['ano'],
tipo=cleaned_data['tipo'])
except ObjectDoesNotExist:
self.logger.info("NormaJuridica com numero={}, ano={}, tipo={} não existe.".format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
self.logger.info("NormaJuridica com numero={}, ano={}, tipo={} não existe.".format(
cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
msg = _('A norma a ser relacionada não existe.')
raise ValidationError(msg)
else:
self.logger.info("NormaJuridica com numero={}, ano={}, tipo={} obtida com sucesso.".format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
self.logger.info("NormaJuridica com numero={}, ano={}, tipo={} obtida com sucesso.".format(
cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
cleaned_data['norma_relacionada'] = norma_relacionada
return cleaned_data
@ -344,6 +358,7 @@ class NormaRelacionadaForm(ModelForm):
def save(self, commit=False):
relacionada = super(NormaRelacionadaForm, self).save(commit)
relacionada.norma_relacionada = self.cleaned_data['norma_relacionada']
if relacionada.tipo_vinculo.revoga_integralmente:
relacionada.norma_relacionada.data_vigencia = relacionada.norma_principal.data
relacionada.norma_relacionada.save()
relacionada.save()
@ -387,7 +402,7 @@ class NormaPesquisaSimplesForm(forms.Form):
row2 = to_row(
[('titulo', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Índice de Normas'),
@ -409,7 +424,8 @@ class NormaPesquisaSimplesForm(forms.Form):
if (data_inicial and data_final and
data_inicial > data_final):
self.logger.error("Data Final ({}) menor que a Data Inicial ({}).".format(data_final, data_inicial))
self.logger.error("Data Final ({}) menor que a Data Inicial ({}).".format(
data_final, data_inicial))
raise ValidationError(_(
'A Data Final não pode ser menor que a Data Inicial'))
else:

25
sapl/norma/migrations/0017_normaestatisticas.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-12-17 18:44
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('norma', '0016_tipovinculonormajuridica_revoga_integramente'),
]
operations = [
migrations.CreateModel(
name='NormaEstatisticas',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('usuario', models.CharField(max_length=50)),
('horario_acesso', models.DateTimeField(auto_now=True, null=True)),
('norma', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='norma.NormaJuridica')),
],
),
]

25
sapl/norma/migrations/0018_auto_20190101_1618.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-01 18:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0017_normaestatisticas'),
]
operations = [
migrations.AlterField(
model_name='anexonormajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='normajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
]

20
sapl/norma/migrations/0018_normaestatisticas_ano.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-12-19 18:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0017_normaestatisticas'),
]
operations = [
migrations.AddField(
model_name='normaestatisticas',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=2018, verbose_name='Ano'),
),
]

20
sapl/norma/migrations/0019_auto_20181219_1807.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-12-19 20:07
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0018_normaestatisticas_ano'),
]
operations = [
migrations.AlterField(
model_name='normaestatisticas',
name='horario_acesso',
field=models.DateTimeField(blank=True, null=True),
),
]

25
sapl/norma/migrations/0019_auto_20190104_1021.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-04 12:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0018_auto_20190101_1618'),
]
operations = [
migrations.AlterField(
model_name='anexonormajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='normajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
]

31
sapl/norma/migrations/0020_auto_20190106_0454.py

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-06 06:54
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('norma', '0019_auto_20190104_1021'),
]
operations = [
migrations.AlterField(
model_name='normajuridica',
name='data_publicacao',
field=models.DateField(blank=True, null=True, verbose_name='Data de Publicação'),
),
migrations.AlterField(
model_name='normajuridica',
name='tipo',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='norma.TipoNormaJuridica', verbose_name='Tipo da Norma Jurídica'),
),
migrations.AlterField(
model_name='normajuridica',
name='veiculo_publicacao',
field=models.CharField(blank=True, max_length=30, verbose_name='Veículo de Publicação'),
),
]

31
sapl/norma/migrations/0020_auto_20190107_1407.py

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-07 16:07
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.norma.models
class Migration(migrations.Migration):
dependencies = [
('norma', '0019_auto_20181219_1807'),
]
operations = [
migrations.AlterField(
model_name='anexonormajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='normaestatisticas',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=sapl.norma.models.get_ano_atual, verbose_name='Ano'),
),
migrations.AlterField(
model_name='normajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
]

16
sapl/norma/migrations/0021_merge_20190108_1538.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2019-01-08 17:38
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('norma', '0020_auto_20190107_1407'),
('norma', '0020_auto_20190106_0454'),
]
operations = [
]

21
sapl/norma/migrations/0022_auto_20190108_1606.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2019-01-08 18:06
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.norma.models
class Migration(migrations.Migration):
dependencies = [
('norma', '0021_merge_20190108_1538'),
]
operations = [
migrations.AlterField(
model_name='normaestatisticas',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=sapl.norma.models.get_ano_atual, verbose_name='Ano'),
),
]

19
sapl/norma/migrations/0023_auto_20190219_1535.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-02-19 18:35
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('norma', '0022_auto_20190108_1606'),
]
operations = [
migrations.AlterModelOptions(
name='normarelacionada',
options={'ordering': ('norma_principal__ano', 'norma_relacionada__ano'), 'verbose_name': 'Norma Relacionada', 'verbose_name_plural': 'Normas Relacionadas'},
),
]

40
sapl/norma/models.py

@ -2,6 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.template import defaultfilters
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from model_utils import Choices
import reversion
@ -83,7 +84,7 @@ class NormaJuridica(models.Model):
tipo = models.ForeignKey(
TipoNormaJuridica,
on_delete=models.PROTECT,
verbose_name=_('Tipo da Norma Juridica'))
verbose_name=_('Tipo da Norma Jurídica'))
materia = models.ForeignKey(
MateriaLegislativa, blank=True, null=True,
on_delete=models.PROTECT, verbose_name=_('Matéria'))
@ -98,11 +99,11 @@ class NormaJuridica(models.Model):
choices=ESFERA_FEDERACAO_CHOICES)
data = models.DateField(blank=False, null=True, verbose_name=_('Data'))
data_publicacao = models.DateField(
blank=True, null=True, verbose_name=_('Data Publicação'))
blank=True, null=True, verbose_name=_('Data de Publicação'))
veiculo_publicacao = models.CharField(
max_length=30,
blank=True,
verbose_name=_('Veículo Publicação'))
verbose_name=_('Veículo de Publicação'))
pagina_inicio_publicacao = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Pg. Início'))
pagina_fim_publicacao = models.PositiveIntegerField(
@ -119,7 +120,8 @@ class NormaJuridica(models.Model):
assuntos = models.ManyToManyField(
AssuntoNorma, blank=True,
verbose_name=_('Assuntos'))
data_vigencia = models.DateField(blank=True, null=True, verbose_name=_('Data Fim Vigência'))
data_vigencia = models.DateField(
blank=True, null=True, verbose_name=_('Data Fim Vigência'))
timestamp = models.DateTimeField(null=True)
texto_articulado = GenericRelation(
@ -143,9 +145,11 @@ class NormaJuridica(models.Model):
def get_normas_relacionadas(self):
principais = NormaRelacionada.objects.filter(
norma_principal=self.id)
norma_principal=self.id).order_by('norma_principal__ano',
'norma_relacionada__ano')
relacionadas = NormaRelacionada.objects.filter(
norma_relacionada=self.id)
norma_relacionada=self.id).order_by('norma_principal__ano',
'norma_relacionada__ano')
return (principais, relacionadas)
def get_anexos_norma_juridica(self):
@ -191,6 +195,24 @@ class NormaJuridica(models.Model):
update_fields=update_fields)
def get_ano_atual():
return timezone.now().year
class NormaEstatisticas(models.Model):
usuario = models.CharField(max_length=50)
horario_acesso = models.DateTimeField(
blank=True, null=True)
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'),
choices=RANGE_ANOS, default=get_ano_atual)
norma = models.ForeignKey(NormaJuridica,
on_delete=models.CASCADE)
def __str__(self):
return _('Usuário: %(usuario)s, Norma: %(norma)s') % {
'usuario': self.usuario, 'norma': self.norma}
@reversion.register()
class AutoriaNorma(models.Model):
autor = models.ForeignKey(Autor,
@ -213,6 +235,7 @@ class AutoriaNorma(models.Model):
return _('Autoria: %(autor)s - %(norma)s') % {
'autor': self.autor, 'norma': self.norma}
@reversion.register()
class LegislacaoCitada(models.Model):
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE)
@ -290,6 +313,7 @@ class NormaRelacionada(models.Model):
class Meta:
verbose_name = _('Norma Relacionada')
verbose_name_plural = _('Normas Relacionadas')
ordering = ('norma_principal__ano', 'norma_relacionada__ano')
def __str__(self):
return _('Principal: %(norma_principal)s'
@ -306,8 +330,8 @@ class AnexoNormaJuridica(models.Model):
on_delete=models.PROTECT,
verbose_name=_('Norma Juridica'))
assunto_anexo = models.TextField(
blank = True,
default = "",
blank=True,
default="",
verbose_name=_('Assunto do Anexo'),
max_length=250
)

16
sapl/norma/tests/test_norma.py

@ -1,8 +1,9 @@
import pytest
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from model_mommy import mommy
import pytest
from sapl.base.models import AppConfig
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.norma.forms import (NormaJuridicaForm, NormaPesquisaSimplesForm,
NormaRelacionadaForm)
@ -15,6 +16,7 @@ def test_incluir_norma_submit(admin_client):
tipo = mommy.make(TipoNormaJuridica,
sigla='T',
descricao='Teste')
config = mommy.make(AppConfig)
# Testa POST
response = admin_client.post(reverse('sapl.norma:normajuridica_create'),
@ -79,6 +81,15 @@ def test_norma_juridica_materia_inexistente():
tipo = mommy.make(TipoNormaJuridica)
tipo_materia = mommy.make(TipoMateriaLegislativa, descricao='VETO')
# cria uma matéria qualquer em 2017 pois, no teste, o campo ano_materia
# está vazio
materia = mommy.make(MateriaLegislativa,
tipo=tipo_materia,
ano=2017,
numero=1,
data_apresentacao='2017-03-05'
)
form = NormaJuridicaForm(data={'tipo': str(tipo.pk),
'numero': '1',
'ano': '2017',
@ -92,7 +103,8 @@ def test_norma_juridica_materia_inexistente():
assert not form.is_valid()
assert form.errors['__all__'] == [_("Matéria Legislativa 2/2017 (VETO) é inexistente.")]
assert form.errors['__all__'] == [
_("Matéria Legislativa 2/2017 (VETO) é inexistente.")]
@pytest.mark.django_db(transaction=False)

27
sapl/norma/views.py

@ -1,6 +1,5 @@
import re
import logging
import re
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
@ -14,6 +13,8 @@ from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView
from django_filters.views import FilterView
import weasyprint
from sapl import settings
import sapl
from sapl.base.models import AppConfig
from sapl.compilacao.views import IntegracaoTaView
@ -24,7 +25,7 @@ from sapl.utils import show_results_filter_set
from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm,
NormaPesquisaSimplesForm, NormaRelacionadaForm, AutoriaNormaForm)
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, TipoVinculoNormaJuridica, AutoriaNorma)
TipoNormaJuridica, TipoVinculoNormaJuridica, AutoriaNorma, NormaEstatisticas)
# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '')
@ -107,6 +108,8 @@ class NormaPesquisaView(FilterView):
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
context['USE_SOLR'] = settings.USE_SOLR if hasattr(
settings, 'USE_SOLR') else False
return context
@ -190,7 +193,14 @@ class NormaCrud(Crud):
return reverse('%s:%s' % (namespace, 'norma_pesquisa'))
class DetailView(Crud.DetailView):
pass
def get(self, request, *args, **kwargs):
estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas
if estatisticas_acesso_normas == 'S':
NormaEstatisticas.objects.create(usuario=str(self.request.user),
norma_id=kwargs['pk'],
ano=timezone.now().year,
horario_acesso=timezone.now())
return super().get(request, *args, **kwargs)
class DeleteView(Crud.DeleteView):
@ -210,12 +220,14 @@ class NormaCrud(Crud):
username = self.request.user.username
try:
self.logger.debug('user=' + username + '. Tentando obter objeto de modelo da esfera da federação.')
self.logger.debug(
'user=' + username + '. Tentando obter objeto de modelo da esfera da federação.')
esfera = sapl.base.models.AppConfig.objects.last(
).esfera_federacao
self.initial['esfera_federacao'] = esfera
except:
self.logger.error('user=' + username + '. Erro ao obter objeto de modelo da esfera da federação.')
self.logger.error(
'user=' + username + '. Erro ao obter objeto de modelo da esfera da federação.')
pass
self.initial['complemento'] = False
return self.initial
@ -324,6 +336,7 @@ class AutoriaNormaCrud(MasterDetailCrud):
})
return initial
class ImpressosView(PermissionRequiredMixin, TemplateView):
template_name = 'materia/impressos/impressos.html'
permission_required = ('materia.can_access_impressos', )
@ -331,7 +344,7 @@ class ImpressosView(PermissionRequiredMixin, TemplateView):
def gerar_pdf_impressos(request, context, template_name):
template = loader.get_template(template_name)
html = template.render(RequestContext(request, context))
html = template.render(context, request)
pdf = weasyprint.HTML(string=html, base_url=request.build_absolute_uri()
).write_pdf()

39
sapl/parlamentares/forms.py

@ -1,8 +1,7 @@
import logging
from datetime import timedelta
import logging
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import Fieldset, Layout
from django import forms
from django.contrib.auth import get_user_model
@ -16,6 +15,7 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from floppyforms.widgets import ClearableFileInput
from image_cropping.widgets import CropWidget, ImageCropWidget
from sapl.utils import FileFieldCheckMixin
from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import form_actions, to_row
@ -66,7 +66,8 @@ def validar_datas_legislatura(eleicao, inicio, fim, pk=None):
# Verifica se há alguma outra data de eleição cadastrada
if Legislatura.objects.filter(
data_eleicao=eleicao).exclude(pk=pk).exists():
logger.error("Esta data de eleição ({}) já foi cadastrada.".format(eleicao))
logger.error(
"Esta data de eleição ({}) já foi cadastrada.".format(eleicao))
msg_error = _('Esta data de eleição já foi cadastrada')
return (False, msg_error)
@ -75,6 +76,7 @@ def validar_datas_legislatura(eleicao, inicio, fim, pk=None):
class MandatoForm(ModelForm):
logger = logging.getLogger(__name__)
class Meta:
model = Mandato
fields = ['legislatura', 'coligacao', 'votos_recebidos',
@ -158,7 +160,6 @@ class LegislaturaForm(ModelForm):
if not self.is_valid():
return self.cleaned_data
numero = data['numero']
data_inicio = data['data_inicio']
data_fim = data['data_fim']
@ -166,22 +167,23 @@ class LegislaturaForm(ModelForm):
pk = self.instance.pk
ultima_legislatura = Legislatura.objects.filter(data_inicio__lt=data_inicio
).order_by('-data_inicio').first()
).order_by('-data_inicio').exclude(id=pk).first()
proxima_legislatura = Legislatura.objects.filter(data_fim__gt=data_fim
).order_by('data_fim').first()
).order_by('data_fim').exclude(id=pk).first()
if ultima_legislatura and ultima_legislatura.numero >= numero:
self.logger.error("Número ({}) deve ser maior que o da legislatura anterior ({})."
.format(numero, ultima_legislatura.numero))
raise ValidationError(_("Número deve ser maior que o da legislatura anterior"))
raise ValidationError(_("Número deve ser maior que o da legislatura anterior ({})."
.format(numero)))
elif proxima_legislatura and proxima_legislatura.numero <= numero:
self.logger.error("O Número ({}) deve ser menor que {}, pois existe uma "
"legislatura afrente cronologicamente desta que está sendo criada!"
"legislatura cronologicamente à frente desta que está sendo criada!"
.format(numero, proxima_legislatura.numero))
msg_erro = "O Número deve ser menor que {}, pois existe uma " \
"legislatura afrente cronologicamente desta que está sendo criada!"
"legislatura cronologicamente à frente desta que está sendo criada!"
msg_erro = msg_erro.format(proxima_legislatura.numero)
raise ValidationError(_(msg_erro))
@ -195,7 +197,7 @@ class LegislaturaForm(ModelForm):
return data
class ParlamentarForm(ModelForm):
class ParlamentarForm(FileFieldCheckMixin, ModelForm):
class Meta:
model = Parlamentar
@ -241,7 +243,7 @@ class ParlamentarCreateForm(ParlamentarForm):
data_expedicao_diploma=self.cleaned_data['data_expedicao_diploma'])
content_type = ContentType.objects.get_for_model(Parlamentar)
object_id = parlamentar.pk
tipo = TipoAutor.objects.get(descricao='Parlamentar')
tipo = TipoAutor.objects.get(content_type=content_type)
Autor.objects.create(
content_type=content_type,
object_id=object_id,
@ -385,6 +387,7 @@ class ComposicaoColigacaoForm(ModelForm):
class FrenteForm(ModelForm):
logger = logging.getLogger(__name__)
def __init__(self, *args, **kwargs):
super(FrenteForm, self).__init__(*args, **kwargs)
self.fields['parlamentares'].queryset = Parlamentar.objects.filter(
@ -404,8 +407,9 @@ class FrenteForm(ModelForm):
if cd['data_extincao'] and cd['data_criacao'] >= cd['data_extincao']:
self.logger.error("Data Dissolução ({}) não pode ser anterior a Data Criação ({})."
.format(cd['data_extincao'],cd['data_criacao']))
raise ValidationError(_("Data Dissolução não pode ser anterior a Data Criação"))
.format(cd['data_extincao'], cd['data_criacao']))
raise ValidationError(
_("Data Dissolução não pode ser anterior a Data Criação"))
return cd
@ -443,7 +447,7 @@ class VotanteForm(ModelForm):
def __init__(self, *args, **kwargs):
row1 = to_row([('username', 4)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Votante'),
row1, form_actions(label='Salvar'))
@ -466,7 +470,8 @@ class VotanteForm(ModelForm):
username = cd['username']
user = get_user_model().objects.filter(username=username)
if not user.exists():
self.logger.error("Não foi possível vincular usuário. Usuário {} não existe.".format(username))
self.logger.error(
"Não foi possível vincular usuário. Usuário {} não existe.".format(username))
raise ValidationError(_(
"{} [{}] {}".format(
'Não foi possível vincular usuário. Usuário',

6
sapl/parlamentares/models.py

@ -1,10 +1,10 @@
import reversion
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from image_cropping.fields import ImageCropField, ImageRatioField
from model_utils import Choices
import reversion
from sapl.base.models import Autor
from sapl.decorators import vigencia_atual
@ -28,10 +28,14 @@ class Legislatura(models.Model):
def atual(self):
current_year = timezone.now().year
if not self.data_fim:
self.data_fim = timezone.now().date()
return self.data_inicio.year <= current_year <= self.data_fim.year
@vigencia_atual
def __str__(self):
if not self.data_fim:
self.data_fim = timezone.now().date()
return _('%(numero)sª (%(start)s - %(end)s)') % {
'numero': self.numero,
'start': self.data_inicio.year,

72
sapl/parlamentares/tests/test_parlamentares.py

@ -237,6 +237,14 @@ def test_legislatura_form_invalido():
@pytest.mark.django_db(transaction=False)
def test_legislatura_form_datas_invalidas():
legislatura_form = LegislaturaForm(data={'numero': '1',
'data_inicio': '2017-02-01',
'data_fim': '2021-12-31',
'data_eleicao': '2016-11-01'
})
assert legislatura_form.is_valid()
legislatura_form = LegislaturaForm(data={'numero': '1',
'data_inicio': '2017-02-01',
'data_fim': '2021-12-31',
@ -261,6 +269,70 @@ def test_legislatura_form_datas_invalidas():
assert legislatura_form.errors['__all__'] == [expected]
@pytest.mark.django_db(transaction=False)
def test_legislatura_form_numeros_invalidos():
legislatura_form = LegislaturaForm(data={'numero': '5',
'data_inicio': '2017-02-01',
'data_fim': '2021-12-31',
'data_eleicao': '2016-11-01'
})
assert legislatura_form.is_valid()
legislatura = mommy.make(Legislatura, pk=1,
numero=5,
data_inicio='2017-02-01',
data_fim='2021-12-31',
data_eleicao='2016-11-01')
legislatura_form = LegislaturaForm(data={'numero': '6',
'data_inicio': '2014-02-01',
'data_fim': '2016-12-31',
'data_eleicao': '2013-11-01'
})
assert not legislatura_form.is_valid()
legislatura_form = LegislaturaForm(data={'numero': '4',
'data_inicio': '2022-02-01',
'data_fim': '2025-12-31',
'data_eleicao': '2021-11-01'
})
assert not legislatura_form.is_valid()
legislatura_form = LegislaturaForm(data={'numero': '5',
'data_inicio': '2014-02-01',
'data_fim': '2016-12-31',
'data_eleicao': '2013-11-01'
})
legislatura_form.instance = legislatura
assert legislatura_form.is_valid()
legislatura = mommy.make(Legislatura, pk=2,
numero=1,
data_inicio='2002-02-01',
data_fim='2005-12-31',
data_eleicao='2001-11-01')
legislatura2 = mommy.make(Legislatura, pk=3,
numero=3,
data_inicio='2008-02-01',
data_fim='2011-12-31',
data_eleicao='2007-11-01')
legislatura_form = LegislaturaForm(data={'numero': '1',
'data_inicio': '2010-02-01',
'data_fim': '2013-12-31',
'data_eleicao': '2009-11-01'
})
legislatura_form.instance = legislatura
assert not legislatura_form.is_valid()
@pytest.mark.django_db(transaction=False)
def test_valida_campos_obrigatorios_frente_form():
form = FrenteForm(data={})

134
sapl/parlamentares/views.py

@ -1,6 +1,6 @@
from datetime import datetime
import json
import logging
from datetime import datetime
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
@ -17,8 +17,9 @@ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import FormView
from django.views.generic.edit import UpdateView
from image_cropping.utils import get_backend
from sapl.base.forms import SessaoLegislativaForm
from sapl.base.forms import SessaoLegislativaForm, PartidoForm
from sapl.base.models import Autor
from sapl.comissoes.models import Participacao
from sapl.crud.base import (RP_CHANGE, RP_DETAIL, RP_LIST, Crud, CrudAux,
@ -35,8 +36,8 @@ from .models import (CargoMesa, Coligacao, ComposicaoColigacao, ComposicaoMesa,
NivelInstrucao, Parlamentar, Partido, SessaoLegislativa,
SituacaoMilitar, TipoAfastamento, TipoDependente, Votante)
CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa')
PartidoCrud = CrudAux.build(Partido, 'partidos')
TipoDependenteCrud = CrudAux.build(TipoDependente, 'tipo_dependente')
NivelInstrucaoCrud = CrudAux.build(NivelInstrucao, 'nivel_instrucao')
TipoAfastamentoCrud = CrudAux.build(TipoAfastamento, 'tipo_afastamento')
@ -45,6 +46,7 @@ TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar')
DependenteCrud = MasterDetailCrud.build(
Dependente, 'parlamentar', 'dependente')
class SessaoLegislativaCrud(CrudAux):
model = SessaoLegislativa
@ -54,6 +56,17 @@ class SessaoLegislativaCrud(CrudAux):
class UpdateView(CrudAux.UpdateView):
form_class = SessaoLegislativaForm
class PartidoCrud(CrudAux):
model = Partido
class CreateView(CrudAux.CreateView):
form_class = PartidoForm
class UpdateView(CrudAux.UpdateView):
form_class = PartidoForm
class VotanteView(MasterDetailCrud):
model = Votante
parent_field = 'parlamentar'
@ -89,6 +102,7 @@ class FrenteList(MasterDetailCrud):
class BaseMixin(Crud.PublicMixin, MasterDetailCrud.BaseMixin):
list_field_names = ['nome', 'data_criacao', 'data_extincao']
@classmethod
def url_name(cls, suffix):
return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
@ -276,24 +290,33 @@ def parlamentares_frente_selected(request):
logger = logging.getLogger(__name__)
username = request.user.username
try:
logger.info("user=" + username + ". Tentando objet objeto Frente com id={}.".format(request.GET['frente_id']))
logger.info("user=" + username +
". Tentando objet objeto Frente com id={}.".format(request.GET['frente_id']))
frente = Frente.objects.get(id=int(request.GET['frente_id']))
except ObjectDoesNotExist:
logger.error("user=" + username + ". Frente buscada (id={}) não existe. Retornada lista vazia.".format(request.GET['frente_id']))
logger.error("user=" + username +
". Frente buscada (id={}) não existe. Retornada lista vazia.".format(request.GET['frente_id']))
lista_parlamentar_id = []
else:
logger.info("user=" + username + ". Frente (id={}) encontrada com sucesso.".format(request.GET['frente_id']))
logger.info("user=" + username +
". Frente (id={}) encontrada com sucesso.".format(request.GET['frente_id']))
lista_parlamentar_id = frente.parlamentares.all().values_list(
'id', flat=True)
return JsonResponse({'id_list': list(lista_parlamentar_id)})
class FrenteCrud(CrudAux):
class FrenteCrud(Crud):
model = Frente
help_topic = 'tipo_situa_militar'
public = [RP_DETAIL, RP_LIST]
list_field_names = ['nome', 'data_criacao', 'data_extincao', 'parlamentares']
list_field_names = ['nome', 'data_criacao',
'data_extincao', 'parlamentares']
class BaseMixin(Crud.BaseMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['subnav_template_name'] = ''
return context
class CreateView(Crud.CreateView):
form_class = FrenteForm
@ -305,7 +328,6 @@ class FrenteCrud(CrudAux):
form_class = FrenteForm
class MandatoCrud(MasterDetailCrud):
model = Mandato
parent_field = 'parlamentar'
@ -371,11 +393,13 @@ class LegislaturaCrud(CrudAux):
def get_initial(self):
username = self.request.user.username
try:
self.logger.error("user=" + username + ". Tentando obter última Legislatura.")
self.logger.error("user=" + username +
". Tentando obter última Legislatura.")
ultima_legislatura = Legislatura.objects.latest('numero')
numero = ultima_legislatura.numero + 1
except Legislatura.DoesNotExist:
self.logger.error("user=" + username + ". Legislatura não encontrada. Número definido como 1.")
self.logger.error(
"user=" + username + ". Legislatura não encontrada. Número definido como 1.")
numero = 1
return {'numero': numero}
@ -450,6 +474,10 @@ class ParlamentarCrud(Crud):
layout_key = 'ParlamentarUpdate'
def render_to_response(self, context, **response_kwargs):
context['form'].helper.include_media = False
return super().render_to_response(context, **response_kwargs)
class CreateView(Crud.CreateView):
form_class = ParlamentarCreateForm
@ -476,10 +504,12 @@ class ParlamentarCrud(Crud):
def take_legislatura_id(self):
username = self.request.user.username
try:
self.logger.debug("user=" + username + ". Tentando obter id da legislatura.")
self.logger.debug("user=" + username +
". Tentando obter id da legislatura.")
return int(self.request.GET['pk'])
except:
self.logger.error("user=" + username + ". Legislatura não possui ID. Buscando em todas as entradas.")
self.logger.error(
"user=" + username + ". Legislatura não possui ID. Buscando em todas as entradas.")
legislaturas = Legislatura.objects.all()
for l in legislaturas:
if l.atual():
@ -501,14 +531,17 @@ class ParlamentarCrud(Crud):
mandato_titular=F('mandato__titular')).distinct()
else:
try:
self.logger.debug("user=" + username + ". Tentando obter o mais recente registro do objeto Legislatura.")
self.logger.debug(
"user=" + username + ". Tentando obter o mais recente registro do objeto Legislatura.")
l = Legislatura.objects.all().order_by(
'-data_inicio').first()
except ObjectDoesNotExist:
self.logger.error("user=" + username + ". Objeto não encontrado. Retornando todos os registros.")
self.logger.error(
"user=" + username + ". Objeto não encontrado. Retornando todos os registros.")
return Legislatura.objects.all()
else:
self.logger.info("user=" + username + ". Objeto encontrado com sucesso.")
self.logger.info("user=" + username +
". Objeto encontrado com sucesso.")
if l is None:
return Legislatura.objects.all()
return queryset.filter(mandato__legislatura_id=l).annotate(
@ -570,11 +603,12 @@ class ParlamentarCrud(Crud):
.format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim))
row[1] = (
'O Parlamentar possui duas filiações conflitantes',
None)
None, None)
# Caso encontre UMA filiação nessas condições
else:
self.logger.info("user=" + username + ". Filiação encontrada com sucesso.")
self.logger.debug("user=" + username +
". Filiação encontrada com sucesso.")
row[1] = (filiacao.partido.sigla, None, None)
return context
@ -606,13 +640,16 @@ class ParlamentarMateriasView(FormView):
parlamentar_pk = kwargs['pk']
username = request.user.username
try:
self.logger.debug("user=" + username + ". Tentando obter Autor (object_id={}).".format(parlamentar_pk))
self.logger.debug(
"user=" + username + ". Tentando obter Autor (object_id={}).".format(parlamentar_pk))
autor = Autor.objects.get(
content_type=ContentType.objects.get_for_model(Parlamentar),
object_id=parlamentar_pk)
except ObjectDoesNotExist:
mensagem = _('Este Parlamentar (pk={}) não é Autor de matéria.'.format(parlamentar_pk))
self.logger.error("user=" + username + ". Este Parlamentar (pk={}) não é Autor de matéria.".format(parlamentar_pk))
mensagem = _(
'Este Parlamentar (pk={}) não é Autor de matéria.'.format(parlamentar_pk))
self.logger.error(
"user=" + username + ". Este Parlamentar (pk={}) não é Autor de matéria.".format(parlamentar_pk))
messages.add_message(request, messages.ERROR, mensagem)
return HttpResponseRedirect(
reverse(
@ -700,7 +737,8 @@ class MesaDiretoraView(FormView):
sessao_atual = sessoes.filter(data_inicio__year__lte=year).exclude(
data_inicio__gt=timezone.now()).order_by('-data_inicio').first()
mesa = sessao_atual.composicaomesa_set.all().order_by('cargo_id') if sessao_atual else []
mesa = sessao_atual.composicaomesa_set.all().order_by(
'cargo_id') if sessao_atual else []
cargos_ocupados = [m.cargo for m in mesa]
cargos = CargoMesa.objects.all()
@ -710,9 +748,9 @@ class MesaDiretoraView(FormView):
parlamentares_ocupados = [m.parlamentar for m in mesa]
parlamentares_vagos = list(
set(
[p.parlamentar for p in parlamentares]) - set(
[p.parlamentar for p in parlamentares if p.parlamentar.ativo]) - set(
parlamentares_ocupados))
parlamentares_vagos.sort(key=lambda x: x.nome_parlamentar)
# Se todos os cargos estiverem ocupados, a listagem de parlamentares
# deve ser renderizada vazia
if not cargos_vagos:
@ -756,7 +794,8 @@ def altera_field_mesa(request):
else:
year = timezone.now().year
try:
logger.debug("user=" + username + ". Tentando obter id de sessoes com data_inicio.ano={}.".format(year))
logger.debug(
"user=" + username + ". Tentando obter id de sessoes com data_inicio.ano={}.".format(year))
sessao_selecionada = sessoes.get(data_inicio__year=year).id
except ObjectDoesNotExist:
logger.error("user=" + username + ". Id de sessoes com data_inicio.ano={} não encontrado. "
@ -779,6 +818,7 @@ def altera_field_mesa(request):
[p.parlamentar for p in parlamentares]) - set(
parlamentares_ocupados))
parlamentares_vagos.sort(key=lambda x: x.nome_parlamentar)
lista_sessoes = [(s.id, s.__str__()) for s in sessoes]
lista_composicao = [(c.id, c.parlamentar.__str__(),
c.cargo.__str__()) for c in composicao_mesa]
@ -809,24 +849,29 @@ def insere_parlamentar_composicao(request):
composicao = ComposicaoMesa()
try:
logger.debug("user=" + username + ". Tentando obter SessaoLegislativa com id={}.".format(request.POST['sessao']))
logger.debug(
"user=" + username + ". Tentando obter SessaoLegislativa com id={}.".format(request.POST['sessao']))
composicao.sessao_legislativa = SessaoLegislativa.objects.get(
id=int(request.POST['sessao']))
except MultiValueDictKeyError:
logger.error("user=" + username + ". 'MultiValueDictKeyError', nenhuma sessão foi inserida!")
logger.error(
"user=" + username + ". 'MultiValueDictKeyError', nenhuma sessão foi inserida!")
return JsonResponse({'msg': ('Nenhuma sessão foi inserida!', 0)})
try:
logger.debug("user=" + username + ". Tentando obter Parlamentar com id={}.".format(request.POST['parlamentar']))
logger.debug(
"user=" + username + ". Tentando obter Parlamentar com id={}.".format(request.POST['parlamentar']))
composicao.parlamentar = Parlamentar.objects.get(
id=int(request.POST['parlamentar']))
except MultiValueDictKeyError:
logger.error("user=" + username + ". 'MultiValueDictKeyError', nenhum parlamentar foi inserido!")
logger.error(
"user=" + username + ". 'MultiValueDictKeyError', nenhum parlamentar foi inserido!")
return JsonResponse({
'msg': ('Nenhum parlamentar foi inserido!', 0)})
try:
logger.info("user=" + username + ". Tentando obter CargoMesa com id={}.".format(request.POST['cargo']))
logger.info("user=" + username +
". Tentando obter CargoMesa com id={}.".format(request.POST['cargo']))
composicao.cargo = CargoMesa.objects.get(
id=int(request.POST['cargo']))
parlamentar_ja_inserido = ComposicaoMesa.objects.filter(
@ -839,14 +884,16 @@ def insere_parlamentar_composicao(request):
composicao.save()
except MultiValueDictKeyError:
logger.error("user=" + username + ". 'MultiValueDictKeyError', nenhum cargo foi inserido!")
logger.error("user=" + username +
". 'MultiValueDictKeyError', nenhum cargo foi inserido!")
return JsonResponse({'msg': ('Nenhum cargo foi inserido!', 0)})
logger.info("user=" + username + ". Parlamentar inserido com sucesso!")
return JsonResponse({'msg': ('Parlamentar inserido com sucesso!', 1)})
else:
logger.error("user=" + username + " não tem permissão para esta operação!")
logger.error("user=" + username +
" não tem permissão para esta operação!")
return JsonResponse(
{'msg': ('Você não tem permissão para esta operação!', 0)})
@ -864,7 +911,8 @@ def remove_parlamentar_composicao(request):
if 'composicao_mesa' in request.POST:
try:
logger.debug("user=" + username + ". Tentando obter ComposicaoMesa com id={}.".format(request.POST['composicao_mesa']))
logger.debug("user=" + username + ". Tentando obter ComposicaoMesa com id={}.".format(
request.POST['composicao_mesa']))
composicao = ComposicaoMesa.objects.get(
id=request.POST['composicao_mesa'])
except ObjectDoesNotExist:
@ -876,12 +924,14 @@ def remove_parlamentar_composicao(request):
composicao.delete()
logger.info("user=" + username + ". ComposicaoMesa com id={} excluido com sucesso!".format(request.POST['composicao_mesa']))
logger.info("user=" + username + ". ComposicaoMesa com id={} excluido com sucesso!".format(
request.POST['composicao_mesa']))
return JsonResponse(
{'msg': (
'Parlamentar excluido com sucesso!', 1)})
else:
logger.info("user=" + username + ". Nenhum parlamentar escolhido para ser excluído.")
logger.info("user=" + username +
". Nenhum parlamentar escolhido para ser excluído.")
return JsonResponse(
{'msg': (
'Selecione algum parlamentar para ser excluido!', 0)})
@ -957,7 +1007,8 @@ def altera_field_mesa_public_view(request):
else:
try:
year = timezone.now().year
logger.info("user=" + username + ". Tentando obter sessões com data_inicio.ano = {}.".format(year))
logger.info("user=" + username +
". Tentando obter sessões com data_inicio.ano = {}.".format(year))
sessao_selecionada = sessoes.get(data_inicio__year=year).id
except ObjectDoesNotExist:
logger.error("user=" + username + ". Sessões não encontradas com com data_inicio.ano = {}. "
@ -987,7 +1038,16 @@ def altera_field_mesa_public_view(request):
partido_parlamentar_sessao_legislativa(sessao,
parlamentar))
if parlamentar.fotografia:
lista_fotos.append(parlamentar.fotografia.url)
thumbnail_url = get_backend().get_thumbnail_url(
parlamentar.fotografia,
{
'size': (128, 128),
'box': parlamentar.cropping,
'crop': True,
'detail': True,
}
)
lista_fotos.append(thumbnail_url)
else:
lista_fotos.append(None)

438
sapl/protocoloadm/forms.py

@ -1,9 +1,9 @@
import django_filters
import logging
from crispy_forms.bootstrap import InlineRadios
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout
from crispy_forms.bootstrap import InlineRadios, Alert
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Div
from django import forms
from django.core.exceptions import (MultipleObjectsReturned,
ObjectDoesNotExist, ValidationError)
@ -12,34 +12,37 @@ from django.db.models import Max
from django.forms import ModelForm
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.base.models import Autor, TipoAutor
from sapl.base.models import Autor, TipoAutor, AppConfig
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa,
UnidadeTramitacao)
from sapl.protocoloadm.models import Protocolo
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter,
RangeWidgetOverride, autor_label, autor_modal)
RangeWidgetOverride, autor_label, autor_modal,
choice_anos_com_protocolo, choice_force_optional,
choice_anos_com_documentoadministrativo,
FilterOverridesMetaMixin, choice_anos_com_materias,
FileFieldCheckMixin)
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo)
TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'), ('2', 'Interno'), ('', '---------')]
TIPOS_PROTOCOLO_CREATE = [('0', 'Recebido'), ('1', 'Enviado'), ('2', 'Interno')]
NATUREZA_PROCESSO = [('', '---------'),
('0', 'Administrativo'),
('1', 'Legislativo')]
TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'),
('2', 'Interno')]
TIPOS_PROTOCOLO_CREATE = [
('0', 'Recebido'), ('1', 'Enviado'), ('2', 'Interno')]
NATUREZA_PROCESSO = [('0', 'Administrativo'),
('1', 'Legislativo')]
def ANO_CHOICES():
return [('', '---------')] + RANGE_ANOS
EM_TRAMITACAO = [(0, 'Sim'), (1, 'Não')]
EM_TRAMITACAO = [('', '---------'),
(0, 'Sim'),
(1, 'Não')]
class AcompanhamentoDocumentoForm(ModelForm):
@ -55,7 +58,7 @@ class AcompanhamentoDocumentoForm(ModelForm):
Column(form_actions(label='Cadastrar'), css_class='col-md-2')
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
_('Acompanhamento de Documento por e-mail'), row1
@ -66,20 +69,18 @@ class AcompanhamentoDocumentoForm(ModelForm):
class ProtocoloFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateTimeField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': 'Data (%s)' % (_('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
ano = django_filters.ChoiceFilter(required=False,
ano = django_filters.ChoiceFilter(
required=False,
label='Ano',
choices=ANO_CHOICES)
choices=choice_anos_com_protocolo)
assunto_ementa = django_filters.CharFilter(lookup_expr='icontains')
assunto_ementa = django_filters.CharFilter(
label=_('Assunto'),
lookup_expr='icontains')
interessado = django_filters.CharFilter(lookup_expr='icontains')
interessado = django_filters.CharFilter(
label=_('Interessado'),
lookup_expr='icontains')
autor = django_filters.CharFilter(widget=forms.HiddenInput())
@ -96,9 +97,9 @@ class ProtocoloFilterSet(django_filters.FilterSet):
widget=forms.Select(
attrs={'class': 'selector'}))
o = AnoNumeroOrderingFilter()
o = AnoNumeroOrderingFilter(help_text='')
class Meta:
class Meta(FilterOverridesMetaMixin):
model = Protocolo
fields = ['numero',
'tipo_documento',
@ -109,8 +110,7 @@ class ProtocoloFilterSet(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
super(ProtocoloFilterSet, self).__init__(*args, **kwargs)
self.filters['autor'].label = 'Tipo de Matéria'
self.filters['assunto_ementa'].label = 'Assunto'
self.filters['timestamp'].label = 'Data (Inicial - Final)'
row1 = to_row(
[('numero', 4),
@ -135,47 +135,44 @@ class ProtocoloFilterSet(django_filters.FilterSet):
'Limpar Autor',
css_class='btn btn-primary btn-sm'), 10)])
row5 = to_row(
[('tipo_processo', 12)])
row6 = to_row(
[('o', 12)])
[('tipo_processo', 6), ('o', 6)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar Protocolo'),
row1, row2,
row3,
row5,
HTML(autor_label),
HTML(autor_modal),
row4, row5, row6,
row4,
form_actions(label='Pesquisar'))
)
class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': 'Data (%s)' % (_('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
ano = django_filters.ChoiceFilter(required=False,
ano = django_filters.ChoiceFilter(
required=False,
label='Ano',
choices=ANO_CHOICES)
choices=choice_anos_com_documentoadministrativo)
tramitacao = django_filters.ChoiceFilter(required=False,
label='Em Tramitação?',
choices=EM_TRAMITACAO)
choices=YES_NO_CHOICES)
assunto = django_filters.CharFilter(lookup_expr='icontains')
assunto = django_filters.CharFilter(
label=_('Assunto'),
lookup_expr='icontains')
interessado = django_filters.CharFilter(lookup_expr='icontains')
interessado = django_filters.CharFilter(
label=_('Interessado'),
lookup_expr='icontains')
o = AnoNumeroOrderingFilter()
o = AnoNumeroOrderingFilter(help_text='')
class Meta:
class Meta(FilterOverridesMetaMixin):
model = DocumentoAdministrativo
fields = ['tipo',
'numero',
@ -190,37 +187,38 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
local_atual = 'tramitacaoadministrativo__unidade_tramitacao_destino'
self.filters['tipo'].label = 'Tipo de Documento'
self.filters['protocolo__numero'].label = 'Núm. Protocolo'
self.filters['tramitacaoadministrativo__status'].label = 'Situação'
self.filters[local_atual].label = 'Localização Atual'
row1 = to_row(
[('tipo', 6),
('numero', 6)])
[('tipo', 8),
('o', 4), ])
row2 = to_row(
[('ano', 4),
[('numero', 2),
('ano', 2),
('protocolo__numero', 2),
('numero_externo', 2),
('data', 4)])
row3 = to_row(
[('interessado', 4),
('assunto', 4),
('tramitacao', 4)])
[('interessado', 6),
('assunto', 6)])
row4 = to_row(
[('tramitacaoadministrativo__unidade_tramitacao_destino', 6),
('tramitacaoadministrativo__status', 6)])
row5 = to_row(
[('o', 12)])
[
('tramitacao', 2),
('tramitacaoadministrativo__status', 5),
('tramitacaoadministrativo__unidade_tramitacao_destino', 5),
])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar Documento'),
row1, row2,
row3, row4, row5,
row3, row4,
form_actions(label='Pesquisar'))
)
@ -255,10 +253,12 @@ class AnularProcoloAdmForm(ModelForm):
ano = cleaned_data['ano']
try:
self.logger.debug("Tentando obter Protocolo com numero={} e ano={}.".format(numero, ano))
self.logger.debug(
"Tentando obter Protocolo com numero={} e ano={}.".format(numero, ano))
protocolo = Protocolo.objects.get(numero=numero, ano=ano)
if protocolo.anulado:
self.logger.error("Protocolo %s/%s já encontra-se anulado" % (numero, ano))
self.logger.error(
"Protocolo %s/%s já encontra-se anulado" % (numero, ano))
raise forms.ValidationError(
_("Protocolo %s/%s já encontra-se anulado")
% (numero, ano))
@ -306,7 +306,7 @@ class AnularProcoloAdmForm(ModelForm):
row2 = to_row(
[('justificativa_anulacao', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação do Protocolo'),
row1,
@ -343,7 +343,14 @@ class ProtocoloDocumentForm(ModelForm):
observacao = forms.CharField(required=False,
widget=forms.Textarea, label=_('Observação'))
numero = forms.IntegerField(required=False, label=_('Número de Protocolo (opcional)'))
numero = forms.IntegerField(
required=False, label=_('Número de Protocolo (opcional)'))
data_hora_manual = forms.ChoiceField(
label=_('Informar data e hora manualmente?'),
widget=forms.RadioSelect(),
choices=YES_NO_CHOICES,
initial=False)
class Meta:
model = Protocolo
@ -353,7 +360,9 @@ class ProtocoloDocumentForm(ModelForm):
'assunto',
'interessado',
'observacao',
'numero'
'numero',
'data',
'hora',
]
def __init__(self, *args, **kwargs):
@ -361,30 +370,62 @@ class ProtocoloDocumentForm(ModelForm):
row1 = to_row(
[(InlineRadios('tipo_protocolo'), 12)])
row2 = to_row(
[('tipo_documento', 6),
('numero_paginas', 6)])
row3 = to_row(
[('assunto', 12)])
[('tipo_documento', 5),
('numero_paginas', 2),
(Div(), 1),
(InlineRadios('data_hora_manual'), 4),
])
row3 = to_row([
(Div(), 2),
(Alert(
"""
Usuário: <strong>{}</strong> - {}<br>
IP: <strong>{}</strong> - {}<br>
""".format(
kwargs['initial']['user_data_hora_manual'],
Protocolo._meta.get_field(
'user_data_hora_manual').help_text,
kwargs['initial']['ip_data_hora_manual'],
Protocolo._meta.get_field(
'ip_data_hora_manual').help_text,
),
dismiss=False,
css_class='alert-info'), 6),
('data', 2),
('hora', 2),
])
row4 = to_row(
[('interessado', 12)])
[('assunto', 12)])
row5 = to_row(
[('observacao', 12)])
[('interessado', 12)])
row6 = to_row(
[('observacao', 12)])
row7 = to_row(
[('numero', 12)])
self.helper = FormHelper()
fieldset = Fieldset(_('Protocolo com data e hora informados manualmente'),
row3,
css_id='protocolo_data_hora_manual')
config = AppConfig.objects.first()
if not config.protocolo_manual:
row3 = to_row([(HTML("&nbsp;"), 12)])
fieldset = row3
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação de Documento'),
row1,
row2,
row3,
row2),
fieldset,
row4,
row5,
HTML("&nbsp;"),
),
Fieldset(_('Número do Protocolo (Apenas se quiser que a numeração comece '
'a partir do número a ser informado)'),
row6,
row7,
HTML("&nbsp;"),
form_actions(label=_('Protocolar Documento'))
)
@ -392,6 +433,10 @@ class ProtocoloDocumentForm(ModelForm):
super(ProtocoloDocumentForm, self).__init__(
*args, **kwargs)
if not config.protocolo_manual:
self.fields['data_hora_manual'].widget = forms.HiddenInput()
class ProtocoloMateriaForm(ModelForm):
@ -420,7 +465,8 @@ class ProtocoloMateriaForm(ModelForm):
ano_materia = forms.CharField(
label=_('Ano matéria'), required=False)
vincular_materia = forms.ChoiceField(label=_('Vincular a matéria existente?'),
vincular_materia = forms.ChoiceField(
label=_('Vincular a matéria existente?'),
widget=forms.RadioSelect(),
choices=YES_NO_CHOICES,
initial=False)
@ -433,7 +479,14 @@ class ProtocoloMateriaForm(ModelForm):
assunto_ementa = forms.CharField(required=True,
widget=forms.Textarea, label=_('Ementa'))
numero = forms.IntegerField(required=False, label=_('Número de Protocolo (opcional)'))
numero = forms.IntegerField(
required=False, label=_('Número de Protocolo (opcional)'))
data_hora_manual = forms.ChoiceField(
label=_('Informar data e hora manualmente?'),
widget=forms.RadioSelect(),
choices=YES_NO_CHOICES,
initial=False)
class Meta:
model = Protocolo
@ -446,19 +499,24 @@ class ProtocoloMateriaForm(ModelForm):
'numero_materia',
'ano_materia',
'vincular_materia',
'numero'
'numero',
'data',
'hora',
]
def clean_autor(self):
autor_field = self.cleaned_data['autor']
try:
self.logger.debug("Tentando obter Autor com id={}.".format(autor_field.id))
self.logger.debug(
"Tentando obter Autor com id={}.".format(autor_field.id))
autor = Autor.objects.get(id=autor_field.id)
except ObjectDoesNotExist:
self.logger.error("Autor com id={} não encontrado. Definido como None.".format(autor_field.id))
self.logger.error(
"Autor com id={} não encontrado. Definido como None.".format(autor_field.id))
autor_field = None
else:
self.logger.info("Autor com id={} encontrado com sucesso.".format(autor_field.id))
self.logger.info(
"Autor com id={} encontrado com sucesso.".format(autor_field.id))
autor_field = autor
return autor_field
@ -473,7 +531,8 @@ class ProtocoloMateriaForm(ModelForm):
if data['vincular_materia'] == 'True':
try:
if not data['ano_materia'] or not data['numero_materia']:
self.logger.error("Não foram informados o número ou ano da matéria a ser vinculada")
self.logger.error(
"Não foram informados o número ou ano da matéria a ser vinculada")
raise ValidationError(
'Favor informar o número e ano da matéria a ser vinculada')
self.logger.debug("Tentando obter MateriaLegislativa com ano={}, numero={} e data={}."
@ -489,7 +548,8 @@ class ProtocoloMateriaForm(ModelForm):
except ObjectDoesNotExist:
self.logger.error("MateriaLegislativa informada (ano={}, numero={} e data={}) não existente."
.format(data['ano_materia'], data['numero_materia'], data['tipo_materia']))
raise ValidationError(_('Matéria Legislativa informada não existente.'))
raise ValidationError(
_('Matéria Legislativa informada não existente.'))
return data
@ -501,28 +561,61 @@ class ProtocoloMateriaForm(ModelForm):
('tipo_autor', 3),
('autor', 3)])
row2 = to_row(
[(InlineRadios('vincular_materia'), 4),
('numero_materia', 4),
('ano_materia', 4), ])
row3 = to_row(
[('assunto_ementa', 12)])
[(InlineRadios('vincular_materia'), 3),
('numero_materia', 2),
('ano_materia', 2),
(Div(), 1),
(InlineRadios('data_hora_manual'), 4),
])
row3 = to_row([
(Div(), 2),
(Alert(
"""
Usuário: <strong>{}</strong> - {}<br>
IP: <strong>{}</strong> - {}<br>
""".format(
kwargs['initial']['user_data_hora_manual'],
Protocolo._meta.get_field(
'user_data_hora_manual').help_text,
kwargs['initial']['ip_data_hora_manual'],
Protocolo._meta.get_field(
'ip_data_hora_manual').help_text,
),
dismiss=False,
css_class='alert-info'), 6),
('data', 2),
('hora', 2),
])
row4 = to_row(
[('observacao', 12)])
[('assunto_ementa', 12)])
row5 = to_row(
[('observacao', 12)])
row6 = to_row(
[('numero', 12)])
self.helper = FormHelper()
fieldset = Fieldset(_('Protocolo com data e hora informados manualmente'),
row3,
css_id='protocolo_data_hora_manual')
config = AppConfig.objects.first()
if not config.protocolo_manual:
row3 = to_row([(HTML("&nbsp;"), 12)])
fieldset = row3
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação da Matéria'),
row1,
row2,
row3,
row2),
fieldset,
row4,
row5,
HTML("&nbsp;"),
),
Fieldset(_('Número do Protocolo (Apenas se quiser que a numeração comece'
' a partir do número a ser informado)'),
row5,
row6,
HTML("&nbsp;"),
form_actions(label=_('Protocolar Matéria')))
)
@ -530,8 +623,11 @@ class ProtocoloMateriaForm(ModelForm):
super(ProtocoloMateriaForm, self).__init__(
*args, **kwargs)
if not config.protocolo_manual:
self.fields['data_hora_manual'].widget = forms.HiddenInput()
class DocumentoAcessorioAdministrativoForm(ModelForm):
class DocumentoAcessorioAdministrativoForm(FileFieldCheckMixin, ModelForm):
class Meta:
model = DocumentoAcessorioAdministrativo
@ -687,16 +783,17 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm):
return self.cleaned_data
class DocumentoAdministrativoForm(ModelForm):
class DocumentoAdministrativoForm(FileFieldCheckMixin, ModelForm):
logger = logging.getLogger(__name__)
data = forms.DateField(initial=timezone.now)
ano_protocolo = forms.ChoiceField(required=False,
ano_protocolo = forms.ChoiceField(
required=False,
label=Protocolo._meta.
get_field('ano').verbose_name,
choices=RANGE_ANOS,
choices=choice_force_optional(choice_anos_com_protocolo),
widget=forms.Select(
attrs={'class': 'selector'}))
@ -754,10 +851,10 @@ class DocumentoAdministrativoForm(ModelForm):
if not self.instance.pk or mudanca_doc:
doc_exists = DocumentoAdministrativo.objects.filter(numero=numero_documento,
tipo=tipo_documento,
ano=ano_protocolo).exists()
ano=ano_documento).exists()
if doc_exists:
self.logger.error("DocumentoAdministrativo (numero={}, tipo={} e ano={}) já existe."
.format(numero_documento, tipo_documento, ano_protocolo))
.format(numero_documento, tipo_documento, ano_documento))
raise ValidationError(_('Documento já existente'))
# campos opcionais, mas que se informados devem ser válidos
@ -775,10 +872,11 @@ class DocumentoAdministrativoForm(ModelForm):
numero_protocolo, ano_protocolo))
raise ValidationError(msg)
except MultipleObjectsReturned:
self.logger.error("Existe mais de um Protocolo com este ano ({}) e número ({}).".format(ano_protocolo,numero_protocolo))
self.logger.error("Existe mais de um Protocolo com este ano ({}) e número ({}).".format(
ano_protocolo, numero_protocolo))
msg = _(
'Existe mais de um Protocolo com este ano e número.' % (
numero_protocolo, ano_protocolo))
'Existe mais de um Protocolo com este ano (%s) e número (%s).' % (
ano_protocolo, numero_protocolo))
raise ValidationError(msg)
inst = self.instance.protocolo
@ -814,7 +912,7 @@ class DocumentoAdministrativoForm(ModelForm):
def __init__(self, *args, **kwargs):
row1 = to_row(
[('tipo', 4), ('numero', 4), ('ano', 4)])
[('tipo', 6), ('numero', 3), ('ano', 3)])
row2 = to_row(
[('data', 4), ('numero_protocolo', 4), ('ano_protocolo', 4)])
@ -823,7 +921,7 @@ class DocumentoAdministrativoForm(ModelForm):
[('assunto', 12)])
row4 = to_row(
[('interessado', 8), ('tramitacao', 2), (InlineRadios('restrito'), 2)])
[('interessado', 7), ('tramitacao', 2), (InlineRadios('restrito'), 3)])
row5 = to_row(
[('texto_integral', 12)])
@ -834,7 +932,7 @@ class DocumentoAdministrativoForm(ModelForm):
row7 = to_row(
[('observacao', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'),
row1, row2, row3, row4, row5),
@ -848,14 +946,14 @@ class DesvincularDocumentoForm(ModelForm):
logger = logging.getLogger(__name__)
numero = forms.CharField(required=True,
label=DocumentoAdministrativo._meta.
get_field('numero').verbose_name
)
ano = forms.ChoiceField(required=True,
label=DocumentoAdministrativo._meta.
get_field('ano').verbose_name,
choices=RANGE_ANOS,
numero = forms.CharField(
required=True,
label=DocumentoAdministrativo._meta.get_field('numero').verbose_name)
ano = forms.ChoiceField(
required=True,
label=DocumentoAdministrativo._meta.get_field('ano').verbose_name,
choices=choice_anos_com_documentoadministrativo,
widget=forms.Select(attrs={'class': 'selector'}))
def clean(self):
@ -873,13 +971,16 @@ class DesvincularDocumentoForm(ModelForm):
try:
self.logger.debug("Tentando obter DocumentoAdministrativo com numero={}, ano={} e tipo={}."
.format(numero, ano, tipo))
documento = DocumentoAdministrativo.objects.get(numero=numero, ano=ano, tipo=tipo)
documento = DocumentoAdministrativo.objects.get(
numero=numero, ano=ano, tipo=tipo)
if not documento.protocolo:
self.logger.error("DocumentoAdministrativo %s %s/%s não se encontra vinculado a nenhum protocolo." % (tipo, numero, ano))
self.logger.error(
"DocumentoAdministrativo %s %s/%s não se encontra vinculado a nenhum protocolo." % (tipo, numero, ano))
raise forms.ValidationError(
_("%s %s/%s não se encontra vinculado a nenhum protocolo" % (tipo, numero, ano)))
except ObjectDoesNotExist:
self.logger.error("DocumentoAdministrativo %s %s/%s não existe" % (tipo, numero, ano))
self.logger.error(
"DocumentoAdministrativo %s %s/%s não existe" % (tipo, numero, ano))
raise forms.ValidationError(
_("%s %s/%s não existe" % (tipo, numero, ano)))
@ -899,7 +1000,7 @@ class DesvincularDocumentoForm(ModelForm):
('ano', 4),
('tipo', 4)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação do Documento'),
row1,
@ -919,7 +1020,7 @@ class DesvincularMateriaForm(forms.Form):
label=_('Número da Matéria'))
ano = forms.ChoiceField(required=True,
label=_('Ano da Matéria'),
choices=RANGE_ANOS,
choices=choice_anos_com_materias,
widget=forms.Select(attrs={'class': 'selector'}))
tipo = forms.ModelChoiceField(label=_('Tipo de Matéria'),
required=True,
@ -941,13 +1042,16 @@ class DesvincularMateriaForm(forms.Form):
try:
self.logger.info("Tentando obter MateriaLegislativa com numero={}, ano={} e tipo={}."
.format(numero, ano, tipo))
materia = MateriaLegislativa.objects.get(numero=numero, ano=ano, tipo=tipo)
materia = MateriaLegislativa.objects.get(
numero=numero, ano=ano, tipo=tipo)
if not materia.numero_protocolo:
self.logger.error("MateriaLegislativa %s %s/%s não se encontra vinculada a nenhum protocolo" % (tipo, numero, ano))
self.logger.error(
"MateriaLegislativa %s %s/%s não se encontra vinculada a nenhum protocolo" % (tipo, numero, ano))
raise forms.ValidationError(
_("%s %s/%s não se encontra vinculada a nenhum protocolo" % (tipo, numero, ano)))
except ObjectDoesNotExist:
self.logger.error("MateriaLegislativa %s %s/%s não existe" % (tipo, numero, ano))
self.logger.error(
"MateriaLegislativa %s %s/%s não existe" % (tipo, numero, ano))
raise forms.ValidationError(
_("%s %s/%s não existe" % (tipo, numero, ano)))
@ -961,7 +1065,7 @@ class DesvincularMateriaForm(forms.Form):
('ano', 4),
('tipo', 4)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação da Matéria'),
row1,
@ -1000,3 +1104,81 @@ def filtra_tramitacao_adm_destino_and_status(status, destino):
status=status,
unidade_tramitacao_destino=destino).distinct().values_list(
'documento_id', flat=True)
class FichaPesquisaAdmForm(forms.Form):
logger = logging.getLogger(__name__)
tipo_documento = forms.ModelChoiceField(
label=TipoDocumentoAdministrativo._meta.verbose_name,
queryset=TipoDocumentoAdministrativo.objects.all(),
empty_label='Selecione')
data_inicial = forms.DateField(
label='Data Inicial',
widget=forms.DateInput(format='%d/%m/%Y')
)
data_final = forms.DateField(
label='Data Final',
widget=forms.DateInput(format='%d/%m/%Y')
)
def __init__(self, *args, **kwargs):
super(FichaPesquisaAdmForm, self).__init__(*args, **kwargs)
row1 = to_row(
[('tipo_documento', 6),
('data_inicial', 3),
('data_final', 3)])
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Formulário de Ficha'),
row1,
form_actions(label='Pesquisar')
)
)
def clean(self):
super(FichaPesquisaAdmForm, self).clean()
if not self.is_valid():
return self.cleaned_data
cleaned_data = self.cleaned_data
if not self.is_valid():
return cleaned_data
if cleaned_data['data_final'] < cleaned_data['data_inicial']:
self.logger.error("A Data Final ({}) não pode ser menor que a Data Inicial ({})."
.format(cleaned_data['data_final'], cleaned_data['data_inicial']))
raise ValidationError(_(
'A Data Final não pode ser menor que a Data Inicial'))
return cleaned_data
class FichaSelecionaAdmForm(forms.Form):
documento = forms.ModelChoiceField(
widget=forms.RadioSelect,
queryset=DocumentoAdministrativo.objects.all(),
label='')
def __init__(self, *args, **kwargs):
super(FichaSelecionaAdmForm, self).__init__(*args, **kwargs)
row1 = to_row(
[('documento', 12)])
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Selecione a ficha que deseja imprimir'),
row1,
form_actions(label='Gerar Impresso')
)
)

20
sapl/protocoloadm/migrations/0010_auto_20181212_1900.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-12-12 21:00
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0009_merge'),
]
operations = [
migrations.AlterField(
model_name='protocolo',
name='justificativa_anulacao',
field=models.CharField(blank=True, max_length=260, verbose_name='Motivo'),
),
]

25
sapl/protocoloadm/migrations/0011_auto_20190101_1618.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-01 18:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0010_auto_20181212_1900'),
]
operations = [
migrations.AlterField(
model_name='documentoadministrativo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='protocolo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano do Protocolo'),
),
]

25
sapl/protocoloadm/migrations/0011_auto_20190107_1407.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-07 16:07
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0010_auto_20181212_1900'),
]
operations = [
migrations.AlterField(
model_name='documentoadministrativo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='protocolo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano do Protocolo'),
),
]

25
sapl/protocoloadm/migrations/0012_auto_20190104_1021.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-04 12:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0011_auto_20190101_1618'),
]
operations = [
migrations.AlterField(
model_name='documentoadministrativo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='protocolo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano do Protocolo'),
),
]

26
sapl/protocoloadm/migrations/0013_auto_20190106_1336.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-06 15:36
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0012_auto_20190104_1021'),
]
operations = [
migrations.AlterField(
model_name='protocolo',
name='tipo_documento',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='protocoloadm.TipoDocumentoAdministrativo', verbose_name='Tipo de Documento'),
),
migrations.AlterField(
model_name='protocolo',
name='tipo_materia',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='materia.TipoMateriaLegislativa', verbose_name='Tipo de Matéria'),
),
]

35
sapl/protocoloadm/migrations/0014_auto_20190110_1300.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-10 15:00
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0013_auto_20190106_1336'),
]
operations = [
migrations.AddField(
model_name='protocolo',
name='ip_data_hora_manual',
field=models.CharField(blank=True, help_text='Endereço IP da estação de trabalho do usuário que está realizando Protocolo e informando data e hora manualmente.', max_length=15, verbose_name='IP'),
),
migrations.AddField(
model_name='protocolo',
name='user_data_hora_manual',
field=models.CharField(blank=True, help_text='Usuário que está realizando Protocolo e informando data e hora manualmente.', max_length=20, verbose_name='IP'),
),
migrations.AlterField(
model_name='protocolo',
name='data',
field=models.DateField(blank=True, help_text='Informado manualmente', null=True, verbose_name='Data do Protocolo'),
),
migrations.AlterField(
model_name='protocolo',
name='hora',
field=models.TimeField(blank=True, help_text='Informado manualmente', null=True, verbose_name='Hora do Protocolo'),
),
]

16
sapl/protocoloadm/migrations/0014_merge_20190108_1538.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2019-01-08 17:38
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0011_auto_20190107_1407'),
('protocoloadm', '0013_auto_20190106_1336'),
]
operations = [
]

25
sapl/protocoloadm/migrations/0015_auto_20190108_1606.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2019-01-08 18:06
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0014_merge_20190108_1538'),
]
operations = [
migrations.AlterField(
model_name='documentoadministrativo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='protocolo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano do Protocolo'),
),
]

21
sapl/protocoloadm/migrations/0015_protocolo_timestamp_data_hora_manual.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-10 15:43
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0014_auto_20190110_1300'),
]
operations = [
migrations.AddField(
model_name='protocolo',
name='timestamp_data_hora_manual',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

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

Loading…
Cancel
Save