Browse Source

Merge pull request #1 from interlegis/master

16122016
pull/878/head
Rogério Frá 8 years ago
committed by GitHub
parent
commit
4b3c0b4189
  1. 2
      .gitignore
  2. 290
      README.rst
  3. 110
      docs/deploy.rst
  4. 41
      docs/howtogit.rst
  5. 73
      docs/implementacoes.rst
  6. 31
      docs/importacao_25_31.rst
  7. 220
      docs/instacao31.rst
  8. 27
      docs/traducao.rst
  9. 6
      gunicorn_start.sh
  10. 7
      requirements/requirements.txt
  11. 1
      sapl/api/serializers.py
  12. 9
      sapl/api/views.py
  13. 3
      sapl/base/admin.py
  14. 102
      sapl/base/forms.py
  15. 38
      sapl/base/urls.py
  16. 2
      sapl/base/views.py
  17. 34
      sapl/compilacao/compilacao_data_tables.sql
  18. 8
      sapl/compilacao/forms.py
  19. 21
      sapl/compilacao/migrations/0070_perfilestruturaltextoarticulado_parent.py
  20. 20
      sapl/compilacao/migrations/0071_tipotextoarticulado_perfis.py
  21. 55
      sapl/compilacao/migrations/0072_auto_20161112_1553.py
  22. 130
      sapl/compilacao/models.py
  23. 87
      sapl/compilacao/views.py
  24. 14
      sapl/crispy_layout_mixin.py
  25. 14
      sapl/crud/base.py
  26. 45
      sapl/legacy/migration.py
  27. 145
      sapl/materia/forms.py
  28. 35
      sapl/materia/migrations/0068_auto_20161110_0910.py
  29. 21
      sapl/materia/migrations/0069_tipoproposicao_perfis.py
  30. 21
      sapl/materia/migrations/0070_auto_20161111_1301.py
  31. 19
      sapl/materia/migrations/0071_auto_20161130_1001.py
  32. 19
      sapl/materia/migrations/0072_auto_20161130_1618.py
  33. 80
      sapl/materia/models.py
  34. 2
      sapl/materia/tests/test_materia.py
  35. 94
      sapl/materia/views.py
  36. 160
      sapl/norma/forms.py
  37. 20
      sapl/norma/migrations/0022_auto_20161110_0910.py
  38. 41
      sapl/norma/migrations/0023_auto_20161123_1359.py
  39. 25
      sapl/norma/migrations/0024_auto_20161123_1430.py
  40. 29
      sapl/norma/migrations/0025_normarelacionada.py
  41. 21
      sapl/norma/migrations/0026_auto_20161123_1450.py
  42. 26
      sapl/norma/migrations/0027_auto_20161123_1538.py
  43. 21
      sapl/norma/migrations/0028_auto_20161202_1025.py
  44. 69
      sapl/norma/models.py
  45. 19
      sapl/norma/urls.py
  46. 191
      sapl/norma/views.py
  47. 283
      sapl/painel/views.py
  48. 2
      sapl/parlamentares/models.py
  49. 11
      sapl/parlamentares/views.py
  50. 32
      sapl/protocoloadm/forms.py
  51. 25
      sapl/protocoloadm/migrations/0007_auto_20161110_0910.py
  52. 49
      sapl/protocoloadm/models.py
  53. 20
      sapl/protocoloadm/views.py
  54. 2
      sapl/relatorios/views.py
  55. 15
      sapl/rules/__init__.py
  56. 9
      sapl/rules/apps.py
  57. 26
      sapl/rules/map_rules.py
  58. 37
      sapl/sessao/forms.py
  59. 41
      sapl/sessao/models.py
  60. 50
      sapl/sessao/views.py
  61. 4
      sapl/settings.py
  62. 6
      sapl/static/js/app.js
  63. 4
      sapl/static/js/compilacao.js
  64. 8
      sapl/static/js/compilacao_view.js
  65. 16
      sapl/static/styles/compilacao.scss
  66. 6
      sapl/templates/base.html
  67. 2
      sapl/templates/base/layouts.yaml
  68. 13
      sapl/templates/base/login.html
  69. 1
      sapl/templates/base/nova_senha_form.html
  70. 14
      sapl/templates/base/recupera_senha_email_enviado.html
  71. 5
      sapl/templates/base/recuperar_senha_completo.html
  72. 13
      sapl/templates/base/recuperar_senha_email.html
  73. 1
      sapl/templates/base/recuperar_senha_email_form.html
  74. 2
      sapl/templates/compilacao/ajax_actions_dinamic_edit.html
  75. 2
      sapl/templates/compilacao/text_edit.html
  76. 11
      sapl/templates/compilacao/text_list.html
  77. 2
      sapl/templates/compilacao/text_list_bloco.html
  78. 6
      sapl/templates/compilacao/textoarticulado_detail.html
  79. 12
      sapl/templates/compilacao/tipotextoarticulado_detail.html
  80. 20
      sapl/templates/materia/layouts.yaml
  81. 5
      sapl/templates/materia/materialegislativa_filter.html
  82. 9
      sapl/templates/materia/prop_devolvidas_list.html
  83. 16
      sapl/templates/materia/proposicao_detail.html
  84. 21
      sapl/templates/materia/proposicao_form.html
  85. 6
      sapl/templates/materia/tipoproposicao_form.html
  86. 2
      sapl/templates/navbar.yaml
  87. 15
      sapl/templates/norma/layouts.yaml
  88. 39
      sapl/templates/norma/list_pesquisa.html
  89. 34
      sapl/templates/norma/normajuridica_detail.html
  90. 62
      sapl/templates/norma/normajuridica_filter.html
  91. 27
      sapl/templates/norma/normarelacionada_form.html
  92. 20
      sapl/templates/norma/pesquisa.html
  93. 2
      sapl/templates/norma/subnav.yaml
  94. 234
      sapl/templates/painel/index.html
  95. 8
      sapl/templates/parlamentares/parlamentar_perfil_publico.html
  96. 26
      sapl/templates/protocoloadm/comprovante.html
  97. 16
      sapl/templates/protocoloadm/documentoadministrativo_filter.html
  98. 10
      sapl/templates/protocoloadm/protocolo_filter.html
  99. 8
      sapl/templates/protocoloadm/protocolo_list.html
  100. 25
      sapl/templates/protocoloadm/protocolo_mostrar.html

2
.gitignore

@ -87,8 +87,8 @@ target/
# specific to this project
whoosh_index
collected_static
bower
bower_components
media

290
README.rst

@ -17,303 +17,37 @@ atual do sistema (2.5), visite a página do `projeto na Interlegis wiki <https:/
Instalação do Ambiente de Desenvolvimento
=========================================
`Instalação do Ambiente de Desenvolvimento <https://github.com/interlegis/sapl/blob/master/docs/instacao31.rst>`_
* Procedimento testado nos seguintes SO's:
* `Ubuntu 16.04 64bits <https://github.com/interlegis/sapl/blob/master/README.rst>`_;
Instruções para Importação da base mysql 2.5
============================================
`Importação da Base do SAPL 2.5 para SAPL 3.1 <https://github.com/interlegis/sapl/blob/master/docs/importacao_25_31.rst>`_
* edite e incremente outros, ou ainda, crie outros readme's dentro do projeto para outros SO's e adicione o link aqui.
Instalar as seguintes dependências do sistema::
----------------------------------------------------------------------------------------
Instruções para Deploy
======================
`Deploy SAPL com Nginx + Gunicorn <https://github.com/interlegis/sapl/blob/master/docs/deploy.rst>`_
* ::
sudo apt-get install git nginx python3-dev libpq-dev graphviz-dev graphviz \
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 curl
sudo easy_install3 pip lxml
sudo -i
curl -sL https://deb.nodesource.com/setup_5.x | bash -
exit
sudo apt-get install nodejs
sudo npm install npm -g
sudo npm install -g bower
Instalar o virtualenv usando python 3 para o projeto.
-----------------------------------------------------
* Para usar `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html#basic-installation>`_, instale com::
sudo pip install virtualenvwrapper
mkdir ~/Envs
* Edite o arquivo ``.bashrc`` e adicione ao seu final as configurações abaixo para o virtualenvwrapper::
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
export WORKON_HOME=$HOME/.virtualenvs
export PROJECT_HOME=$HOME/Envs
source /usr/local/bin/virtualenvwrapper.sh
* Saia do terminal e entre novamente para que as configurações do virtualenvwrapper sejam carregadas.
Clonar o projeto do github, ou fazer um fork e depois clonar
------------------------------------------------------------
* Para apenas clonar do repositório do Interlegis::
cd ~/Envs
git clone git://github.com/interlegis/sapl
* 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 e clicar em **Fork**.
Será criado um domínio pelo qual será possível **clonar, corrigir, customizar, melhorar, contribuir, etc**::
cd ~/Envs
git clone git://github.com/[SEU NOME]/sapl
* As configurações e instruções de uso para o git estão espalhadas pela internet e possui muito coisa bacana. As tarefas básicas de git e suas interações com github são tranquilas de se aprender.
Criar o ambiente virtual de desenvolvimento para o SAPL
-------------------------------------------------------
* ::
mkvirtualenv sapl -a $HOME/Envs/sapl -p /usr/bin/python3
Instalação e configuração das dependências do projeto
-----------------------------------------------------
* **Acesse o terminal e entre no virtualenv**::
workon sapl
* **Instalar dependências python**::
pip install -r requirements/dev-requirements.txt
* **Configurar Postgresql**:
* Acessar Postrgresql para criar o banco ``sapl`` com a role ``sapl``::
sudo su - postgres
psql
CREATE ROLE sapl LOGIN
ENCRYPTED PASSWORD 'sapl'
NOSUPERUSER INHERIT CREATEDB NOCREATEROLE NOREPLICATION;
ALTER ROLE sapl VALID UNTIL 'infinity';
CREATE DATABASE sapl
WITH OWNER = sapl
ENCODING = 'UTF8'
TABLESPACE = pg_default
LC_COLLATE = 'pt_BR.UTF-8'
LC_CTYPE = 'pt_BR.UTF-8'
CONNECTION LIMIT = -1;
\q
exit
* Se você possui uma cópia da base de dados do SAPL, essa é a hora para restaurá-la.
* Obs: no ambiente de desenvolvimento, a role deve ter permissão para criar outro banco. Isso é usado pelos testes automatizados.
* (caso você já possua uma instalação do postrgresql anterior ao processo de instalação do ambiente de desenvolvimento do SAPL em sua máquina e sábia como fazer, esteja livre para proceder como desejar, porém, ao configurar o arquivo ``.env`` no próximo passo, as mesmas definições deverão ser usadas)
* **Configurar arquivo .env**:
* Criação da `SECRET_KEY <https://docs.djangoproject.com/es/1.9/ref/settings/#std:setting-SECRET_KEY>`_:
É necessário criar um projeto fake para extrair uma chave SECRET_KEY::
mkdir ~/Envs/temp
cd ~/Envs/temp
django-admin startproject sapl_temp
grep SECRET_KEY sapl_temp/sapl_temp/settings.py
Copie a linha que aparecerá, volte para a pasta do projeto SAPL e apague sua pasta temporária::
cd ~/Envs/sapl
rm -R ~/Envs/temp
* Criar o arquivo ``.env`` dentro da pasta ~/Envs/sapl/sapl/.env::
DATABASE_URL = postgresql://USER:PASSWORD@HOST:PORT/NAME
SECRET_KEY = Gere alguma chave e coloque aqui
DEBUG = [True/False]
EMAIL_USE_TLS = [True/False]
EMAIL_PORT = [Insira este parâmetro]
EMAIL_HOST = [Insira este parâmetro]
EMAIL_HOST_USER = [Insira este parâmetro]
EMAIL_HOST_PASSWORD = [Insira este parâmetro]
* Uma configuração mínima para atender os procedimentos acima seria::
DATABASE_URL = postgresql://sapl:sapl@localhost:5432/sapl
SECRET_KEY = 'Substitua esta linha pela copiada acima'
DEBUG = True
EMAIL_USE_TLS = True
EMAIL_PORT = 587
EMAIL_HOST =
EMAIL_HOST_USER =
EMAIL_HOST_PASSWORD =
* Instalar as dependências do ``bower``::
./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
* Atualizar arquivos estáticos::
./manage.py collectstatic --noinput
* Subir o servidor do django::
./manage.py runserver
* Acesse o SAPL em::
http://localhost:8000/
Instruções para criação do super usuário e de usuários de testes
===========================================================================
* Criar super usuário do django-contrib-admin (Será solicitado alguns dados para criação)::
./manage.py createsuperuser
* `Os perfis semânticos do SAPL <https://github.com/interlegis/sapl/blob/master/sapl/rules/__init__.py>`_ são fixos e atualizados a cada execução do comando:
./manage.py migrate
* Os perfis fixos não aceitam customização via admin, porém outros grupos podem ser criados. O SAPL não interferirá no conjunto de permissões definidas em grupos customizados e se comportará diante de usuários segundo seus grupos e suas permissões.
* Os usuários de testes de perfil são criados apenas se o SAPL estiver rodando em modo DEBUG=True. Todos com senha "interlegis", serão::
operador_administrativo
operador_protocoloadm
operador_comissoes
operador_materia
operador_norma
operador_sessao
operador_painel
operador_geral
Instruções para Tradução
========================
Nós utilizamos o `Transifex <https://www.transifex.com>`_ para gerenciar as traduções do projeto.
Se você deseja contribuir, por favor crie uma conta no site e peça para se juntar a nós em `Transifex SAPL Page <https://www.transifex.com/projects/p/sapl>`_.
Assim que for aceito, você já pode começar a traduzir.
Para integrar as últimas traduções ao projeto atual, siga estes passos:
* Siga as instruções em `Development Environment Installation`_.
* Instale `Transifex Client <http://docs.transifex.com/client/config/>`_.
Aviso:
O Transifex Client armazena senhas em 'plain text' no arquivo ``~/.transifexrc``.
Nós preferimos logar no site do Transifex por meio de redes sociais (GitHub, Google Plus, Linkedin) e modificar, frequentemente, a senha utilizada pelo client.
* `Pull translations <http://docs.transifex.com/client/pull/>`_ ou `push translations <http://docs.transifex.com/client/push/>`_ usando o client. Faça o Pull somente com o repositório vazio, isto é, faça o commit de suas mudanças antes de fazer o Pull de novas traduções.
* Execute o programa com ``.manage.py runserver`` e cheque o sistema para ver se as traduções tiveram efeito.
Nota:
O idioma do browser é utilizado para escolher as traduções que devem mostradas.
`Instruções para Tradução <https://github.com/interlegis/sapl/blob/master/docs/traducao.rst>`_
Orientações gerais de implementação
===================================
`Instruções para Implementação <https://github.com/interlegis/sapl/blob/master/docs/implementacoes.rst>`_
Boas Práticas
--------------
* Utilize a língua portuguesa em todo o código, nas mensagens de commit e na documentação do projeto.
* Mensagens de commit seguem o padrão de 50/72 colunas. Comece toda mensagem de commit com o verbo no infinitivo. Para mais informações, clique nos links abaixo:
- Http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
- Http://stackoverflow.com/questions/2290016/git-commit-messages-50-72-formatting
* Mantenha todo o código de acordo com o padrão da PEP8 (sem exceções).
* Antes de todo ``git push``:
- Execute ``git pull --rebase`` (quase sempre).
- Em casos excepcionais, faça somente ``git pull`` para criar um merge.
* Antes de ``git commit``, sempre:
- Execute ``./manage.py check``
- Execute todos os testes com ``py.test`` na pasta raiz do projeto
* Em caso de Implementação de modelo que envolva a classe ``django.contrib.auth.models.User``, não a use diretamente, use para isso a função ``get_settings_auth_user_model()`` de ``sapl.utils``. Exemplo:
- no lugar de ``owner = models.ForeignKey(User, ... )``
- use ``owner = models.ForeignKey(get_settings_auth_user_model(), ... )``
- Não use em qualquer modelagem futura, ``ForeignKey`` com ``User`` ou mesmo ``settings.AUTH_USER_MODEL`` sem o import correto que não é o do projeto e sim o que está em ``sapl.utils``, ou seja (``from django.conf import settings``)
- em https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#referencing-the-user-model é explicado por que ser dessa forma!
- Já em qualquer uso em implementação de execução, ao fazer uma query, por exemplo:
- não use ``django.contrib.auth.models.User`` para utilizar as caracteristicas do model, para isso, use esta função: django.contrib.auth.get_user_model()
- Seguir esses passos simplificará qualquer customização futura que venha a ser feita na autenticação do usuários ao evitar correções de inúmeros import's e ainda, desta forma, torna a funcionalidade de autenticação reimplementável por qualquer outro projeto que venha usar partes ou o todo do SAPL.
Atenção:
O usuário do banco de dados ``sapl`` deve ter a permissão ``create database`` no postgres para que os testes tenham sucesso
* Se você não faz parte da equipe principal, faça o fork deste repositório e envie pull requests.
Todos são bem-vindos para contribuir. Por favor, faça uma pull request separada para cada correção ou criação de novas funcionalidades.
* Novas funcionalidades estão sujeitas a aprovação, uma vez que elas podem ter impacto em várias pessoas.
Nós sugerimos que você abra uma nova issue para discutir novas funcionalidades. Elas podem ser escritas tanto em Português, quanto em Inglês.
Testes
------
* Escrever testes para todas as funcionalidades que você implementar.
* Manter a cobertura de testes próximo a 100%.
* Para executar todos os testes você deve entrar em seu virtualenv e executar este comando **na raiz do seu projeto**::
py.test
* Para executar os teste de cobertura use::
py.test --cov . --cov-report term --cov-report html && firefox htmlcov/index.html
Orientações gerais sobre o GitHub
===================================
`Instruções para GitHub <https://github.com/interlegis/sapl/blob/master/docs/howtogit.rst>`_
* Na primeira vez que for executar os testes após uma migração (``./manage.py migrate``) use a opção de recriação da base de testes.
É necessário fazer usar esta opção apenas uma vez::
py.test --create-db
Issues
------

110
docs/deploy.rst

@ -0,0 +1,110 @@
==============================
Instruções para fazer o Deploy
==============================
Para efeitos deste doc, foram consideradas as tecnologias NGINX + GUNICORN para servir a aplicação Django SAPL.
O NGINX é o servidor WEB, e o GUNICORN é o servidor da aplicação para o servidor WEB.
É altamente recomendável que para produção o SAPL não seja executado em modo debug.
Para isso edite o arquivo ``.env`` criado anteriormente em::
sudo nano /var/interlegis/sapl/sapl/.env
alterando o variável DEBUG para false::
DEBUG = False
Instalando Pacotes
------------------
Instalar o NGINX::
sudo apt-get install nginx
Instalar o Gunicorn::
sudo pip install gunicorn
Preparando o NGINX
------------------
sudo nano /etc/nginx/sites-available/sapl31.conf::
upstream ENDERECO_SITE {
server unix:/var/interlegis/sapl/run/gunicorn.sock fail_timeout=0;
}
server {
listen 80;
server_name ENDERECO_SITE;
client_max_body_size 4G;
access_log /var/log/nginx-access.log;
error_log /var/log/nginx-error.log;
location /static/ {
alias /var/interlegis/sapl/collected_static/;
}
location /media/ {
alias /var/interlegis/sapl/media/;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass http://ENDERECO_SITE;
break;
}
}
# Error pages
error_page 500 502 503 504 /500.html;
location = /500.html {
root /var/interlegis/sapl/sapl/static/;
}
}
Criar link simbólico para ativar o site::
sudo ln -s /etc/nginx/sites-available/sapl31.conf /etc/nginx/sites-enabled/sapl3
Reiniciar o nginx
sudo service nginx restart
Preparando o Gunicorn
---------------------
Na raiz do Projeto sapl, existe o arquivo chamado gunicorn_start.sh
Para definir o parametro NUM_WORKERS utilize a seguinte fórmula: 2 * CPUs 1.
Para uma máquina de CPU única o valor seria 3
Para rodar o gunicorn::
workon sapl
/var/interlegis/sapl/.gunicorn_start.sh
#Referências.
http://michal.karzynski.pl/blog/2013/06/09/django-nginx-gunicorn-virtualenv-supervisor/
Para multiplas aplicações Django.
http://michal.karzynski.pl/blog/2013/10/29/serving-multiple-django-applications-with-nginx-gunicorn-supervisor/

41
docs/howtogit.rst

@ -0,0 +1,41 @@
De forma muito simples e em linhas gerais o básico sobre GIT
Glosário
---------
Git - Sistema de controle de versão de aquivos
GitHub - É um serviço web que oferece diversas funcionalidades extras aplicadas ao git
Branch - Significa ramificar seu projeto, criar um snapshot.
Merge - Significa incorporar seu branch no master
Pode ser útil
-------------
Atualizar a base local:
git pull --rebase git://github.com/interlegis/sapl
Exibir informações:
git status
Na base local descartar alguma alteração feita nos arquivos:
git checkout sapl/legacy_migration_settings.py
Atualizar para alguma brach especifica (ex:785-atualizar-migracao):
git checkout 785-atualizar-migracao
Voltar para a branch master
git checkout master
Verificar 5 ultimos comits:
git log --oneline -n 5

73
docs/implementacoes.rst

@ -0,0 +1,73 @@
Orientações gerais de implementação
------------------------------------
Boas Práticas
--------------
* Utilize a língua portuguesa em todo o código, nas mensagens de commit e na documentação do projeto.
* Mensagens de commit seguem o padrão de 50/72 colunas. Comece toda mensagem de commit com o verbo no infinitivo. Para mais informações, clique nos links abaixo:
- Http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
- Http://stackoverflow.com/questions/2290016/git-commit-messages-50-72-formatting
* Mantenha todo o código de acordo com o padrão da PEP8 (sem exceções).
* Antes de todo ``git push``:
- Execute ``git pull --rebase`` (quase sempre).
- Em casos excepcionais, faça somente ``git pull`` para criar um merge.
* Antes de ``git commit``, sempre:
- Execute ``./manage.py check``
- Execute todos os testes com ``py.test`` na pasta raiz do projeto
* Em caso de Implementação de modelo que envolva a classe ``django.contrib.auth.models.User``, não a use diretamente, use para isso a função ``get_settings_auth_user_model()`` de ``sapl.utils``. Exemplo:
- no lugar de ``owner = models.ForeignKey(User, ... )``
- use ``owner = models.ForeignKey(get_settings_auth_user_model(), ... )``
- Não use em qualquer modelagem futura, ``ForeignKey`` com ``User`` ou mesmo ``settings.AUTH_USER_MODEL`` sem o import correto que não é o do projeto e sim o que está em ``sapl.utils``, ou seja (``from django.conf import settings``)
- em https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#referencing-the-user-model é explicado por que ser dessa forma!
- Já em qualquer uso em implementação de execução, ao fazer uma query, por exemplo:
- não use ``django.contrib.auth.models.User`` para utilizar as caracteristicas do model, para isso, use esta função: django.contrib.auth.get_user_model()
- Seguir esses passos simplificará qualquer customização futura que venha a ser feita na autenticação do usuários ao evitar correções de inúmeros import's e ainda, desta forma, torna a funcionalidade de autenticação reimplementável por qualquer outro projeto que venha usar partes ou o todo do SAPL.
Atenção:
O usuário do banco de dados ``sapl`` deve ter a permissão ``create database`` no postgres para que os testes tenham sucesso
* Se você não faz parte da equipe principal, faça o fork deste repositório e envie pull requests.
Todos são bem-vindos para contribuir. Por favor, faça uma pull request separada para cada correção ou criação de novas funcionalidades.
* Novas funcionalidades estão sujeitas a aprovação, uma vez que elas podem ter impacto em várias pessoas.
Nós sugerimos que você abra uma nova issue para discutir novas funcionalidades. Elas podem ser escritas tanto em Português, quanto em Inglês.
Testes
------
* Escrever testes para todas as funcionalidades que você implementar.
* Manter a cobertura de testes próximo a 100%.
* Para executar todos os testes você deve entrar em seu virtualenv e executar este comando **na raiz do seu projeto**::
py.test
* Para executar os teste de cobertura use::
py.test --cov . --cov-report term --cov-report html && firefox htmlcov/index.html
* Na primeira vez que for executar os testes após uma migração (``./manage.py migrate``) use a opção de recriação da base de testes.
É necessário fazer usar esta opção apenas uma vez::
py.test --create-db

31
docs/importacao_25_31.rst

@ -0,0 +1,31 @@
Instruções para Importação da base mysql 2.5
============================================
Para entrar no ambiente virtual::
workon sapl
Instalar Dependências::
pip3 install -r requirements/migration-requirements.txt
Criar um arquivo sapl/legacy/.env com o seguinte conteúdo (parametros de acesso ao banco 2.5)::
DATABASE_URL = mysql://[usuario do mysql]:[senha do myuysql]@[host]:[porta]/[banco]
o conteúdo do arquivo será semelhante a isso::
DATABASE_URL = mysql://sapl:sapl@localhost:3306/interlegis
Posteriormente rodar a seguinte sequencia de comandos estando no ambiente virtual::
./manage.py shell --settings=sapl.legacy_migration_settings
%run sapl/legacy/migration.py
migrate()

220
docs/instacao31.rst

@ -0,0 +1,220 @@
Instalação do Ambiente de Desenvolvimento
=========================================
* Procedimento testado nos seguintes SO's:
* `Ubuntu 16.04 64bits <https://github.com/interlegis/sapl/blob/master/README.rst>`_;
* Para esta instalação foi utilizado o usuário de sistema sapl31
Atualizar o sistema::
----------------------
::
sudo apt-get update
sudo apt-get upgrade
Instalar as seguintes dependências do sistema::
----------------------------------------------------------------------------------------
* ::
sudo apt-get install git python3-dev libpq-dev graphviz-dev graphviz \
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
sudo -i
curl -sL https://deb.nodesource.com/setup_5.x | bash -
exit
sudo apt-get install nodejs
sudo npm install npm -g
sudo npm install -g bower
Instalar o virtualenv usando python 3 para o projeto.
-----------------------------------------------------
* Para usar `virtualenvwrapper <https://virtualenvwrapper.readthedocs.org/en/latest/install.html#basic-installation>`_, instale com::
sudo pip3 install virtualenvwrapper
sudo mkdir -p /var/interlegis/.virtualenvs
* Ajustar as permissões - onde sapl31 trocar por usuario::
sudo chown -R sapl31:sapl31 /var/interlegis/
* Edite o arquivo ``.bashrc`` e adicione ao seu final as configurações abaixo para o virtualenvwrapper::
nano /home/sapl31/.bashrc
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
export WORKON_HOME=/var/interlegis/.virtualenvs
export PROJECT_HOME=/var/interlegis
source /usr/local/bin/virtualenvwrapper.sh
* Carregue as configurações do virtualenvwrapper::
source /home/sapl31/.bashrc
Clonar o projeto do github, ou fazer um fork e depois clonar
------------------------------------------------------------
* Para apenas clonar do repositório do Interlegis::
cd /var/interlegis
git clone git://github.com/interlegis/sapl
* 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 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
* As configurações e instruções de uso para o git estão espalhadas pela internet e possui muito coisa bacana. As tarefas básicas de git e suas interações com github são tranquilas de se aprender.
Criar o ambiente virtual de desenvolvimento para o SAPL
-------------------------------------------------------
* ::
mkvirtualenv sapl -a /var/interlegis/sapl -p /usr/bin/python3
Instalação e configuração das dependências do projeto
-----------------------------------------------------
* **Acesse o terminal e entre no virtualenv**::
workon sapl
* **Instalar dependências python**::
pip install -r /var/interlegis/sapl/requirements/dev-requirements.txt
* **Configurar Postgresql**::
sudo -u postgres psql -c "CREATE ROLE sapl LOGIN ENCRYPTED PASSWORD 'sapl' NOSUPERUSER INHERIT CREATEDB NOCREATEROLE NOREPLICATION;"
sudo -u postgres psql -c "ALTER ROLE sapl VALID UNTIL 'infinity';"
sudo -u postgres psql -c "CREATE DATABASE sapl WITH OWNER = sapl ENCODING = 'UTF8' TABLESPACE = pg_default LC_COLLATE = 'pt_BR.UTF-8' LC_CTYPE = 'pt_BR.UTF-8' CONNECTION LIMIT = -1;"
* Se você possui uma cópia da base de dados do SAPL, essa é a hora para restaurá-la.
* Obs: no ambiente de desenvolvimento, a role deve ter permissão para criar outro banco. Isso é usado pelos testes automatizados.
* (caso você já possua uma instalação do postrgresql anterior ao processo de instalação do ambiente de desenvolvimento do SAPL em sua máquina e sábia como fazer, esteja livre para proceder como desejar, porém, ao configurar o arquivo ``.env`` no próximo passo, as mesmas definições deverão ser usadas)
* **Ajustar as permissões - onde sapl31 trocar por usuario**::
sudo chown -R sapl31:sapl31 /var/interlegis/
* **Configurar arquivo .env**::
Criação da `SECRET_KEY <https://docs.djangoproject.com/es/1.9/ref/settings/#std:setting-SECRET_KEY>`_:
* **Criar o arquivo ``.env`` dentro da pasta /var/interlegis/sapl/sapl/.env**::
nano /var/interlegis/sapl/sapl/.env
DATABASE_URL = postgresql://USER:PASSWORD@HOST:PORT/NAME
SECRET_KEY = Gere alguma chave e coloque aqui
DEBUG = [True/False]
EMAIL_USE_TLS = [True/False]
EMAIL_PORT = [Insira este parâmetro]
EMAIL_HOST = [Insira este parâmetro]
EMAIL_HOST_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]
* 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::
python manage.py generate_secret_key
Copie a chave que aparecerá, edite o arquivo .env e altere o valor do parâmetro SECRET_KEY.
* Posicionar-se no diretorio do Projeto::
cd /var/interlegis/sapl
* Instalar as dependências do ``bower``::
./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
* Atualizar arquivos estáticos::
./manage.py collectstatic --noinput
* Subir o servidor do django::
./manage.py runserver 0.0.0.0:8001
* Acesse o SAPL em::
http://localhost:8001/
Instruções para criação do super usuário e de usuários de testes
===========================================================================
* Criar super usuário do django-contrib-admin (Será solicitado alguns dados para criação)::
./manage.py createsuperuser
* `Os perfis semânticos do SAPL <https://github.com/interlegis/sapl/blob/master/sapl/rules/__init__.py>`_ são fixos e atualizados a cada execução do comando::
./manage.py migrate
* Os perfis fixos não aceitam customização via admin, porém outros grupos podem ser criados. O SAPL não interferirá no conjunto de permissões definidas em grupos customizados e se comportará diante de usuários segundo seus grupos e suas permissões.
* Os usuários de testes de perfil são criados apenas se o SAPL estiver rodando em modo DEBUG=True. Todos com senha "interlegis", serão::
operador_administrativo
operador_protocoloadm
operador_comissoes
operador_materia
operador_norma
operador_sessao
operador_painel
operador_geral

27
docs/traducao.rst

@ -0,0 +1,27 @@
Instruções para Tradução
========================
Nós utilizamos o `Transifex <https://www.transifex.com>`_ para gerenciar as traduções do projeto.
Se você deseja contribuir, por favor crie uma conta no site e peça para se juntar a nós em `Transifex SAPL Page <https://www.transifex.com/projects/p/sapl>`_.
Assim que for aceito, você já pode começar a traduzir.
Para integrar as últimas traduções ao projeto atual, siga estes passos:
* Siga as instruções em `Development Environment Installation`_.
* Instale `Transifex Client <http://docs.transifex.com/client/config/>`_.
Aviso:
O Transifex Client armazena senhas em 'plain text' no arquivo ``~/.transifexrc``.
Nós preferimos logar no site do Transifex por meio de redes sociais (GitHub, Google Plus, Linkedin) e modificar, frequentemente, a senha utilizada pelo client.
* `Pull translations <http://docs.transifex.com/client/pull/>`_ ou `push translations <http://docs.transifex.com/client/push/>`_ usando o client. Faça o Pull somente com o repositório vazio, isto é, faça o commit de suas mudanças antes de fazer o Pull de novas traduções.
* Execute o programa com ``.manage.py runserver`` e cheque o sistema para ver se as traduções tiveram efeito.
Nota:
O idioma do browser é utilizado para escolher as traduções que devem mostradas.

6
gunicorn_start.sh

@ -3,8 +3,8 @@
# As seen in http://tutos.readthedocs.org/en/latest/source/ndg.html
NAME="SAPL" # Name of the application (*)
DJANGODIR=/home/sapl31/sapl # Django project directory (*)
SOCKFILE=/home/sapl31/sapl/run/gunicorn.sock # we will communicate using this unix socket (*)
DJANGODIR=/var/interlegis/sapl # Django project directory (*)
SOCKFILE=/var/interlegis/sapl/run/gunicorn.sock # we will communicate using this unix socket (*)
USER=`whoami` # the user to run as (*)
GROUP=`whoami` # the group to run as (*)
NUM_WORKERS=9 # how many worker processes should Gunicorn spawn (*)
@ -16,7 +16,7 @@ echo "Starting $NAME as `whoami`"
# Activate the virtual environment
cd $DJANGODIR
source ~/.virtualenvs/sapl/bin/activate
source /var/interlegis/.virtualenvs/sapl/bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

7
requirements/requirements.txt

@ -1,6 +1,9 @@
dj-database-url==0.4.1
django==1.9.7
django-admin-bootstrapped==2.5.7
django>=1.9,<1.10
# TODO O django-admin-bootstrapped 2.5.7 não inseriu a mudança que permite
# a compatibilidade com Django 1.9+. A linha abaixo será mudada quando uma
# nova versão do django-admin-bootstrapped for lançada
git+git://github.com/django-admin-bootstrapped/django-admin-bootstrapped.git
django-bootstrap3==7.0.1
django-bower==5.1.0
django-braces==1.9.0

1
sapl/api/serializers.py

@ -51,3 +51,4 @@ class MateriaLegislativaSerializer(serializers.ModelSerializer):
class Meta:
model = MateriaLegislativa
fields = '__all__'

9
sapl/api/views.py

@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework.filters import DjangoFilterBackend
from rest_framework.generics import ListAPIView
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from rest_framework.viewsets import GenericViewSet
from sapl.api.forms import AutorChoiceFilterSet
@ -59,10 +59,6 @@ class AutorListView(ListAPIView):
de Autores mas feito para Possíveis Autores armazenados
segundo o ContentType associado ao Tipo de Autor via
relacionamento genérico.
<<<<<<< HEAD
=======
>>>>>>> master
Busca feita sem django-filter processada no get_queryset
-> processo no cadastro de autores para seleção e busca
dos possíveis autores
@ -87,7 +83,7 @@ class AutorListView(ListAPIView):
TR_AUTOR_SERIALIZER = 3
# FIXME aplicar permissão correta de usuário
permission_classes = (IsAuthenticated,)
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Autor.objects.all()
model = Autor
@ -116,7 +112,6 @@ class AutorListView(ListAPIView):
desativa o django-filter se a busca for por possiveis autores
parametro tr = TR_CHOICE_SERIALIZER
"""
if self.tr == AutorListView.TR_CHOICE_SERIALIZER:
self.filter_class = None
self.filter_backends = []

3
sapl/base/admin.py

@ -8,6 +8,9 @@ register_all_models_in_admin(__name__)
admin.site.unregister(ProblemaMigracao)
admin.site.site_title = 'Administração - SAPL'
admin.site.site_header = 'Administração - SAPL'
@admin.register(ProblemaMigracao)
class ProblemaMigracaoAdmin(admin.ModelAdmin):

102
sapl/base/forms.py

@ -1,19 +1,20 @@
import django_filters
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import Group
from django.contrib.auth.forms import (AuthenticationForm, PasswordResetForm,
SetPasswordForm)
from django.contrib.auth.models import Group, User
from django.contrib.auth.password_validation import validate_password
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.forms import ModelForm
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
@ -27,6 +28,7 @@ from sapl.utils import (RANGE_ANOS, ChoiceWithoutValidationField,
from .models import AppConfig, CasaLegislativa
ACTION_CREATE_USERS_AUTOR_CHOICE = [
('C', _('Criar novo Usuário')),
('A', _('Associar um usuário existente')),
@ -96,7 +98,7 @@ class AutorForm(ModelForm):
label=_('Confirmar Email'))
username = forms.CharField(label=get_user_model()._meta.get_field(
'username').verbose_name.capitalize(),
get_user_model().USERNAME_FIELD).verbose_name.capitalize(),
required=False,
max_length=50)
@ -188,26 +190,36 @@ class AutorForm(ModelForm):
self.fields['autor_related'].initial = self.instance.autor_related
if self.instance.user:
self.fields['username'].initial = self.instance.user.username
self.fields['username'].initial = getattr(
self.instance.user,
get_user_model().USERNAME_FIELD)
self.fields['action_user'].initial = 'A'
self.fields['username'].label = string_concat(
self.fields['username'].label,
' (', self.instance.user.username, ')')
' (', getattr(
self.instance.user,
get_user_model().USERNAME_FIELD), ')')
if 'status_user' in self.Meta.fields:
self.fields['status_user'].initial = 'R'
self.fields['status_user'].label = string_concat(
self.fields['status_user'].label,
' (', self.instance.user.username, ')')
' (', getattr(
self.instance.user,
get_user_model().USERNAME_FIELD), ')')
self.fields['username'].widget.attrs.update({
'data': self.instance.user.username
'data': getattr(
self.instance.user,
get_user_model().USERNAME_FIELD)
if self.instance.user else ''})
if 'status_user' in self.Meta.fields:
self.fields['status_user'].widget.attrs.update({
'data': self.instance.user.username
'data': getattr(
self.instance.user,
get_user_model().USERNAME_FIELD)
if self.instance.user else ''})
def valida_igualdade(self, texto1, texto2, msg):
@ -225,7 +237,9 @@ class AutorForm(ModelForm):
if 'status_user' in self.Meta.fields:
if self.instance.pk and self.instance.user_id:
if self.instance.user.username != cd['username']:
if getattr(
self.instance.user.username,
get_user_model().USERNAME_FIELD) != cd['username']:
if 'status_user' not in cd or not cd['status_user']:
raise ValidationError(
_('Foi trocado ou removido o usuário deste Autor, '
@ -241,7 +255,8 @@ class AutorForm(ModelForm):
qs_user = qs_user.exclude(pk=self.instance.user.pk)
if cd['action_user'] == 'C':
if User.objects.filter(username=cd['username']).exists():
param_username = {get_user_model().USERNAME_FIELD: cd['username']}
if User.objects.filter(**param_username).exists():
raise ValidationError(
_('Já existe usuário com o username "%s". '
'Para utilizar esse username você deve selecionar '
@ -275,7 +290,8 @@ class AutorForm(ModelForm):
_('Já existe um Autor com este email.'))
elif cd['action_user'] == 'A':
if not User.objects.filter(username=cd['username']).exists():
param_username = {get_user_model().USERNAME_FIELD: cd['username']}
if not User.objects.filter(**param_username).exists():
raise ValidationError(
_('Não existe usuário com username "%s". '
'Para utilizar esse username você deve selecionar '
@ -286,7 +302,9 @@ class AutorForm(ModelForm):
if 'username' not in cd or not cd['username']:
raise ValidationError(_('O username deve ser informado.'))
if qs_autor.filter(user__username=cd['username']).exists():
param_username = {
'user__' + get_user_model().USERNAME_FIELD: cd['username']}
if qs_autor.filter(**param_username).exists():
raise ValidationError(
_('Já existe um Autor para este usuário.'))
@ -332,16 +350,23 @@ class AutorForm(ModelForm):
user_old = autor.user if autor.user_id else None
u = None
param_username = {
get_user_model().USERNAME_FIELD: self.cleaned_data['username']}
if self.cleaned_data['action_user'] == 'A':
u = get_user_model().objects.get(
username=self.cleaned_data['username'])
u = get_user_model().objects.get(**param_username)
if not u.is_active:
u.is_active = settings.DEBUG
u.save()
elif self.cleaned_data['action_user'] == 'C':
u = get_user_model().objects.create(
username=self.cleaned_data['username'],
email=self.cleaned_data['email'])
param_username = {
get_user_model().USERNAME_FIELD: self.cleaned_data['username']}
if get_user_model().USERNAME_FIELD != 'email':
param_username['email'] = self.cleaned_data['email']
u = get_user_model().objects.create(**param_username)
u.set_password(self.cleaned_data['senha'])
# Define usuário como ativo em ambiente de desenvolvimento
# pode logar sem a necessidade de passar pela validação de email
@ -671,3 +696,42 @@ class ConfiguracoesAppForm(ModelForm):
'texto_articulado_materia',
'texto_articulado_norma',
'proposicao_incorporacao_obrigatoria']
class RecuperarSenhaForm(PasswordResetForm):
def __init__(self, *args, **kwargs):
row1 = to_row(
[('email', 12)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Insira o e-mail cadastrado com a sua conta'),
row1,
form_actions(save_label='Enviar'))
)
super(RecuperarSenhaForm, self).__init__(*args, **kwargs)
def clean(self):
email_existente = User.objects.filter(
email=self.data['email']).exists()
if not email_existente:
msg = 'Não existe nenhum usuário cadastrado com este e-mail.'
raise ValidationError(msg)
return self.cleaned_data
class NovaSenhaForm(SetPasswordForm):
def __init__(self, user, *args, **kwargs):
self.user = user
super(NovaSenhaForm, self).__init__(user, *args, **kwargs)
row1 = to_row(
[('new_password1', 6),
('new_password2', 6)])
self.helper = FormHelper()
self.helper.layout = Layout(
row1,
form_actions(save_label='Enviar'))

38
sapl/base/urls.py

@ -1,12 +1,17 @@
from django.conf.urls import include, url
from django.contrib.auth import views
from django.contrib.auth.views import (password_reset,
password_reset_done,
password_reset_confirm,
password_reset_complete)
from django.contrib.auth.decorators import permission_required
from django.views.generic.base import TemplateView
from sapl.base.views import AutorCrud, ConfirmarEmailView, TipoAutorCrud
from .apps import AppConfig
from .forms import LoginForm
from .forms import LoginForm, NovaSenhaForm, RecuperarSenhaForm
from sapl.settings import EMAIL_SEND_USER
from .views import (AppConfigCrud, CasaLegislativaCrud, HelpView,
RelatorioAtasView, RelatorioHistoricoTramitacaoView,
RelatorioMateriasPorAnoAutorTipoView,
@ -16,6 +21,35 @@ from .views import (AppConfigCrud, CasaLegislativaCrud, HelpView,
app_name = AppConfig.name
recuperar_senha = [
url(r'^recuperar-senha/email/$',
password_reset,
{'post_reset_redirect': 'sapl.base:recuperar_senha_finalizado',
'email_template_name': 'base/recuperar_senha_email.html',
'html_email_template_name': 'base/recuperar_senha_email.html',
'template_name': 'base/recuperar_senha_email_form.html',
'from_email': EMAIL_SEND_USER,
'password_reset_form': RecuperarSenhaForm},
name='recuperar_senha_email'),
url(r'^recuperar-senha/finalizado/$',
password_reset_done,
{'template_name': 'base/recupera_senha_email_enviado.html'},
name='recuperar_senha_finalizado'),
url(r'^recuperar-senha/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
password_reset_confirm,
{'post_reset_redirect': 'sapl.base:recuperar_senha_completo',
'template_name': 'base/nova_senha_form.html',
'set_password_form': NovaSenhaForm},
name='recuperar_senha_confirma'),
url(r'^recuperar-senha/completo/$',
password_reset_complete,
{'template_name': 'base/recuperar_senha_completo.html'},
name='recuperar_senha_completo'),
]
urlpatterns = [
url(r'^sistema/autor/tipo/', include(TipoAutorCrud.get_urls())),
@ -66,4 +100,4 @@ urlpatterns = [
name='login'),
url(r'^logout/$', views.logout, {'next_page': '/login'}, name='logout'),
]
] + recuperar_senha

2
sapl/base/views.py

@ -62,7 +62,7 @@ class AutorCrud(CrudAux):
help_path = 'autor'
class BaseMixin(CrudAux.BaseMixin):
list_field_names = ['tipo', 'nome', 'user__username']
list_field_names = ['tipo', 'nome', 'user']
class DeleteView(CrudAux.DeleteView):

34
sapl/compilacao/compilacao_data_tables.sql

@ -1,6 +1,10 @@
INSERT INTO compilacao_perfilestruturaltextoarticulado (id, sigla, nome, padrao) VALUES (2, 'LC95-v', 'Lei Complementar 95 com Variação', false);
INSERT INTO compilacao_perfilestruturaltextoarticulado (id, sigla, nome, padrao) VALUES (1, 'LC95', 'Lei Complementar 95', true);
SELECT pg_catalog.setval('compilacao_perfilestruturaltextoarticulado_id_seq', 2, true);
INSERT INTO compilacao_perfilestruturaltextoarticulado (id, sigla, nome, padrao, parent_id) VALUES (1, 'LC95', 'Lei Complementar 95', true, null);
INSERT INTO compilacao_perfilestruturaltextoarticulado (id, sigla, nome, padrao, parent_id) VALUES (2, 'LC95-v', 'Lei Complementar 95 com Variação', false, null);
INSERT INTO compilacao_perfilestruturaltextoarticulado (id, sigla, nome, padrao, parent_id) VALUES (3, 'PLOL', 'Projeto de Lei Ordinária do Legislativo', false, 1);
INSERT INTO compilacao_perfilestruturaltextoarticulado (id, sigla, nome, padrao, parent_id) VALUES (4, 'REQ', 'Requerimento', false, null);
SELECT pg_catalog.setval('compilacao_perfilestruturaltextoarticulado_id_seq', 4, true);
update compilacao_perfilestruturaltextoarticulado set nome = 'Projeto de Lei Ordinária', sigla = 'PLO' where id = 3;
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (1, 'Articulação', 'articulacao', '', '', 0, '', '', '', '', '', '', true, 'N', 'N', 'N', 'N', 'N', 'N', '-', '-', '-', '-', '-', true, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (2, 'Ementa', 'ementa', '', '', 0, '', '', '', '', '', '', false, 'N', '1', '1', '1', '1', '1', '-', '-', '-', '-', '-', false, false);
@ -19,12 +23,14 @@ INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (117, 'SubSeção', 'subsecao', '', 'SubSeção ', 0, '', '<br>', '', '<br>', '<br>', '', false, '1', '1', '1', '1', '1', '1', '-', '-', '-', '-', '-', false, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (119, 'Artigo', 'artigo', '', 'Art. ', 9, '.', '&nbsp;&ndash;&nbsp;', '', '', '', '', true, '1', 'A', '1', '1', '1', '1', '-', '-', '-', '-', '-', true, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (120, 'Caput', 'caput', '', '', 0, '', '', '', '', '', '', false, 'N', 'N', 'N', 'N', 'N', 'N', '-', '-', '-', '-', '-', false, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (121, 'Parágrafo', 'paragrafo', '', '§ ;Parágrafo Único ', 9, '', '&nbsp;&ndash;&nbsp;', '', '', '', '', false, '1', '1', '1', '1', '1', '1', '-', '-', '-', '-', '-', false, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (122, 'Inciso', 'inciso', '', '', 0, '', '&nbsp;&ndash;&nbsp;', '', '', '', '', false, 'I', '1', '1', '1', '1', '1', '-', '-', '-', '-', '-', false, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (123, 'Alinea', 'alinea', '', '', 0, ')', '&nbsp;&ndash;&nbsp;', '', '', '', '', false, 'a', '1', '1', '1', '1', '1', '-', '-', '-', '-', '-', false, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (124, 'Item', 'item', '', '', 0, '', '&nbsp;&ndash;&nbsp;', '', '', '', '', false, '1', '1', '1', '1', '1', '1', '.', '.', '.', '.', '.', false, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (121, 'Parágrafo', 'paragrafo indent', '', '§ ;Parágrafo Único ', 9, '', '&nbsp;&ndash;&nbsp;', '', '', '', '', false, '1', '1', '1', '1', '1', '1', '-', '-', '-', '-', '-', false, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (122, 'Inciso', 'inciso indent', '', '', 0, '', '&nbsp;&ndash;&nbsp;', '', '', '', '', false, 'I', '1', '1', '1', '1', '1', '-', '-', '-', '-', '-', false, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (123, 'Alinea', 'alinea indent', '', '', 0, ')', '&nbsp;&ndash;&nbsp;', '', '', '', '', false, 'a', '1', '1', '1', '1', '1', '-', '-', '-', '-', '-', false, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (124, 'Item', 'item indent', '', '', 0, '', '&nbsp;&ndash;&nbsp;', '', '', '', '', false, '1', '1', '1', '1', '1', '1', '.', '.', '.', '.', '.', false, false);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (125, 'Texto Não Estruturado', 'texto_n_estruturado', '', '', 0, '', '', '', '', '', '', true, 'N', 'N', 'N', 'N', 'N', 'N', '-', '-', '-', '-', '-', false, false);
SELECT pg_catalog.setval('compilacao_tipodispositivo_id_seq', 125, true);
INSERT INTO compilacao_tipodispositivo (id, nome, class_css, rotulo_prefixo_html, rotulo_prefixo_texto, rotulo_ordinal, rotulo_sufixo_texto, rotulo_sufixo_html, texto_prefixo_html, texto_sufixo_html, nota_automatica_prefixo_html, nota_automatica_sufixo_html, contagem_continua, formato_variacao0, formato_variacao1, formato_variacao2, formato_variacao3, formato_variacao4, formato_variacao5, rotulo_separador_variacao01, rotulo_separador_variacao12, rotulo_separador_variacao23, rotulo_separador_variacao34, rotulo_separador_variacao45, dispositivo_de_articulacao, dispositivo_de_alteracao) VALUES (126, 'Justificativa', 'justificativa', '', '', 0, '', '', '', '<div class="titulo">Justificativa</div>', '', '', true, 'N', 'N', 'N', 'N', 'N', 'N', '-', '-', '-', '-', '-', false, false);
SELECT pg_catalog.setval('compilacao_tipodispositivo_id_seq', 126, true);
delete from compilacao_tipodispositivorelationship;
@ -245,6 +251,18 @@ INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id,
INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (125, 104, false, 1, -1, false);
INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (125, 104, false, 2, -1, false);
INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (126, 1, false, 3, -1, false);
INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (2, 1, false, 4, -1, false);
INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (125, 1, false, 4, -1, false);
INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (126, 1, false, 4, -1, false);
INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (122, 119, false, 1, -1, false);
INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (122, 119, false, 1, -1, false);
INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (122, 119, false, 2, -1, true);
INSERT INTO compilacao_tipodispositivorelationship (filho_permitido_id, pai_id, filho_de_insercao_automatica, perfil_id, quantidade_permitida, permitir_variacao) VALUES (122, 119, false, 2, -1, true);
INSERT INTO compilacao_tiponota (id, sigla, nome, modelo) VALUES (1, 'NE', 'Nota Explicativa', '');

8
sapl/compilacao/forms.py

@ -8,6 +8,7 @@ from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset,
from django import forms
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from django.db.models import Q
from django.forms import widgets
from django.forms.forms import Form
from django.forms.models import ModelForm
from django.template import defaultfilters
@ -24,6 +25,7 @@ from sapl.compilacao.utils import DISPOSITIVO_SELECT_RELATED
from sapl.crispy_layout_mixin import SaplFormLayout, to_column, to_row
from sapl.utils import YES_NO_CHOICES
error_messages = {
'required': _('Este campo é obrigatório'),
'invalid': _('URL inválida.')
@ -62,9 +64,12 @@ class TipoTaForm(ModelForm):
'descricao',
'content_type',
'participacao_social',
'publicacao_func'
'publicacao_func',
'perfis'
]
widgets = {'perfis': widgets.CheckboxSelectMultiple()}
def __init__(self, *args, **kwargs):
row1 = to_row([
@ -75,6 +80,7 @@ class TipoTaForm(ModelForm):
row2 = to_row([
(InlineRadios('participacao_social'), 3),
(InlineRadios('publicacao_func'), 3),
('perfis', 12),
])
self.helper = FormHelper()

21
sapl/compilacao/migrations/0070_perfilestruturaltextoarticulado_parent.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-11 13:01
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0069_auto_20161107_1932'),
]
operations = [
migrations.AddField(
model_name='perfilestruturaltextoarticulado',
name='parent',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='perfil_parent_set', to='compilacao.PerfilEstruturalTextoArticulado', verbose_name='Perfil Herdado'),
),
]

20
sapl/compilacao/migrations/0071_tipotextoarticulado_perfis.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-12 14:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0070_perfilestruturaltextoarticulado_parent'),
]
operations = [
migrations.AddField(
model_name='tipotextoarticulado',
name='perfis',
field=models.ManyToManyField(blank=True, help_text='\n Apenas os perfis selecionados aqui estarão disponíveis\n para o editor de Textos Articulados cujo Tipo seja este\n em edição.\n ', to='compilacao.PerfilEstruturalTextoArticulado', verbose_name='Perfis Estruturais de Textos Articulados'),
),
]

55
sapl/compilacao/migrations/0072_auto_20161112_1553.py

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-12 15:53
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0071_tipotextoarticulado_perfis'),
]
operations = [
migrations.AlterField(
model_name='tipodispositivo',
name='nota_automatica_prefixo_html',
field=models.TextField(blank=True, verbose_name='Prefixo html da nota automática'),
),
migrations.AlterField(
model_name='tipodispositivo',
name='nota_automatica_sufixo_html',
field=models.TextField(blank=True, verbose_name='Sufixo html da nota automática'),
),
migrations.AlterField(
model_name='tipodispositivo',
name='rotulo_prefixo_html',
field=models.TextField(blank=True, verbose_name='Prefixo html do rótulo'),
),
migrations.AlterField(
model_name='tipodispositivo',
name='rotulo_prefixo_texto',
field=models.TextField(blank=True, verbose_name='Prefixo de Edição do rótulo'),
),
migrations.AlterField(
model_name='tipodispositivo',
name='rotulo_sufixo_html',
field=models.TextField(blank=True, verbose_name='Sufixo html do rótulo'),
),
migrations.AlterField(
model_name='tipodispositivo',
name='rotulo_sufixo_texto',
field=models.TextField(blank=True, verbose_name='Sufixo de Edição do rótulo'),
),
migrations.AlterField(
model_name='tipodispositivo',
name='texto_prefixo_html',
field=models.TextField(blank=True, verbose_name='Prefixo html do texto'),
),
migrations.AlterField(
model_name='tipodispositivo',
name='texto_sufixo_html',
field=models.TextField(blank=True, verbose_name='Sufixo html do texto'),
),
]

130
sapl/compilacao/models.py

@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import F, Q
from django.db.models.aggregates import Max
from django.db.models.deletion import PROTECT
from django.http.response import Http404
from django.template import defaultfilters
from django.utils.decorators import classonlymethod
@ -74,6 +75,31 @@ class BaseModel(models.Model):
update_fields=update_fields)
class PerfilEstruturalTextoArticulado(BaseModel):
sigla = models.CharField(
max_length=10, unique=True, verbose_name=_('Sigla'))
nome = models.CharField(max_length=50, verbose_name=_('Nome'))
padrao = models.BooleanField(
default=False,
choices=YES_NO_CHOICES, verbose_name=_('Padrão'))
parent = models.ForeignKey(
'self',
blank=True, null=True, default=None,
related_name='perfil_parent_set',
on_delete=PROTECT,
verbose_name=_('Perfil Herdado'))
class Meta:
verbose_name = _('Perfil Estrutural de Texto Articulado')
verbose_name_plural = _('Perfis Estruturais de Textos Articulados')
ordering = ['-padrao', 'sigla']
def __str__(self):
return self.nome
class TipoTextoArticulado(models.Model):
sigla = models.CharField(max_length=3, verbose_name=_('Sigla'))
descricao = models.CharField(max_length=50, verbose_name=_('Descrição'))
@ -93,6 +119,15 @@ class TipoTextoArticulado(models.Model):
choices=YES_NO_CHOICES,
verbose_name=_('Histórico de Publicação'))
perfis = models.ManyToManyField(
PerfilEstruturalTextoArticulado,
blank=True, verbose_name=_('Perfis Estruturais de Textos Articulados'),
help_text=_("""
Apenas os perfis selecionados aqui estarão disponíveis
para o editor de Textos Articulados cujo Tipo seja este
em edição.
"""))
class Meta:
verbose_name = _('Tipo de Texto Articulado')
verbose_name_plural = _('Tipos de Texto Articulados')
@ -538,13 +573,11 @@ class TipoDispositivo(BaseModel):
blank=True,
max_length=20,
verbose_name=_('Classe CSS'))
rotulo_prefixo_html = models.CharField(
rotulo_prefixo_html = models.TextField(
blank=True,
max_length=100,
verbose_name=_('Prefixo html do rótulo'))
rotulo_prefixo_texto = models.CharField(
rotulo_prefixo_texto = models.TextField(
blank=True,
max_length=30,
verbose_name=_('Prefixo de Edição do rótulo'))
rotulo_ordinal = models.IntegerField(
choices=TIPO_NUMERO_ROTULO,
@ -574,29 +607,23 @@ class TipoDispositivo(BaseModel):
max_length=1,
default="-",
verbose_name=_('Separador entre Variação 4 e Variação 5'))
rotulo_sufixo_texto = models.CharField(
rotulo_sufixo_texto = models.TextField(
blank=True,
max_length=30,
verbose_name=_('Sufixo de Edição do rótulo'))
rotulo_sufixo_html = models.CharField(
rotulo_sufixo_html = models.TextField(
blank=True,
max_length=100,
verbose_name=_('Sufixo html do rótulo'))
texto_prefixo_html = models.CharField(
texto_prefixo_html = models.TextField(
blank=True,
max_length=100,
verbose_name=_('Prefixo html do texto'))
texto_sufixo_html = models.CharField(
texto_sufixo_html = models.TextField(
blank=True,
max_length=100,
verbose_name=_('Sufixo html do texto'))
nota_automatica_prefixo_html = models.CharField(
nota_automatica_prefixo_html = models.TextField(
blank=True,
max_length=100,
verbose_name=_('Prefixo html da nota automática'))
nota_automatica_sufixo_html = models.CharField(
nota_automatica_sufixo_html = models.TextField(
blank=True,
max_length=100,
verbose_name=_('Sufixo html da nota automática'))
contagem_continua = models.BooleanField(
choices=YES_NO_CHOICES, verbose_name=_('Contagem contínua'))
@ -657,58 +684,49 @@ class TipoDispositivo(BaseModel):
def permitido_inserir_in(
self, pai_relativo, include_relative_autos=True, perfil_pk=None):
perfil = PerfilEstruturalTextoArticulado.objects.all()
if not perfil_pk:
perfis = PerfilEstruturalTextoArticulado.objects.filter(
padrao=True)[:1]
if not perfis.exists():
return False
perfil_pk = perfis[0].pk
perfil = perfil.filter(padrao=True)
pp = self.possiveis_pais.filter(pai=pai_relativo, perfil_id=perfil_pk)
if pp.exists():
if not include_relative_autos:
if pp[0].filho_de_insercao_automatica:
return False
return True
return False
def permitido_variacao(
self, base, perfil_pk=None):
if not perfil_pk:
perfis = PerfilEstruturalTextoArticulado.objects.filter(
padrao=True)[:1]
else:
perfil = perfil.filter(pk=perfil_pk)
if not perfis.exists():
return False
if not perfil.exists():
return False
perfil_pk = perfis[0].pk
perfil = perfil[0]
pp = self.possiveis_pais.filter(pai=base, perfil_id=perfil_pk)
if pp.exists():
if pp[0].permitir_variacao:
while perfil:
pp = self.possiveis_pais.filter(pai=pai_relativo, perfil=perfil)
if pp.exists():
if not include_relative_autos:
if pp[0].filho_de_insercao_automatica:
return False
return True
perfil = perfil.parent
return False
def permitido_variacao(self, base, perfil_pk=None):
class PerfilEstruturalTextoArticulado(BaseModel):
sigla = models.CharField(
max_length=10, unique=True, verbose_name=_('Sigla'))
nome = models.CharField(max_length=50, verbose_name=_('Nome'))
padrao = models.BooleanField(
default=False,
choices=YES_NO_CHOICES, verbose_name=_('Padrão'))
perfil = PerfilEstruturalTextoArticulado.objects.all()
if not perfil_pk:
perfil = perfil.filter(padrao=True)
class Meta:
verbose_name = _('Perfil Estrutural de Texto Articulado')
verbose_name_plural = _('Perfis Estruturais de Textos Articulados')
else:
perfil = perfil.filter(pk=perfil_pk)
ordering = ['-padrao', 'sigla']
if not perfil.exists():
return False
def __str__(self):
return self.nome
perfil = perfil[0]
while perfil:
pp = self.possiveis_pais.filter(pai=base, perfil=perfil)
if pp.exists():
if pp[0].permitir_variacao:
return True
perfil = perfil.parent
return False
class TipoDispositivoRelationship(BaseModel):

87
sapl/compilacao/views.py

@ -162,7 +162,7 @@ class IntegracaoTaView(TemplateView):
(request.user.has_perm(
'compilacao.change_your_dispositivo_edicao_dinamica') and
ta_values.get('privacidade', STATUS_TA_EDITION
) == STATUS_TA_PUBLIC)):
) == STATUS_TA_PRIVATE)):
"""
o texto articulado será criado/atualizado se:
- texto articulado foi criado.
@ -1154,9 +1154,10 @@ class TextEditView(CompMixin, TemplateView):
result = Dispositivo.objects.filter(ta_id=self.kwargs['ta_id'])
if not result.exists():
# FIXME a inserção básica deve ser refatorada para não depender
# das classes css
ta = self.object
td = TipoDispositivo.objects.filter(class_css='articulacao')[0]
a = Dispositivo()
a.nivel = 0
@ -1169,6 +1170,8 @@ class TextEditView(CompMixin, TemplateView):
a.inicio_eficacia = ta.data
a.save()
return
td = TipoDispositivo.objects.filter(class_css='ementa')[0]
e = Dispositivo()
e.nivel = 1
@ -1638,9 +1641,6 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
'action': 'json_add_prior',
'itens': []}]
if request and 'perfil_estrutural' not in request.session:
self.set_perfil_in_session(request)
perfil_pk = request.session['perfil_estrutural']
prox_possivel = Dispositivo.objects.filter(
@ -2222,42 +2222,47 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
return JsonResponse(data, safe=False)
def get_queryset_perfil_estrutural(self):
perfis = PerfilEstruturalTextoArticulado.objects.all()
return perfis
def json_get_perfis(self, context):
if 'perfil_pk' in self.request.GET:
self.set_perfil_in_session(
self.request, self.request.GET['perfil_pk'])
elif 'perfil_estrutural' not in self.request.session:
self.set_perfil_in_session(request=self.request)
data = {'pk': self.kwargs['dispositivo_id'],
'pai': [self.kwargs['dispositivo_id'], ]}
return data
def set_perfil_in_session(self, request=None, perfil_id=0):
if not request:
return None
def update_perfis(self):
qs = PerfilEstruturalTextoArticulado.objects.all()
request = self.request
if perfil_id:
perfil = PerfilEstruturalTextoArticulado.objects.get(
pk=perfil_id)
request.session['perfil_estrutural'] = perfil.pk
return perfil.pk
ta = None
if hasattr(self, 'object') and isinstance(self.object, Dispositivo):
ta = self.object.ta
elif hasattr(self, 'object') and isinstance(
self.object, TextoArticulado):
ta = self.object
else:
perfis = PerfilEstruturalTextoArticulado.objects.filter(
padrao=True)[:1]
ta_id = self.kwargs.get('ta_id', 0)
if ta_id:
ta = TextoArticulado.objects.get(pk=ta_id)
if not perfis.exists():
request.session.pop('perfil_estrutural')
if ta:
if ta.content_object and hasattr(ta.content_object, 'perfis'):
qs = ta.content_object.perfis
else:
request.session['perfil_estrutural'] = perfis[0].pk
return perfis[0].pk
return None
qs = ta.tipo_ta.perfis.all()
perfil_get = request.GET.get('perfil_pk', 0)
if perfil_get and qs.filter(id=perfil_get).exists():
request.session['perfil_estrutural'] = int(perfil_get)
return qs
perfil_session = request.session.get('perfil_estrutural', perfil_get)
if perfil_session and qs.filter(id=perfil_session).exists():
request.session['perfil_estrutural'] = int(perfil_session)
return qs
if qs.exists():
request.session['perfil_estrutural'] = qs.first().id
return qs
def json_add_next_registra_inclusao(
self, context, local_add='json_add_next'):
@ -2417,9 +2422,11 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
dispositivos_do_bloco = \
bloco_alteracao.dispositivos_alterados_set.order_by(
'ordem_bloco_atualizador')
if dispositivos_do_bloco.exists:
if dispositivos_do_bloco.exists():
ndp.ordem_bloco_atualizador = dispositivos_do_bloco.last(
).ordem_bloco_atualizador + Dispositivo.INTERVALO_ORDEM
else:
ndp.ordem_bloco_atualizador = Dispositivo.INTERVALO_ORDEM
ndp.save()
p.dispositivo_subsequente = ndp
@ -2500,6 +2507,9 @@ class DispositivoDinamicEditView(
self.template_name = 'compilacao/text_edit_bloco.html'
return TextEditView.get(self, request, *args, **kwargs)
self.object = Dispositivo.objects.get(pk=self.kwargs['dispositivo_id'])
perfil_estrutural_list = self.update_perfis()
self.template_name = 'compilacao/ajax_form.html'
self.action = request.GET['action']
@ -2519,9 +2529,6 @@ class DispositivoDinamicEditView(
elif self.action.startswith('get_actions'):
self.form_class = None
self.object = Dispositivo.objects.get(
pk=self.kwargs['dispositivo_id'])
ta_id = self.kwargs['ta_id']
context = {}
@ -2537,17 +2544,9 @@ class DispositivoDinamicEditView(
'ajax_actions_dinamic_edit.html')
if ta_id == str(self.object.ta_id):
context['perfil_estrutural_list'] = perfil_estrutural_list
context['allowed_inserts'] = self.allowed_inserts()
if 'perfil_pk' in request.GET:
self.set_perfil_in_session(
request, request.GET['perfil_pk'])
elif 'perfil_estrutural' not in request.session:
self.set_perfil_in_session(request=request)
context['perfil_estrutural_list'
] = PerfilEstruturalTextoArticulado.objects.all()
return self.render_to_response(context)
elif self.action.startswith('json_'):

14
sapl/crispy_layout_mixin.py

@ -61,8 +61,12 @@ def get_field_display(obj, fieldname):
try:
field = obj._meta.get_field(fieldname)
except:
value = getattr(obj, fieldname)
return '', str(value)
field = getattr(obj, fieldname)
if 'ManyRelatedManager' not in str(type(field))\
and 'RelatedManager' not in str(type(field))\
and 'GenericRelatedObjectManager' not in str(type(field)):
return '', str(field)
verbose_name = str(field.verbose_name)\
if hasattr(field, 'verbose_name') else ''
if hasattr(field, 'choices') and field.choices:
@ -98,7 +102,11 @@ def get_field_display(obj, fieldname):
display += '<li>%s</li>' % str(v)
display += '</ul>'
if not verbose_name:
verbose_name = str(field.related_model._meta.verbose_name_plural)
if hasattr(field, 'related_model'):
verbose_name = str(
field.related_model._meta.verbose_name_plural)
elif hasattr(field, 'model'):
verbose_name = str(field.model._meta.verbose_name_plural)
else:
display = str(value)
return verbose_name, display

14
sapl/crud/base.py

@ -180,7 +180,15 @@ class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin):
perms = self.get_permission_required()
# Torna a view pública se não possuir conteudo
# no atributo permission_required
return self.request.user.has_perms(perms) if len(perms) else True
if not len(perms):
return True
for perm in perms:
if self.request.user.has_perm(perm):
return True
return False
# return self.request.user.has_perms(perms) if len(perms) else True
def dispatch(self, request, *args, **kwargs):
if not self.has_permission():
@ -257,9 +265,7 @@ class CrudBaseMixin(CrispyLayoutFormMixin):
self.permission_required = list(
set(self.permission_required) - set(obj.public))
else:
obj.public = list(
set(self.permission_required) -
set((RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE)))
obj.public = []
self.permission_required = tuple((
self.permission(pr) for pr in self.permission_required))

45
sapl/legacy/migration.py

@ -4,7 +4,7 @@ import pkg_resources
import yaml
from django.apps import apps
from django.apps.config import AppConfig
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
from django.db import connections, models
from django.db.models import CharField, TextField, ProtectedError
@ -12,8 +12,8 @@ from django.db.models.base import ModelBase
from model_mommy import mommy
from model_mommy.mommy import foreign_key_required, make
from sapl.base.models import Autor, ProblemaMigracao, TipoAutor
from sapl.comissoes.models import Composicao, Participacao
from sapl.base.models import Autor, ProblemaMigracao
from sapl.comissoes.models import Comissao, Composicao, Participacao
from sapl.materia.models import (Proposicao, StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao,
Tramitacao)
@ -332,22 +332,24 @@ class DataMigrator:
# warning: model/app migration order is of utmost importance
self.to_delete = []
ProblemaMigracao.objects.all().delete()
User.objects.all().delete()
get_user_model().objects.exclude(is_superuser=True).delete()
info('Começando migração: %s...' % obj)
self._do_migrate(obj)
# exclude logically deleted in legacy base
info('Deletando models com ind_excluido...')
for obj in self.to_delete:
try:
obj.delete()
except ProtectedError:
msg = 'A entrada de PK %s da model %s não pode ser excluida' %\
(obj.pk, obj._meta.model_name)
descricao = 'Um ou mais objetos protegidos '
warn(msg + ' => ' + descricao)
save_relation(obj=obj, problema=msg,
descricao=descricao, eh_stub=False)
while self.to_delete:
for obj in self.to_delete:
try:
obj.delete()
self.to_delete.remove(obj)
except ProtectedError:
msg = 'A entrada de PK %s da model %s não pode ser ' \
'excluida' % (obj.pk, obj._meta.model_name)
descricao = 'Um ou mais objetos protegidos '
warn(msg + ' => ' + descricao)
save_relation(obj=obj, problema=msg,
descricao=descricao, eh_stub=False)
info('Deletando stubs desnecessários...')
while self.delete_stubs():
@ -532,14 +534,21 @@ def adjust_normajuridica_depois_salvar(new, old):
def adjust_autor(new, old):
new.autor_related = TipoAutor.objects.get(pk=old.tip_autor)
if old.cod_parlamentar:
new.autor_related = Parlamentar.objects.get(pk=old.cod_parlamentar)
elif old.cod_comissao:
new.autor_related = Comissao.objects.get(pk=old.cod_comissao)
if old.col_username:
if not User.objects.filter(username=old.col_username).exists():
user = User(username=old.col_username, password=12345)
if not get_user_model().objects.filter(
username=old.col_username).exists():
user = get_user_model()(
username=old.col_username, password=12345)
user.save()
new.user = user
else:
new.user = User.objects.filter(username=old.col_username)[0]
new.user = get_user_model().objects.filter(
username=old.col_username)[0]
AJUSTE_ANTES_SALVAR = {

145
sapl/materia/forms.py

@ -1,12 +1,14 @@
from datetime import date, datetime
import os
from datetime import date, datetime
from itertools import chain
import django_filters
from crispy_forms.bootstrap import (Alert, FormActions, InlineCheckboxes,
InlineRadios)
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (HTML, Button, Column, Field, Fieldset, Layout,
Submit)
from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset,
Layout, Submit)
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
@ -16,17 +18,23 @@ from django.db import models, transaction
from django.db.models import Max
from django.forms import ModelForm, widgets
from django.forms.forms import Form
from django.forms.widgets import Select
from django.utils.encoding import force_text
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
import django_filters
import sapl
from sapl.base.models import Autor
from sapl.comissoes.models import Comissao
from sapl.compilacao.models import STATUS_TA_PRIVATE,\
STATUS_TA_IMMUTABLE_PUBLIC, TextoArticulado, STATUS_TA_PUBLIC
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC,
STATUS_TA_PRIVATE, STATUS_TA_PUBLIC,
PerfilEstruturalTextoArticulado,
TextoArticulado)
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
to_row)
from sapl.materia.models import TipoProposicao, MateriaLegislativa,\
RegimeTramitacao, TipoDocumento
from sapl.materia.models import (MateriaLegislativa, RegimeTramitacao,
TipoDocumento, TipoProposicao)
from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica)
from sapl.parlamentares.models import Parlamentar
@ -36,12 +44,10 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
ChoiceWithoutValidationField,
MateriaPesquisaOrderingFilter, RangeWidgetOverride,
autor_label, autor_modal, models_with_gr_for_model)
import sapl
from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial,
DocumentoAcessorio, Numeracao,
Proposicao, Relatoria, TipoMateriaLegislativa, Tramitacao,
UnidadeTramitacao)
DocumentoAcessorio, Numeracao, Proposicao, Relatoria,
TipoMateriaLegislativa, Tramitacao, UnidadeTramitacao)
def ANO_CHOICES():
@ -472,7 +478,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
ementa = django_filters.CharFilter(lookup_expr='icontains')
em_tramitacao = django_filters.ChoiceFilter(required=False,
label=u'Ano da Matéria',
label=u'Em tramitação',
choices=em_tramitacao)
o = MateriaPesquisaOrderingFilter()
@ -583,6 +589,8 @@ def filtra_tramitacao_destino_and_status(status, destino):
class DespachoInicialForm(ModelForm):
comissao = forms.ModelChoiceField(
queryset=Comissao.objects.filter(ativa=True))
class Meta:
model = DespachoInicial
@ -673,7 +681,7 @@ class PrimeiraTramitacaoEmLoteFilterSet(django_filters.FilterSet):
self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['data_apresentacao'].label = 'Data (Inicial - Final)'
self.form.fields['tipo'].required = True
self.form.fields['data_apresentacao'].required = True
self.form.fields['data_apresentacao'].required = False
row1 = to_row([('tipo', 12)])
row2 = to_row([('data_apresentacao', 12)])
@ -708,7 +716,7 @@ class TramitacaoEmLoteFilterSet(django_filters.FilterSet):
self.filters['tramitacao__unidade_tramitacao_destino'
].label = 'Unidade Destino (Último Destino)'
self.form.fields['tipo'].required = True
self.form.fields['data_apresentacao'].required = True
self.form.fields['data_apresentacao'].required = False
self.form.fields['tramitacao__status'].required = True
self.form.fields[
'tramitacao__unidade_tramitacao_destino'].required = True
@ -747,16 +755,20 @@ class TipoProposicaoForm(ModelForm):
fields = ['descricao',
'content_type',
'tipo_conteudo_related_radio',
'tipo_conteudo_related']
'tipo_conteudo_related',
'perfis']
widgets = {'tipo_conteudo_related': forms.HiddenInput()}
widgets = {'tipo_conteudo_related': forms.HiddenInput(),
'perfis': widgets.CheckboxSelectMultiple()}
def __init__(self, *args, **kwargs):
tipo_select = Fieldset(TipoProposicao._meta.verbose_name,
to_column(('descricao', 5)),
to_column(('content_type', 7)),
to_column(('tipo_conteudo_related_radio', 12)))
Div(to_column(('descricao', 5)),
to_column(('content_type', 7)), css_class='clearfix'),
to_column(('tipo_conteudo_related_radio', 6)),
to_column(('perfis', 6)))
self.helper = FormHelper()
self.helper.layout = SaplFormLayout(tipo_select)
@ -795,7 +807,7 @@ class TipoProposicaoForm(ModelForm):
@transaction.atomic
def save(self, commit=False):
tipo_proposicao = super(TipoProposicaoForm, self).save(commit)
tipo_proposicao = self.instance
assert tipo_proposicao.content_type
@ -803,9 +815,45 @@ class TipoProposicaoForm(ModelForm):
tipo_proposicao.content_type.model_class(
).objects.get(pk=self.cleaned_data['tipo_conteudo_related'])
tipo_proposicao.save()
return super().save(True)
return tipo_proposicao
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, choices, 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):
@ -827,11 +875,11 @@ class ProposicaoForm(forms.ModelForm):
ano_materia = forms.CharField(
label='Ano', required=False)
tipo_texto = forms.MultipleChoiceField(
tipo_texto = forms.ChoiceField(
label=_('Tipo do Texto da Proposição'),
required=False,
choices=TIPO_TEXTO_CHOICE,
widget=widgets.CheckboxSelectMultiple())
widget=widgets.RadioSelect())
materia_de_vinculo = forms.ModelChoiceField(
queryset=MateriaLegislativa.objects.all(),
@ -851,7 +899,8 @@ class ProposicaoForm(forms.ModelForm):
'tipo_texto']
widgets = {
'descricao': widgets.Textarea(attrs={'rows': 4})}
'descricao': widgets.Textarea(attrs={'rows': 4}),
'tipo': TipoProposicaoSelect()}
def __init__(self, *args, **kwargs):
self.texto_articulado_proposicao = sapl.base.models.AppConfig.attr(
@ -882,7 +931,7 @@ class ProposicaoForm(forms.ModelForm):
if self.texto_articulado_proposicao:
fields.append(
to_column((InlineCheckboxes('tipo_texto'), 5)),)
to_column((InlineRadios('tipo_texto'), 5)),)
fields.append(to_column((
'texto_original', 7 if self.texto_articulado_proposicao else 12)))
@ -893,12 +942,12 @@ class ProposicaoForm(forms.ModelForm):
super(ProposicaoForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['tipo_texto'].initial = []
self.fields['tipo_texto'].initial = ''
if self.instance.texto_original:
self.fields['tipo_texto'].initial.append('D')
self.fields['tipo_texto'].initial = 'D'
if self.texto_articulado_proposicao:
if self.instance.texto_articulado.exists():
self.fields['tipo_texto'].initial.append('T')
self.fields['tipo_texto'].initial = 'T'
if self.instance.materia_de_vinculo:
self.fields[
@ -939,21 +988,35 @@ class ProposicaoForm(forms.ModelForm):
return cd
def save(self, commit=True):
cd = self.cleaned_data
inst = self.instance
if inst.pk:
if 'tipo_texto' in cd:
if cd['tipo_texto'] == 'T' and inst.texto_original:
inst.texto_original.delete()
elif cd['tipo_texto'] != 'T':
inst.texto_articulado.all().delete()
if 'texto_original' in cd and\
not cd['texto_original'] and \
inst.texto_original:
inst.texto_original.delete()
if self.instance.pk:
return super().save(commit)
self.instance.ano = datetime.now().year
inst.ano = datetime.now().year
numero__max = Proposicao.objects.filter(
autor=self.instance.autor,
autor=inst.autor,
ano=datetime.now().year).aggregate(Max('numero_proposicao'))
numero__max = numero__max['numero_proposicao__max']
self.instance.numero_proposicao = (
inst.numero_proposicao = (
numero__max + 1) if numero__max else 1
self.instance.save()
inst.save()
return self.instance
return inst
class ConfirmarProposicaoForm(ProposicaoForm):
@ -1103,7 +1166,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
if 'regime_tramitacao' not in cd or\
not cd['regime_tramitacao']:
raise ValidationError(
_('Regimente de Tramitação deve ser informado.'))
_('Regime de Tramitação deve ser informado.'))
elif self.instance.tipo.content_type.model_class(
) == TipoDocumento and not cd['materia_de_vinculo']:
@ -1154,6 +1217,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
self.instance.justificativa_devolucao = ''
self.instance.data_devolucao = None
self.instance.data_recebimento = datetime.now()
self.instance.materia_de_vinculo = cd['materia_de_vinculo']
if self.instance.texto_articulado.exists():
ta = self.instance.texto_articulado.first()
@ -1320,19 +1384,19 @@ class ConfirmarProposicaoForm(ProposicaoForm):
protocolo.timestamp = datetime.now()
protocolo.tipo_protocolo = '1'
# 1 Processo Legislativo
# 0 Processo Administrativo
protocolo.tipo_processo = '1'
protocolo.interessado = str(proposicao.autor)
protocolo.autor = proposicao.autor
protocolo.assunto_ementa = proposicao.descricao
protocolo.numero_paginas = cd['numero_de_paginas']
protocolo.anulado = False
if self.instance.tipo.content_type.model_class(
) == TipoMateriaLegislativa:
protocolo.tipo_materia = proposicao.tipo.tipo_conteudo_related
protocolo.tipo_processo = '1'
elif self.instance.tipo.content_type.model_class() == TipoDocumento:
protocolo.tipo_documento = proposicao.tipo.tipo_conteudo_related
protocolo.tipo_processo = '0'
protocolo.save()
@ -1341,6 +1405,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
# 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(

35
sapl/materia/migrations/0068_auto_20161110_0910.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-10 09:10
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0067_auto_20161025_1630'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='ano',
field=models.PositiveSmallIntegerField(choices=[('', 'Selecione'), (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=[('', 'Selecione'), (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=[('', 'Selecione'), (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=[('', 'Selecione'), (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/0069_tipoproposicao_perfis.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-11 10:05
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0069_auto_20161107_1932'),
('materia', '0068_auto_20161110_0910'),
]
operations = [
migrations.AddField(
model_name='tipoproposicao',
name='perfis',
field=models.ManyToManyField(blank=True, to='compilacao.PerfilEstruturalTextoArticulado', verbose_name='Perfis Estruturais de Textos Articulados'),
),
]

21
sapl/materia/migrations/0070_auto_20161111_1301.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-11 13:01
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0069_tipoproposicao_perfis'),
]
operations = [
migrations.AlterField(
model_name='tipoproposicao',
name='perfis',
field=models.ManyToManyField(blank=True, help_text='\n Mesmo que em Configurações da Aplicação nas\n Tabelas Auxiliares esteja definido que Proposições possam\n utilizar Textos Articulados, ao gerar uma proposição,\n a solução de Textos Articulados será disponibilizada se\n o Tipo escolhido para a Proposição estiver associado a ao\n menos um Perfil Estrutural de Texto Articulado.\n ',
to='compilacao.PerfilEstruturalTextoArticulado', verbose_name='Perfis Estruturais de Textos Articulados'),
),
]

19
sapl/materia/migrations/0071_auto_20161130_1001.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2016-11-30 10:01
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0070_auto_20161111_1301'),
]
operations = [
migrations.AlterModelOptions(
name='proposicao',
options={'permissions': (('detail_proposicao_enviada', 'Pode acessar detalhes de uma proposição enviada.'), ('detail_proposicao_devolvida', 'Pode acessar detalhes de uma proposição devolvida.')), 'verbose_name': 'Proposição', 'verbose_name_plural': 'Proposições'},
),
]

19
sapl/materia/migrations/0072_auto_20161130_1618.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2016-11-30 16:18
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0071_auto_20161130_1001'),
]
operations = [
migrations.AlterModelOptions(
name='proposicao',
options={'permissions': (('detail_proposicao_enviada', 'Pode acessar detalhes de uma proposição enviada.'), ('detail_proposicao_devolvida', 'Pode acessar detalhes de uma proposição devolvida.'), ('detail_proposicao_incorporada', 'Pode acessar detalhes de uma proposição incorporada.')), 'verbose_name': 'Proposição', 'verbose_name_plural': 'Proposições'},
),
]

80
sapl/materia/models.py

@ -1,19 +1,24 @@
from datetime import datetime
from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.deletion import PROTECT
from django.utils import formats
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
from sapl.base.models import Autor
from sapl.comissoes.models import Comissao
from sapl.compilacao.models import TextoArticulado
from sapl.compilacao.models import TextoArticulado,\
PerfilEstruturalTextoArticulado
from sapl.parlamentares.models import Parlamentar
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey,
SaplGenericRelation, restringe_tipos_de_arquivo_txt,
texto_upload_path)
EM_TRAMITACAO = [(1, 'Sim'),
(0, 'Não')]
@ -38,19 +43,17 @@ class TipoProposicao(models.Model):
tipo_conteudo_related = SaplGenericForeignKey(
'content_type', 'object_id', verbose_name=_('Seleção de Tipo'))
"""materia_ou_documento = models.CharField(
max_length=1, verbose_name=_('Gera'), choices=MAT_OU_DOC_CHOICES)
modelo = models.CharField(max_length=50, verbose_name=_('Modelo XML'))
# mutually exclusive (depend on materia_ou_documento)
tipo_materia = models.ForeignKey(
TipoMateriaLegislativa,
blank=True,
null=True,
verbose_name=_('Tipo de Matéria'))
tipo_documento = models.ForeignKey(
TipoDocumento, blank=True, null=True,
verbose_name=_('Tipo de Documento'))"""
perfis = models.ManyToManyField(
PerfilEstruturalTextoArticulado,
blank=True, verbose_name=_('Perfis Estruturais de Textos Articulados'),
help_text=_("""
Mesmo que em Configurações da Aplicação nas
Tabelas Auxiliares esteja definido que Proposições possam
utilizar Textos Articulados, ao gerar uma proposição,
a solução de Textos Articulados será disponibilizada se
o Tipo escolhido para a Proposição estiver associado a ao
menos um Perfil Estrutural de Texto Articulado.
"""))
class Meta:
verbose_name = _('Tipo de Proposição')
@ -80,6 +83,7 @@ class TipoMateriaLegislativa(models.Model):
class Meta:
verbose_name = _('Tipo de Matéria Legislativa')
verbose_name_plural = _('Tipos de Matérias Legislativas')
ordering = ['descricao']
def __str__(self):
return self.descricao
@ -197,6 +201,13 @@ class MateriaLegislativa(models.Model):
return _('%(tipo)s%(numero)s de %(ano)s') % {
'tipo': self.tipo, 'numero': self.numero, 'ano': self.ano}
def delete(self, using=None, keep_parents=False):
if self.texto_original:
self.texto_original.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):
@ -284,6 +295,8 @@ class AssuntoMateria(models.Model):
class DespachoInicial(models.Model):
# TODO M2M?
# TODO Despachos não são necessáriamente comissoes, podem ser outros
# órgãos, ex: procuradorias
materia = models.ForeignKey(MateriaLegislativa)
comissao = models.ForeignKey(Comissao)
@ -343,6 +356,13 @@ class DocumentoAcessorio(models.Model):
'data': self.data,
'autor': self.autor}
def delete(self, using=None, keep_parents=False):
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):
@ -395,10 +415,9 @@ class Numeracao(models.Model):
'data_materia',)
def __str__(self):
return _('%(numero)s %(tipo)s - %(data)s') % {
return _('%(numero)s/%(ano)s') % {
'numero': self.numero_materia,
'tipo': self.tipo_materia,
'data': self.data_materia}
'ano': self.data_materia.year}
class Orgao(models.Model):
@ -566,16 +585,42 @@ class Proposicao(models.Model):
documento_gerado = models.ForeignKey(
DocumentoAcessorio, blank=True, null=True)"""
@property
def perfis(self):
return self.tipo.perfis.all()
@property
def title_type(self):
return '%s nº _____ %s' % (
self.tipo, formats.date_format(
self.data_envio if self.data_envio else datetime.now(),
"\d\e d \d\e F \d\e Y"))
class Meta:
verbose_name = _('Proposição')
verbose_name_plural = _('Proposições')
unique_together = (('content_type', 'object_id'), )
permissions = (
('detail_proposicao_enviada',
_('Pode acessar detalhes de uma proposição enviada.')),
('detail_proposicao_devolvida',
_('Pode acessar detalhes de uma proposição devolvida.')),
('detail_proposicao_incorporada',
_('Pode acessar detalhes de uma proposição incorporada.')),
)
def __str__(self):
return '%s %s/%s' % (Proposicao._meta.verbose_name,
self.numero_proposicao,
self.ano)
def delete(self, using=None, keep_parents=False):
if self.texto_original:
self.texto_original.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):
@ -608,6 +653,7 @@ class StatusTramitacao(models.Model):
class Meta:
verbose_name = _('Status de Tramitação')
verbose_name_plural = _('Status de Tramitação')
ordering = ['descricao']
def __str__(self):
return _('%(descricao)s') % {

2
sapl/materia/tests/test_materia.py

@ -159,6 +159,7 @@ def test_despacho_inicial_submit(admin_client):
comissao = mommy.make(Comissao,
tipo=tipo_comissao,
nome='Teste',
ativa=True,
sigla='T',
data_criacao='2016-03-18')
@ -172,6 +173,7 @@ def test_despacho_inicial_submit(admin_client):
# Verifica se o despacho foi criado
despacho = DespachoInicial.objects.first()
assert despacho.comissao == comissao
assert despacho.materia == materia_principal

94
sapl/materia/views.py

@ -12,19 +12,21 @@ from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.http import JsonResponse
from django.http.response import Http404, HttpResponseRedirect
from django.shortcuts import redirect, get_object_or_404
from django.shortcuts import get_object_or_404, redirect
from django.template import Context, loader
from django.utils import formats
from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, TemplateView, UpdateView
from django.views.generic import (CreateView, DetailView, ListView,
TemplateView, UpdateView)
from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView
from django_filters.views import FilterView
import sapl
from sapl.base.models import Autor, CasaLegislativa
from sapl.compilacao.models import STATUS_TA_PRIVATE, STATUS_TA_EDITION,\
STATUS_TA_IMMUTABLE_RESTRICT
from sapl.compilacao.models import (STATUS_TA_EDITION,
STATUS_TA_IMMUTABLE_RESTRICT,
STATUS_TA_PRIVATE)
from sapl.compilacao.views import IntegracaoTaView
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions
from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL,
@ -39,9 +41,9 @@ from sapl.protocoloadm.models import Protocolo
from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label,
autor_modal, gerar_hash_arquivo, get_base_url,
montar_row_autor)
import sapl
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
DespachoInicialForm,
DocumentoAcessorioForm, MateriaLegislativaFilterSet,
MateriaSimplificadaForm, PrimeiraTramitacaoEmLoteFilterSet,
ReceberProposicaoForm, TramitacaoEmLoteFilterSet,
@ -55,7 +57,6 @@ from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial,
TipoMateriaLegislativa, TipoProposicao, Tramitacao,
UnidadeTramitacao)
OrigemCrud = Crud.build(Origem, '')
TipoMateriaCrud = CrudAux.build(
@ -251,7 +252,7 @@ class ProposicaoDevolvida(PermissionRequiredMixin, ListView):
model = Proposicao
ordering = ['data_envio']
paginate_by = 10
permission_required = ('materia.list_proposicao', )
permission_required = ('materia.detail_proposicao_devolvida', )
def get_queryset(self):
return Proposicao.objects.filter(
@ -275,7 +276,7 @@ class ProposicaoPendente(PermissionRequiredMixin, ListView):
model = Proposicao
ordering = ['data_envio', 'autor', 'tipo', 'descricao']
paginate_by = 10
permission_required = ('materia.list_proposicao', )
permission_required = ('materia.detail_proposicao_enviada', )
def get_queryset(self):
return Proposicao.objects.filter(
@ -300,7 +301,7 @@ class ProposicaoRecebida(PermissionRequiredMixin, ListView):
model = Proposicao
ordering = ['data_envio']
paginate_by = 10
permission_required = ('materia.list_proposicao', )
permission_required = 'materia.detail_proposicao_incorporada'
def get_queryset(self):
return Proposicao.objects.filter(
@ -472,10 +473,16 @@ class ProposicaoCrud(Crud):
class DetailView(Crud.DetailView):
layout_key = 'Proposicao'
permission_required = (RP_DETAIL, 'materia.detail_proposicao_enviada',
'materia.detail_proposicao_devolvida',
'materia.detail_proposicao_incorporada')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['subnav_template_name'] = ''
context['title'] = '%s <small>(%s)</small>' % (
self.object, self.object.autor)
return context
def get(self, request, *args, **kwargs):
@ -535,6 +542,37 @@ class ProposicaoCrud(Crud):
return redirect(reverse('sapl.materia:proposicao_detail',
kwargs={'pk': kwargs['pk']}))
def dispatch(self, request, *args, **kwargs):
try:
p = Proposicao.objects.get(id=kwargs['pk'])
except:
raise Http404()
if not self.has_permission():
return self.handle_no_permission()
if p.autor.user != request.user:
if not p.data_envio and not p.data_devolucao:
raise Http404()
if p.data_devolucao and not request.user.has_perm(
'materia.detail_proposicao_devolvida'):
raise Http404()
if p.data_envio and not p.data_recebimento\
and not request.user.has_perm(
'materia.detail_proposicao_enviada'):
raise Http404()
if p.data_envio and p.data_recebimento\
and not request.user.has_perm(
'materia.detail_proposicao_incorporada'):
raise Http404()
return super(PermissionRequiredMixin, self).dispatch(
request, *args, **kwargs)
class DeleteView(BaseLocalMixin, Crud.DeleteView):
def _action_is_valid(self, request, *args, **kwargs):
@ -624,18 +662,19 @@ class ProposicaoCrud(Crud):
def get_rows(self, object_list):
for obj in object_list:
if obj.data_recebimento is None:
obj.data_recebimento = 'Não recebida'\
if obj.data_envio else 'Não enviada'
else:
obj.data_recebimento = formats.date_format(
obj.data_recebimento, "DATETIME_FORMAT")
if obj.data_envio is None:
obj.data_envio = 'Em elaboração...'
else:
obj.data_envio = formats.date_format(
obj.data_envio, "DATETIME_FORMAT")
if obj.data_recebimento is None:
obj.data_recebimento = 'Não recebida'
else:
obj.data_envio = formats.date_format(
obj.data_recebimento, "DATETIME_FORMAT")
return [self._as_row(obj) for obj in object_list]
@ -843,6 +882,12 @@ class DespachoInicialCrud(MasterDetailCrud):
help_path = ''
public = [RP_LIST, RP_DETAIL]
class CreateView(MasterDetailCrud.CreateView):
form_class = DespachoInicialForm
class UpdateView(MasterDetailCrud.UpdateView):
form_class = DespachoInicialForm
class LegislacaoCitadaCrud(MasterDetailCrud):
model = LegislacaoCitada
@ -987,6 +1032,17 @@ class MateriaLegislativaCrud(Crud):
def cancel_url(self):
return self.search_url
class DeleteView(Crud.DeleteView):
def get_success_url(self):
return self.search_url
class DetailView(Crud.DetailView):
@property
def layout_key(self):
return 'MateriaLegislativaDetail'
class ListView(Crud.ListView, RedirectView):
def get_redirect_url(self, *args, **kwargs):
@ -1100,6 +1156,9 @@ 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', '-numero')
kwargs.update({
'queryset': qs,
})
@ -1460,6 +1519,9 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
context['unidade_local'] = [UnidadeTramitacao.objects.get(
id=qr['tramitacao__unidade_tramitacao_destino'])]
context['object_list'] = context['object_list'].order_by(
'ano', 'numero')
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
return context

160
sapl/norma/forms.py

@ -4,15 +4,18 @@ from crispy_forms.helper import FormHelper
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.forms import ModelForm, widgets
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import RANGE_ANOS
from sapl.utils import RANGE_ANOS, RangeWidgetOverride
from .models import AssuntoNorma, NormaJuridica
from .models import (AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica)
def get_esferas():
@ -30,94 +33,45 @@ ORDENACAO_CHOICES = [('', '---------'),
('data,tipo,ano,numero', _('Data/Tipo/Ano/Número'))]
# TODO termos, pesquisa textual, assunto(M2M)
class NormaJuridicaPesquisaForm(ModelForm):
periodo_inicial = forms.DateField(label=u'Período Inicial',
input_formats=['%d/%m/%Y'],
required=False,
widget=forms.DateInput(
format='%d/%m/%Y',
attrs={'class': 'dateinput'}))
periodo_final = forms.DateField(label=u'Período Final',
input_formats=['%d/%m/%Y'],
required=False,
widget=forms.DateInput(
format='%d/%m/%Y',
attrs={'class': 'dateinput'}))
publicacao_inicial = forms.DateField(label=u'Publicação Inicial',
input_formats=['%d/%m/%Y'],
required=False,
widget=forms.DateInput(
format='%d/%m/%Y',
attrs={'class': 'dateinput'}))
publicacao_final = forms.DateField(label=u'Publicação Final',
input_formats=['%d/%m/%Y'],
required=False,
widget=forms.DateInput(
format='%d/%m/%Y',
attrs={'class': 'dateinput'}))
ano = forms.ModelChoiceField(
label='Ano',
required=False,
queryset=NormaJuridica.objects.order_by('-ano').values_list(
'ano', flat=True).distinct(),
empty_label='Selecione'
)
class NormaFilterSet(django_filters.FilterSet):
em_vigencia = forms.ChoiceField(
label='Em vigência?',
choices=YES_NO_CHOICES,
required=False)
RANGE_ANOS.insert(0, ('', 'Selecione'))
ordenacao = forms.ChoiceField(
label='Ordenação',
choices=ORDENACAO_CHOICES,
required=False)
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
numero = forms.IntegerField(required=False)
ano = django_filters.ChoiceFilter(required=False,
label=u'Ano',
choices=RANGE_ANOS)
assunto = forms.ModelChoiceField(
label='Assunto',
required=False,
queryset=AssuntoNorma.objects.all(),
empty_label='Selecione'
)
ementa = django_filters.CharFilter(lookup_expr='icontains')
assuntos = django_filters.ModelChoiceFilter(
queryset=AssuntoNorma.objects.all())
class Meta:
model = NormaJuridica
fields = ['tipo',
'numero',
'ano',
'periodo_inicial',
'periodo_final',
'publicacao_inicial',
'publicacao_final',
'assunto']
fields = ['tipo', 'numero', 'ano', 'data',
'data_publicacao', 'ementa', 'assuntos']
def __init__(self, *args, **kwargs):
row1 = to_row([('tipo', 12)])
row2 = to_row([('numero', 6), ('ano', 6)])
row3 = to_row([('periodo_inicial', 6), ('periodo_final', 6)])
row4 = to_row([('publicacao_inicial', 6), ('publicacao_final', 6)])
row5 = to_row([('em_vigencia', 4), ('ordenacao', 4), ('assunto', 4)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset('Pesquisa Norma Juridica',
row1, row2, row3, row4, row5),
form_actions(save_label='Pesquisar')
super(NormaFilterSet, self).__init__(*args, **kwargs)
row1 = to_row([('tipo', 4), ('numero', 4), ('ano', 4)])
row2 = to_row([('data', 6), ('data_publicacao', 6)])
row3 = to_row([('ementa', 8), ('assuntos', 4)])
self.form.helper = FormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Norma'),
row1, row2, row3,
form_actions(save_label='Pesquisar'))
)
super(NormaJuridicaPesquisaForm, self).__init__(*args, **kwargs)
class NormaJuridicaForm(ModelForm):
@ -195,3 +149,49 @@ class NormaJuridicaForm(ModelForm):
norma.materia = self.cleaned_data['materia']
norma = super(NormaJuridicaForm, self).save(commit=True)
return norma
class NormaRelacionadaForm(ModelForm):
tipo = forms.ModelChoiceField(
label='Tipo',
required=True,
queryset=TipoNormaJuridica.objects.all(),
empty_label='----------',
)
numero = forms.CharField(label='Número', required=True)
ano = forms.CharField(label='Ano', required=True)
ementa = forms.CharField(
required=False,
widget=forms.Textarea(attrs={'disabled': 'disabled'}))
class Meta:
model = NormaRelacionada
fields = ['tipo', 'numero', 'ano', 'ementa', 'tipo_vinculo']
def __init__(self, *args, **kwargs):
super(NormaRelacionadaForm, self).__init__(*args, **kwargs)
def clean(self):
if self.errors:
return self.errors
cleaned_data = self.cleaned_data
try:
norma_relacionada = NormaJuridica.objects.get(
numero=cleaned_data['numero'],
ano=cleaned_data['ano'],
tipo=cleaned_data['tipo'])
except ObjectDoesNotExist:
msg = _('A norma a ser relacionada não existe.')
raise ValidationError(msg)
else:
cleaned_data['norma_relacionada'] = norma_relacionada
return cleaned_data
def save(self, commit=False):
relacionada = super(NormaRelacionadaForm, self).save(commit)
relacionada.norma_relacionada = self.cleaned_data['norma_relacionada']
relacionada.save()
return relacionada

20
sapl/norma/migrations/0022_auto_20161110_0910.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-10 09:10
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0021_auto_20161028_1335'),
]
operations = [
migrations.AlterField(
model_name='normajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[('', 'Selecione'), (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'),
),
]

41
sapl/norma/migrations/0023_auto_20161123_1359.py

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-23 13:59
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0022_auto_20161110_0910'),
]
operations = [
migrations.AlterModelOptions(
name='vinculonormajuridica',
options={'verbose_name': 'Tipo de Vínculo entre Normas Jurídicas', 'verbose_name_plural': 'Tipos de Vínculos entre Normas Jurídicas'},
),
migrations.RemoveField(
model_name='vinculonormajuridica',
name='norma_referente',
),
migrations.RemoveField(
model_name='vinculonormajuridica',
name='norma_referida',
),
migrations.RemoveField(
model_name='vinculonormajuridica',
name='tipo_vinculo',
),
migrations.AddField(
model_name='vinculonormajuridica',
name='descricao',
field=models.CharField(blank=True, max_length=20, verbose_name='Descrição'),
),
migrations.AddField(
model_name='vinculonormajuridica',
name='sigla',
field=models.CharField(blank=True, max_length=1, verbose_name='Nome'),
),
]

25
sapl/norma/migrations/0024_auto_20161123_1430.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-23 14:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0023_auto_20161123_1359'),
]
operations = [
migrations.AlterField(
model_name='vinculonormajuridica',
name='descricao',
field=models.CharField(blank=True, max_length=50, verbose_name='Descrição'),
),
migrations.AlterField(
model_name='vinculonormajuridica',
name='sigla',
field=models.CharField(blank=True, max_length=1, verbose_name='Sigla'),
),
]

29
sapl/norma/migrations/0025_normarelacionada.py

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-23 14:44
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('norma', '0024_auto_20161123_1430'),
]
operations = [
migrations.CreateModel(
name='NormaRelacionada',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('norma_principal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='norma_principal', to='norma.NormaJuridica')),
('norma_relacionada', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='norma_relacionada', to='norma.NormaJuridica')),
('tipo_vinculo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='norma.VinculoNormaJuridica')),
],
options={
'verbose_name': 'Norma Relacionada',
'verbose_name_plural': 'Normas Relacionadas',
},
),
]

21
sapl/norma/migrations/0026_auto_20161123_1450.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-23 14:50
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('norma', '0025_normarelacionada'),
]
operations = [
migrations.AlterField(
model_name='normarelacionada',
name='tipo_vinculo',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='norma.VinculoNormaJuridica', verbose_name='Tipo de Vínculo'),
),
]

26
sapl/norma/migrations/0027_auto_20161123_1538.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-23 15:38
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('norma', '0026_auto_20161123_1450'),
]
operations = [
migrations.AlterField(
model_name='normarelacionada',
name='norma_principal',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='norma_principal', to='norma.NormaJuridica', verbose_name='Norma Principal'),
),
migrations.AlterField(
model_name='normarelacionada',
name='norma_relacionada',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='norma_relacionada', to='norma.NormaJuridica', verbose_name='Norma Relacionada'),
),
]

21
sapl/norma/migrations/0028_auto_20161202_1025.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-12-02 10:25
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('norma', '0027_auto_20161123_1538'),
]
operations = [
migrations.AlterField(
model_name='normajuridica',
name='materia',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='materia.MateriaLegislativa', verbose_name='Matéria'),
),
]

69
sapl/norma/models.py

@ -50,6 +50,7 @@ class TipoNormaJuridica(models.Model):
class Meta:
verbose_name = _('Tipo de Norma Jurídica')
verbose_name_plural = _('Tipos de Norma Jurídica')
ordering = ['descricao']
def __str__(self):
return self.descricao
@ -68,7 +69,8 @@ class NormaJuridica(models.Model):
verbose_name=_('Texto Integral'))
tipo = models.ForeignKey(
TipoNormaJuridica, verbose_name=_('Tipo da Norma Juridica'))
materia = models.ForeignKey(MateriaLegislativa, blank=True, null=True)
materia = models.ForeignKey(
MateriaLegislativa, blank=True, null=True, verbose_name=_('Matéria'))
numero = models.PositiveIntegerField(verbose_name=_('Número'))
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'),
choices=RANGE_ANOS)
@ -116,6 +118,13 @@ class NormaJuridica(models.Model):
'numero': self.numero,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")}
def delete(self, using=None, keep_parents=False):
if self.texto_integral:
self.texto_integral.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):
@ -171,37 +180,37 @@ class LegislacaoCitada(models.Model):
class VinculoNormaJuridica(models.Model):
TIPO_VINCULO_CHOICES = (
('A', _('Altera a norma')),
('R', _('Revoga integralmente a norma')),
('P', _('Revoga parcialmente a norma')),
('T', _('Revoga integralmente por consolidação')),
('C', _('Norma correlata')),
('S', _('Ressalva a norma')),
('E', _('Reedita a norma')),
('I', _('Reedita a norma com alteração')),
('G', _('Regulamenta a norma')),
('K', _('Suspende parcialmente a norma')),
('L', _('Suspende integralmente a norma')),
('N', _('Julgada integralmente inconstitucional')),
('O', _('Julgada parcialmente inconstitucional')),
)
sigla = models.CharField(
max_length=1, blank=True, verbose_name=_('Sigla'))
descricao = models.CharField(
max_length=50, blank=True, verbose_name=_('Descrição'))
class Meta:
verbose_name = _('Tipo de Vínculo entre Normas Jurídicas')
verbose_name_plural = _('Tipos de Vínculos entre Normas Jurídicas')
def __str__(self):
return self.descricao
# TODO M2M ???
norma_referente = models.ForeignKey(
NormaJuridica, related_name='norma_referente_set')
norma_referida = models.ForeignKey(
NormaJuridica, related_name='norma_referida_set')
tipo_vinculo = models.CharField(
max_length=1, blank=True, choices=TIPO_VINCULO_CHOICES)
class NormaRelacionada(models.Model):
norma_principal = models.ForeignKey(
NormaJuridica,
related_name='norma_principal',
verbose_name=_('Norma Principal'))
norma_relacionada = models.ForeignKey(
NormaJuridica,
related_name='norma_relacionada',
verbose_name=_('Norma Relacionada'))
tipo_vinculo = models.ForeignKey(
VinculoNormaJuridica, verbose_name=_('Tipo de Vínculo'))
class Meta:
verbose_name = _('Vínculo entre Normas Jurídicas')
verbose_name_plural = _('Vínculos entre Normas Jurídicas')
verbose_name = _('Norma Relacionada')
verbose_name_plural = _('Normas Relacionadas')
def __str__(self):
return _('Referente: %(referente)s \n'
'Referida: %(referida)s \nVínculo: %(vinculo)s') % {
'referente': self.norma_referente,
'referida': self.norma_referida,
'vinculo': self.tipo_vinculo}
return _('Principal: %(norma_principal)s'
' - Relacionada: %(norma_relacionada)s') % {
'norma_principal': self.norma_principal,
'norma_relacionada': self.norma_relacionada}

19
sapl/norma/urls.py

@ -1,8 +1,8 @@
from django.conf.urls import include, url
from sapl.norma.views import (AssuntoNormaCrud,
NormaCrud, NormaPesquisaView, NormaTaView,
PesquisaNormaListView, TipoNormaCrud)
from sapl.norma.views import (AssuntoNormaCrud, NormaCrud, NormaPesquisaView,
NormaRelacionadaCrud, NormaTaView, TipoNormaCrud,
VinculoNormaJuridicaCrud, recuperar_norma)
from .apps import AppConfig
@ -10,15 +10,20 @@ app_name = AppConfig.name
urlpatterns = [
url(r'^norma/', include(NormaCrud.get_urls())),
url(r'^norma/', include(NormaCrud.get_urls() +
NormaRelacionadaCrud.get_urls())),
# Integração com Compilação
url(r'^norma/(?P<pk>[0-9]+)/ta$', NormaTaView.as_view(), name='norma_ta'),
url(r'^sistema/norma/tipo/', include(TipoNormaCrud.get_urls())),
url(r'^sistema/norma/assunto/', include(AssuntoNormaCrud.get_urls())),
url(r'^norma/pesquisa$',
url(r'^sistema/norma/vinculo/', include(
VinculoNormaJuridicaCrud.get_urls())),
url(r'^norma/pesquisar$',
NormaPesquisaView.as_view(), name='norma_pesquisa'),
url(r'^norma/pesquisa-resultado$',
PesquisaNormaListView.as_view(), name='list_pesquisa_norma'),
url(r'^norma/recuperar-norma/', recuperar_norma),
]

191
sapl/norma/views.py

@ -1,19 +1,21 @@
from datetime import datetime
from django.core.urlresolvers import reverse
from django.http import JsonResponse
from django.shortcuts import redirect
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView, ListView
from django.views.generic.base import RedirectView
from django_filters.views import FilterView
from sapl.base.models import AppConfig
from sapl.compilacao.views import IntegracaoTaView
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud, make_pagination)
from sapl.norma.forms import NormaJuridicaForm
from .forms import NormaJuridicaPesquisaForm
from .models import (AssuntoNorma, NormaJuridica,
TipoNormaJuridica)
from .forms import NormaFilterSet, NormaJuridicaForm, NormaRelacionadaForm
from .models import (AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, VinculoNormaJuridica)
# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '')
AssuntoNormaCrud = CrudAux.build(AssuntoNorma, 'assunto_norma_juridica',
@ -23,6 +25,63 @@ AssuntoNormaCrud = CrudAux.build(AssuntoNorma, 'assunto_norma_juridica',
TipoNormaCrud = CrudAux.build(
TipoNormaJuridica, 'tipo_norma_juridica',
list_field_names=['sigla', 'descricao', 'equivalente_lexml'])
VinculoNormaJuridicaCrud = CrudAux.build(
VinculoNormaJuridica, '', list_field_names=['sigla', 'descricao'])
class NormaRelacionadaCrud(MasterDetailCrud):
model = NormaRelacionada
parent_field = 'norma_principal'
help_path = ''
public = [RP_LIST, RP_DETAIL]
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['norma_relacionada']
class CreateView(MasterDetailCrud.CreateView):
form_class = NormaRelacionadaForm
class UpdateView(MasterDetailCrud.UpdateView):
form_class = NormaRelacionadaForm
def get_initial(self):
self.initial['tipo'] = self.object.norma_relacionada.tipo.id
self.initial['numero'] = self.object.norma_relacionada.numero
self.initial['ano'] = self.object.norma_relacionada.ano
self.initial['ementa'] = self.object.norma_relacionada.ementa
return self.initial
class DetailView(MasterDetailCrud.DetailView):
@property
def layout_key(self):
return 'NormaRelacionadaDetail'
class NormaPesquisaView(FilterView):
model = NormaJuridica
filterset_class = NormaFilterSet
paginate_by = 10
def get_context_data(self, **kwargs):
context = super(NormaPesquisaView, self).get_context_data(**kwargs)
context['title'] = _('Pesquisar Norma Jurídica')
qr = self.request.GET.copy()
if 'page' in qr:
del qr['page']
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
return context
class NormaTaView(IntegracaoTaView):
@ -69,6 +128,11 @@ class NormaCrud(Crud):
namespace = self.model._meta.app_config.name
return reverse('%s:%s' % (namespace, 'norma_pesquisa'))
class DeleteView(Crud.DeleteView):
def get_success_url(self):
return self.search_url
class CreateView(Crud.CreateView):
form_class = NormaJuridicaForm
@ -105,111 +169,18 @@ class NormaCrud(Crud):
return self.initial.copy()
class NormaPesquisaView(FormView):
template_name = "norma/pesquisa.html"
success_url = "norma:norma_pesquisa"
form_class = NormaJuridicaPesquisaForm
def post(self, request, *args, **kwargs):
form = NormaJuridicaPesquisaForm(request.POST)
if form.data['tipo']:
kwargs['tipo'] = form.data['tipo']
if form.data['numero']:
kwargs['numero'] = form.data['numero']
if form.data['ano']:
kwargs['ano'] = form.data['ano']
if form.data['periodo_inicial'] and form.data['periodo_final']:
kwargs['periodo_inicial'] = form.data['periodo_inicial']
kwargs['periodo_final'] = form.data['periodo_final']
if form.data['publicacao_inicial'] and form.data['publicacao_final']:
kwargs['publicacao_inicial'] = form.data['publicacao_inicial']
kwargs['publicacao_final'] = form.data['publicacao_final']
if form.data['ordenacao']:
kwargs['ordenacao'] = form.data['ordenacao']
if form.data['em_vigencia']:
kwargs['em_vigencia'] = form.data['em_vigencia']
if form.data['assunto']:
kwargs['assunto'] = form.data['assunto']
request.session['kwargs'] = kwargs
return redirect('sapl.norma:list_pesquisa_norma')
class PesquisaNormaListView(ListView):
template_name = 'norma/list_pesquisa.html'
model = NormaJuridica
paginate_by = 10
def get_queryset(self):
kwargs = self.request.session['kwargs']
if 'ordenacao' in kwargs:
ordenacao = kwargs.pop('ordenacao').split(',')
for o in ordenacao:
normas = NormaJuridica.objects.all().order_by(o)
else:
normas = NormaJuridica.objects.all()
if 'em_vigencia' in kwargs:
del kwargs['em_vigencia']
normas = normas.filter(
data_vigencia__lte=datetime.now().date())
if 'periodo_inicial' and 'publicacao_inicial' in kwargs:
periodo_inicial = datetime.strptime(
kwargs['periodo_inicial'],
'%d/%m/%Y').strftime('%Y-%m-%d')
periodo_final = datetime.strptime(
kwargs['periodo_final'],
'%d/%m/%Y').strftime('%Y-%m-%d')
publicacao_inicial = datetime.strptime(
kwargs['publicacao_inicial'],
'%d/%m/%Y').strftime('%Y-%m-%d')
publicacao_final = datetime.strptime(
kwargs['publicacao_final'],
'%d/%m/%Y').strftime('%Y-%m-%d')
normas = normas.filter(
data__range=(periodo_inicial, periodo_final),
data_publicacao__range=(publicacao_inicial, publicacao_final))
if 'periodo_inicial' in kwargs:
inicial = datetime.strptime(kwargs['periodo_inicial'],
'%d/%m/%Y').strftime('%Y-%m-%d')
final = datetime.strptime(kwargs['periodo_inicial'],
'%d/%m/%Y').strftime('%Y-%m-%d')
normas = normas.filter(data__range=(inicial, final))
if 'publicacao_inicial' in kwargs:
inicial = datetime.strptime(kwargs['publicacao_inicial'],
'%d/%m/%Y').strftime('%Y-%m-%d')
final = datetime.strptime(kwargs['publicacao_final'],
'%d/%m/%Y').strftime('%Y-%m-%d')
normas = normas.filter(data_publicacao__range=(inicial, final))
if 'tipo' in kwargs:
normas = normas.filter(tipo=kwargs['tipo'])
if 'numero' in kwargs:
normas = normas.filter(numero=kwargs['numero'])
if 'ano' in kwargs:
normas = normas.filter(ano=kwargs['ano'])
if 'assunto' in kwargs:
normas = normas.filter(assuntos=kwargs['assunto'])
return normas
def recuperar_norma(request):
tipo = TipoNormaJuridica.objects.get(pk=request.GET['tipo'])
numero = request.GET['numero']
ano = request.GET['ano']
def get_context_data(self, **kwargs):
context = super(PesquisaNormaListView, self).get_context_data(
**kwargs)
try:
norma = NormaJuridica.objects.get(tipo=tipo,
ano=ano,
numero=numero)
response = JsonResponse({'ementa': norma.ementa,
'id': norma.id})
except ObjectDoesNotExist:
response = JsonResponse({'ementa': '', 'id': 0})
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
return context
return response

283
sapl/painel/views.py

@ -84,21 +84,8 @@ def get_cronometro_status(request, name):
def get_materia_aberta(pk):
try:
materia = OrdemDia.objects.filter(
sessao_plenaria_id=pk, votacao_aberta=True).last()
return materia
except ObjectDoesNotExist:
return False
def get_last_materia(pk):
try:
materia = OrdemDia.objects.filter(
sessao_plenaria_id=pk).last()
return materia
except ObjectDoesNotExist:
return None
return OrdemDia.objects.filter(
sessao_plenaria_id=pk, votacao_aberta=True).last()
def get_presentes(pk, response, materia):
@ -138,11 +125,14 @@ def get_presentes(pk, response, materia):
num_presentes_ordem_dia = len(presentes_ordem_dia)
if materia.tipo_votacao == 1:
tipo_votacao = 'Simbólica'
tipo_votacao = str(_('Simbólica'))
response = get_votos(response, materia)
elif materia.tipo_votacao == 2:
tipo_votacao = 'Nominal'
response = get_votos_nominal(response, materia)
elif materia.tipo_votacao == 3:
tipo_votacao = 'Secreta'
response = get_votos(response, materia)
response.update({
'presentes_ordem_dia': presentes_ordem_dia,
@ -151,10 +141,6 @@ def get_presentes(pk, response, materia):
'num_presentes_sessao_plenaria': num_presentes_sessao_plen,
'status_painel': 'ABERTO',
'msg_painel': str(_('Votação aberta!')),
'numero_votos_sim': 0,
'numero_votos_nao': 0,
'numero_abstencoes': 0,
'total_votos': 0,
'tipo_resultado': tipo_votacao,
'observacao_materia': materia.observacao,
'materia_legislativa_texto': str(materia.materia)})
@ -166,21 +152,8 @@ def get_presentes(pk, response, materia):
def get_materia_expediente_aberta(pk):
try:
materia = ExpedienteMateria.objects.filter(
sessao_plenaria_id=pk, votacao_aberta=True).last()
return materia
except ObjectDoesNotExist:
return False
def get_last_materia_expediente(pk):
try:
materia = ExpedienteMateria.objects.filter(
sessao_plenaria_id=pk).last()
return materia
except ObjectDoesNotExist:
return None
return ExpedienteMateria.objects.filter(
sessao_plenaria_id=pk, votacao_aberta=True).last()
def get_presentes_expediente(pk, response, materia):
@ -221,10 +194,14 @@ def get_presentes_expediente(pk, response, materia):
if materia.tipo_votacao == 1:
tipo_votacao = 'Simbólica'
response = get_votos(response, materia)
elif materia.tipo_votacao == 2:
tipo_votacao = 'Nominal'
response = get_votos_nominal(response, materia)
elif materia.tipo_votacao == 3:
tipo_votacao = 'Secreta'
response = get_votos(response, materia)
response.update({
'presentes_expediente': presentes_expediente,
'num_presentes_expediente': num_presentes_expediente,
@ -232,10 +209,6 @@ def get_presentes_expediente(pk, response, materia):
'num_presentes_sessao_plenaria': num_presentes_sessao_plen,
'status_painel': str(_('ABERTO')),
'msg_painel': str(_('Votação aberta!')),
'numero_votos_sim': 0,
'numero_votos_nao': 0,
'numero_abstencoes': 0,
'total_votos': 0,
'tipo_resultado': tipo_votacao,
'observacao_materia': materia.observacao,
'materia_legislativa_texto': str(materia.materia)})
@ -245,16 +218,14 @@ def get_presentes_expediente(pk, response, materia):
# ##########################GENERAL FUNCTIONS#############################
def response_null_materia(response):
def response_nenhuma_materia(response):
response.update({
'status_painel': 'FECHADO',
'msg_painel': str(_('Nenhuma matéria disponivel para votação.'))
})
'msg_painel': str(_('Nenhuma matéria disponivel para votação.'))})
return JsonResponse(response)
def get_votos(response, materia):
if materia.tipo_votacao == 1:
tipo_votacao = 'Simbólica'
elif materia.tipo_votacao == 2:
@ -262,19 +233,34 @@ def get_votos(response, materia):
elif materia.tipo_votacao == 3:
tipo_votacao = 'Secreta'
registro = RegistroVotacao.objects.filter(
ordem=materia, materia=materia.materia).last()
total = (registro.numero_votos_sim +
registro.numero_votos_nao +
registro.numero_abstencoes)
response.update({
'numero_votos_sim': registro.numero_votos_sim,
'numero_votos_nao': registro.numero_votos_nao,
'numero_abstencoes': registro.numero_abstencoes,
'total_votos': total,
'tipo_votacao': tipo_votacao,
'tipo_resultado': registro.tipo_resultado_votacao.nome,
})
if type(materia) == OrdemDia:
registro = RegistroVotacao.objects.filter(
ordem=materia, materia=materia.materia).last()
else:
registro = RegistroVotacao.objects.filter(
expediente=materia, materia=materia.materia).last()
if registro:
total = (registro.numero_votos_sim +
registro.numero_votos_nao +
registro.numero_abstencoes)
response.update({
'numero_votos_sim': registro.numero_votos_sim,
'numero_votos_nao': registro.numero_votos_nao,
'numero_abstencoes': registro.numero_abstencoes,
'total_votos': total,
'tipo_votacao': tipo_votacao,
'tipo_resultado': registro.tipo_resultado_votacao.nome,
})
else:
response.update({
'numero_votos_sim': 0,
'numero_votos_nao': 0,
'numero_abstencoes': 0,
'total_votos': 0,
'tipo_votacao': tipo_votacao,
'tipo_resultado': 'Ainda não foi votada.',
})
return response
@ -288,48 +274,65 @@ def get_votos_nominal(response, materia):
elif materia.tipo_votacao == 3:
tipo_votacao = 'Secreta'
registro = RegistroVotacao.objects.get(
ordem=materia, materia=materia.materia)
votos_parlamentares = VotoParlamentar.objects.filter(
votacao_id=registro.id)
filiacao = Filiacao.objects.filter(
data_desfiliacao__isnull=True, parlamentar__ativo=True)
parlamentar_partido = {}
for f in filiacao:
parlamentar_partido[
f.parlamentar.nome_parlamentar] = f.partido.sigla
for v in votos_parlamentares:
try:
parlamentar_partido[v.parlamentar.nome_parlamentar]
except KeyError:
votos.update({v.parlamentar.id: {
'parlamentar': v.parlamentar.nome_parlamentar,
'voto': str(v.voto),
'partido': str(_('Sem Registro'))
}})
else:
votos.update({v.parlamentar.id: {
'parlamentar': v.parlamentar.nome_parlamentar,
'voto': str(v.voto),
'partido': parlamentar_partido[v.parlamentar.nome_parlamentar]
}})
total = (registro.numero_votos_sim +
registro.numero_votos_nao +
registro.numero_abstencoes)
if type(materia) == OrdemDia:
registro = RegistroVotacao.objects.filter(
ordem=materia, materia=materia.materia).last()
else:
registro = RegistroVotacao.objects.filter(
expediente=materia, materia=materia.materia).last()
if not registro:
response.update({
'numero_votos_sim': 0,
'numero_votos_nao': 0,
'numero_abstencoes': 0,
'total_votos': 0,
'tipo_votacao': tipo_votacao,
'tipo_resultado': 'Não foi votado ainda',
'votos': None
})
response.update({
'numero_votos_sim': registro.numero_votos_sim,
'numero_votos_nao': registro.numero_votos_nao,
'numero_abstencoes': registro.numero_abstencoes,
'total_votos': total,
'tipo_votacao': tipo_votacao,
'tipo_resultado': registro.tipo_resultado_votacao.nome,
'votos': votos
})
else:
votos_parlamentares = VotoParlamentar.objects.filter(
votacao_id=registro.id)
filiacao = Filiacao.objects.filter(
data_desfiliacao__isnull=True, parlamentar__ativo=True)
parlamentar_partido = {}
for f in filiacao:
parlamentar_partido[
f.parlamentar.nome_parlamentar] = f.partido.sigla
for v in votos_parlamentares:
try:
parlamentar_partido[v.parlamentar.nome_parlamentar]
except KeyError:
votos.update({v.parlamentar.id: {
'parlamentar': v.parlamentar.nome_parlamentar,
'voto': str(v.voto),
'partido': str(_('Sem Registro'))
}})
else:
votos.update({v.parlamentar.id: {
'parlamentar': v.parlamentar.nome_parlamentar,
'voto': str(v.voto),
'partido': parlamentar_partido[
v.parlamentar.nome_parlamentar]
}})
total = (registro.numero_votos_sim +
registro.numero_votos_nao +
registro.numero_abstencoes)
response.update({
'numero_votos_sim': registro.numero_votos_sim,
'numero_votos_nao': registro.numero_votos_nao,
'numero_abstencoes': registro.numero_abstencoes,
'total_votos': total,
'tipo_votacao': tipo_votacao,
'tipo_resultado': registro.tipo_resultado_votacao.nome,
'votos': votos
})
return response
@ -358,37 +361,57 @@ def get_dados_painel(request, pk):
elif expediente:
return JsonResponse(get_presentes_expediente(pk, response, expediente))
ultima_ordem = get_last_materia(pk)
if ultima_ordem:
if ultima_ordem.resultado:
if ultima_ordem.tipo_votacao in [1, 3]:
return JsonResponse(
get_votos(get_presentes(
pk, response, ultima_ordem), ultima_ordem))
elif ultima_ordem.tipo_votacao == 2:
return JsonResponse(
get_votos_nominal(get_presentes(
pk, response, ultima_ordem), ultima_ordem))
else:
return JsonResponse(get_presentes(pk, response, ultima_ordem))
ultimo_expediente = get_last_materia_expediente(pk)
if ultimo_expediente:
if ultimo_expediente.resultado:
if ultimo_expediente.tipo_votacao in [1, 3]:
return JsonResponse(
get_votos(get_presentes(
pk, response, ultimo_expediente),
ultimo_expediente))
elif ultimo_expediente.tipo_votacao == 2:
return JsonResponse(
get_votos_nominal(get_presentes(
pk, response, ultimo_expediente),
ultimo_expediente))
else:
# Ultimo voto em ordem e ultimo voto em expediente
last_ordem_voto = RegistroVotacao.objects.filter(
ordem__sessao_plenaria=sessao).last()
last_expediente_voto = RegistroVotacao.objects.filter(
expediente__sessao_plenaria=sessao).last()
# Ultimas materias votadas
if last_ordem_voto:
ultima_ordem_votada = last_ordem_voto.ordem
if last_expediente_voto:
ultimo_expediente_votado = last_expediente_voto.expediente
# Caso não tenha nenhuma votação aberta
if last_ordem_voto or last_expediente_voto:
# Se alguma ordem E algum expediente já tiver sido votado...
if last_ordem_voto and last_expediente_voto:
# Verifica se o último resultado é um uma ordem do dia
if last_ordem_voto.pk >= last_expediente_voto.pk:
if ultima_ordem_votada.tipo_votacao in [1, 3]:
return JsonResponse(
get_votos(get_presentes(
pk, response, ultima_ordem_votada),
ultima_ordem_votada))
elif ultima_ordem_votada.tipo_votacao == 2:
return JsonResponse(
get_votos_nominal(get_presentes(
pk, response, ultima_ordem_votada),
ultima_ordem_votada))
# Caso não seja, verifica se é um expediente
else:
if ultimo_expediente_votado.tipo_votacao in [1, 3]:
return JsonResponse(
get_votos(get_presentes(
pk, response, ultimo_expediente_votado),
ultimo_expediente_votado))
elif ultimo_expediente_votado.tipo_votacao == 2:
return JsonResponse(
get_votos_nominal(get_presentes(
pk, response,
ultimo_expediente_votado),
ultimo_expediente_votado))
# Caso somente um deles tenha resultado, prioriza a Ordem do Dia
if last_ordem_voto:
return JsonResponse(get_presentes(
pk, response, ultima_ordem_votada))
# Caso a Ordem do dia não tenha resultado, mostra o último expediente
if last_expediente_voto:
return JsonResponse(get_presentes(pk, response,
ultimo_expediente))
else:
return response_null_materia(response)
ultimo_expediente_votado))
# Retorna que não há nenhuma matéria já votada ou aberta
return response_nenhuma_materia(response)

2
sapl/parlamentares/models.py

@ -294,7 +294,7 @@ class Parlamentar(models.Model):
@property
def avatar_html(self):
return '<img class="avatar-parlamentar" src='\
+ self.fotografia.url + '/>'if self.fotografia else ''
+ self.fotografia.url + '>'if self.fotografia else ''
class TipoDependente(models.Model):

11
sapl/parlamentares/views.py

@ -416,12 +416,13 @@ class MesaDiretoraView(FormView):
[p.parlamentar for p in parlamentares]) - set(
parlamentares_ocupados))
sessao_selecionada = SessaoLegislativa.objects.get(
sessao_sel = SessaoLegislativa.objects.get(
id=int(request.POST['sessao']))
if str(sessao_selecionada.legislatura_id) != int(
request.POST['legislatura']):
sessao_selecionada = SessaoLegislativa.objects.filter(
if str(sessao_sel.legislatura_id) != request.POST['legislatura']:
sessao_sel = SessaoLegislativa.objects.filter(
legislatura=Legislatura.objects.first()).first()
return self.render_to_response(
{'legislaturas': Legislatura.objects.all(
).order_by('-numero'),
@ -429,7 +430,7 @@ class MesaDiretoraView(FormView):
id=int(request.POST['legislatura'])),
'sessoes': SessaoLegislativa.objects.filter(
legislatura_id=int(request.POST['legislatura'])),
'sessao_selecionada': sessao_selecionada,
'sessao_selecionada': sessao_sel,
'composicao_mesa': mesa,
'parlamentares': parlamentares_vagos,
'cargos_vagos': cargos_vagos

32
sapl/protocoloadm/forms.py

@ -12,7 +12,7 @@ from django.utils.translation import ugettext_lazy as _
from sapl.base.models import Autor
from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.materia.models import UnidadeTramitacao
from sapl.materia.models import TipoMateriaLegislativa, UnidadeTramitacao
from sapl.utils import (RANGE_ANOS, AnoNumeroOrderingFilter,
RangeWidgetOverride, autor_label, autor_modal)
@ -275,7 +275,7 @@ class ProtocoloDocumentForm(ModelForm):
tipo_documento = forms.ModelChoiceField(
label=_('Tipo de Documento'),
required=False,
required=True,
queryset=TipoDocumentoAdministrativo.objects.all(),
empty_label='Selecione',
)
@ -333,6 +333,21 @@ class ProtocoloDocumentForm(ModelForm):
class ProtocoloMateriaForm(ModelForm):
autor = forms.IntegerField(widget=forms.HiddenInput(), required=False)
tipo_materia = forms.ModelChoiceField(
label=_('Tipo de Matéria'),
required=True,
queryset=TipoMateriaLegislativa.objects.all(),
empty_label='Selecione',
)
numero_paginas = forms.CharField(label=_('Núm. Páginas'), required=True)
observacao = forms.CharField(required=False,
widget=forms.Textarea, label='Observação')
assunto_ementa = forms.CharField(required=True,
widget=forms.Textarea, label='Ementa')
def clean_autor(self):
autor_field = self.cleaned_data['autor']
try:
@ -348,6 +363,7 @@ class ProtocoloMateriaForm(ModelForm):
fields = ['tipo_materia',
'numero_paginas',
'autor',
'assunto_ementa',
'observacao']
def __init__(self, *args, **kwargs):
@ -364,19 +380,15 @@ class ProtocoloMateriaForm(ModelForm):
'limpar Autor',
css_class='btn btn-primary btn-sm'), 10)])
row3 = to_row(
[('assunto_ementa', 12)])
row4 = to_row(
[('observacao', 12)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação da Matéria'),
row1,
HTML(autor_label),
HTML(autor_modal),
row2,
row3,
form_actions(save_label='Protocolar Matéria')
)
)
row1, HTML(autor_label), HTML(autor_modal), row2, row3,
row4, form_actions(save_label='Protocolar Matéria')))
super(ProtocoloMateriaForm, self).__init__(
*args, **kwargs)

25
sapl/protocoloadm/migrations/0007_auto_20161110_0910.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-10 09:10
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0006_auto_20161103_1721'),
]
operations = [
migrations.AlterField(
model_name='documentoadministrativo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[('', 'Selecione'), (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=[('', 'Selecione'), (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'),
),
]

49
sapl/protocoloadm/models.py

@ -14,6 +14,7 @@ class TipoDocumentoAdministrativo(models.Model):
class Meta:
verbose_name = _('Tipo de Documento Administrativo')
verbose_name_plural = _('Tipos de Documento Administrativo')
ordering = ['descricao']
def __str__(self):
return self.descricao
@ -81,6 +82,30 @@ class DocumentoAdministrativo(models.Model):
'tipo': self.tipo, 'assunto': self.assunto
}
def delete(self, using=None, keep_parents=False):
if self.texto_integral:
self.texto_integral.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):
if not self.pk and self.texto_integral:
texto_integral = self.texto_integral
self.texto_integral = None
models.Model.save(self, force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
self.texto_integral = texto_integral
return models.Model.save(self, force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
class DocumentoAcessorioAdministrativo(models.Model):
documento = models.ForeignKey(DocumentoAdministrativo)
@ -106,6 +131,30 @@ class DocumentoAcessorioAdministrativo(models.Model):
def __str__(self):
return self.nome
def delete(self, using=None, keep_parents=False):
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):
if not self.pk and self.arquivo:
arquivo = self.arquivo
self.arquivo = None
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,
update_fields=update_fields)
class Protocolo(models.Model):
numero = models.PositiveIntegerField(

20
sapl/protocoloadm/views.py

@ -92,6 +92,9 @@ class ProtocoloPesquisaView(PermissionRequiredMixin, FilterView):
qs = self.get_queryset().order_by('ano', 'numero')
qs = qs.distinct()
if 'o' in self.request.GET and not self.request.GET['o']:
qs = qs.order_by('-ano', '-numero')
kwargs.update({
'queryset': qs,
@ -220,12 +223,9 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
elif numeracao == 'U':
numero = Protocolo.objects.all().aggregate(Max('numero'))
if numero['numero__max'] is None:
numero['numero__max'] = 0
f.tipo_processo = '0' # TODO validar o significado
f.anulado = False
f.numero = numero['numero__max'] + 1
f.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1
f.ano = datetime.now().year
f.data = datetime.now().strftime('%Y-%m-%d')
f.hora = datetime.now().strftime('%H:%M')
@ -356,19 +356,22 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
protocolo = Protocolo()
protocolo.numero = numero['numero__max'] + 1
protocolo.numero = (
numero['numero__max'] + 1) if numero['numero__max'] else 1
protocolo.ano = datetime.now().year
protocolo.data = datetime.now().strftime("%Y-%m-%d")
protocolo.hora = datetime.now().strftime("%H:%M")
protocolo.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
protocolo.tipo_processo = '0' # TODO validar o significado
protocolo.tipo_processo = '1' # TODO validar o significado
protocolo.anulado = False
if form.cleaned_data['autor']:
protocolo.autor = form.cleaned_data['autor']
protocolo.anulado = False
protocolo.tipo_materia = TipoMateriaLegislativa.objects.get(
id=self.request.POST['tipo_materia'])
protocolo.numero_paginas = self.request.POST['numero_paginas']
protocolo.observacao = self.request.POST['observacao']
protocolo.save()
return redirect(self.get_success_url(protocolo))
@ -403,6 +406,9 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
qs = self.get_queryset()
qs = qs.distinct()
if 'o' in self.request.GET and not self.request.GET['o']:
qs = qs.order_by('-ano', '-numero')
kwargs.update({
'queryset': qs,

2
sapl/relatorios/views.py

@ -920,7 +920,7 @@ def get_etiqueta_protocolos(prots):
dic['titulo'] = str(p.numero) + '/' + str(p.ano)
dic['data'] = '<b>Data: </b>' + p.data.strftime(
"%d/%m/%Y") + ' - <b>Horário: </b>' + p.hora.strftime("%H:%m")
"%d/%m/%Y") + ' - <b>Horário: </b>' + p.hora.strftime("%H:%M")
dic['txt_assunto'] = p.assunto_ementa
dic['txt_interessado'] = p.interessado

15
sapl/rules/__init__.py

@ -2,6 +2,21 @@ from django.utils.translation import ugettext_lazy as _
default_app_config = 'sapl.rules.apps.AppConfig'
"""
Os cinco radicais de permissão completa são:
RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
'.list_', '.detail_', '.add_', '.change_', '.delete_',
Tanto a app crud quanto a app rules estão sempre ligadas a um model. Ao lidar
com permissões, sempre é analisado se é apenas um radical ou permissão
completa, sendo apenas um radical, a permissão completa é montada com base
no model associado.
"""
RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
'.list_', '.detail_', '.add_', '.change_', '.delete_',
SAPL_GROUP_ADMINISTRATIVO = _("Operador Administrativo")
SAPL_GROUP_PROTOCOLO = _("Operador de Protocolo Administrativo")

9
sapl/rules/apps.py

@ -1,6 +1,5 @@
from builtins import LookupError
import django
from django.apps import apps
from django.conf import settings
from django.contrib.auth import get_user_model
@ -8,8 +7,9 @@ from django.contrib.auth.management import _get_all_permissions
from django.core import exceptions
from django.db import models, router
from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import django
from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_COMISSOES,
SAPL_GROUP_GERAL, SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,
@ -205,8 +205,9 @@ def update_groups(app_config, verbosity=2, interactive=True,
def cria_usuario(self, nome, grupo):
nome_usuario = nome
param_username = {get_user_model().USERNAME_FIELD: nome_usuario}
usuario = get_user_model().objects.get_or_create(
username=nome_usuario)[0]
**param_username)[0]
usuario.set_password('interlegis')
usuario.save()
grupo.user_set.add(usuario)
@ -214,7 +215,7 @@ def update_groups(app_config, verbosity=2, interactive=True,
def update_groups(self):
print('')
print(string_concat('\033[93m\033[1m',
_('Atualizando grupos:'),
_('Atualizando grupos do SAPL:'),
'\033[0m'))
for rules_group in self.rules_patterns:
group_name = rules_group['group']

26
sapl/rules/map_rules.py

@ -12,7 +12,9 @@ from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_ANONYMOUS,
SAPL_GROUP_GERAL, SAPL_GROUP_LOGIN_SOCIAL,
SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,
SAPL_GROUP_PAINEL, SAPL_GROUP_PARLAMENTAR,
SAPL_GROUP_PROTOCOLO, SAPL_GROUP_SESSAO)
SAPL_GROUP_PROTOCOLO, SAPL_GROUP_SESSAO,
RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE)
from sapl.sessao import models as sessao
"""
@ -42,20 +44,8 @@ arquivo (sapl.rules.map_rules.py) e criar os grupos definidos na regra de
negócio trabalham com os cinco radiais de permissão
e com qualquer outro tipo de permissão customizada, nesta ordem de precedência.
Os cinco radicais de permissão completa são:
RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
'.list_', '.detail_', '.add_', '.change_', '.delete_',
Tanto a app crud quanto a app rules estão sempre ligadas a um model. Ao lidar
com permissões, sempre é analisado se é apenas um radical ou permissão
completa, sendo apenas um radical, a permissão completa é montada com base
no model associado.
"""
RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
'.list_', '.detail_', '.add_', '.change_', '.delete_',
__base__ = [RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE]
__listdetailchange__ = [RP_LIST, RP_DETAIL, RP_CHANGE]
@ -83,7 +73,9 @@ rules_group_protocolo = {
(materia.Anexada, __base__),
(materia.Autoria, __base__),
(materia.Proposicao, __listdetailchange__),
(materia.Proposicao, ['detail_proposicao_enviada',
'detail_proposicao_devolvida',
'detail_proposicao_incorporada']),
(compilacao.TextoArticulado, ['view_restricted_textoarticulado'])
]
}
@ -128,7 +120,7 @@ rules_group_norma = {
'group': SAPL_GROUP_NORMA,
'rules': [
(norma.NormaJuridica, __base__),
(norma.VinculoNormaJuridica, __base__),
(norma.NormaRelacionada, __base__),
# Publicacao está com permissão apenas para norma e não para matéria
# e proposições apenas por análise do contexto, não é uma limitação
@ -221,6 +213,7 @@ rules_group_geral = {
(norma.AssuntoNorma, __base__),
(norma.TipoNormaJuridica, __base__),
(norma.VinculoNormaJuridica, __base__),
(parlamentares.Legislatura, __base__),
(parlamentares.SessaoLegislativa, __base__),
@ -259,7 +252,8 @@ rules_group_geral = {
# este model é um espelho do model integrado e sua edição pode
# confundir Autores, operadores de matéria e/ou norma.
# Por isso está adicionado apenas para o operador geral
(compilacao.TextoArticulado, __base__ + ['lock_unlock_textoarticulado']),
(compilacao.TextoArticulado,
__base__ + ['lock_unlock_textoarticulado']),
# estes tres models são complexos e a principio apenas o admin tem perm
(compilacao.TipoDispositivo, []),

37
sapl/sessao/forms.py

@ -79,11 +79,26 @@ class ExpedienteMateriaForm(ModelForm):
fields = ['data_ordem', 'numero_ordem', 'tipo_materia', 'observacao',
'numero_materia', 'ano_materia', 'tipo_votacao']
def clean_numero_ordem(self):
sessao = self.instance.sessao_plenaria
ex = ExpedienteMateria.objects.filter(
sessao_plenaria=sessao,
numero_ordem=self.cleaned_data['numero_ordem']).count()
if ex >= 1:
msg = _('Esse número de ordem já existe.')
raise ValidationError(msg)
return self.cleaned_data['numero_ordem']
def clean_data_ordem(self):
return datetime.now()
return self.instance.sessao_plenaria.data_inicio
def clean(self):
cleaned_data = self.cleaned_data
sessao = self.instance.sessao_plenaria
try:
materia = MateriaLegislativa.objects.get(
numero=self.cleaned_data['numero_materia'],
@ -96,6 +111,14 @@ class ExpedienteMateriaForm(ModelForm):
else:
cleaned_data['materia'] = materia
ex = ExpedienteMateria.objects.filter(
sessao_plenaria=sessao,
materia=materia).count()
if ex >= 1:
msg = _('Essa matéria já foi cadastrada.')
raise ValidationError(msg)
return cleaned_data
def save(self, commit=False):
@ -113,10 +136,12 @@ class OrdemDiaForm(ExpedienteMateriaForm):
'numero_materia', 'ano_materia', 'tipo_votacao']
def clean_data_ordem(self):
return datetime.now()
return self.instance.sessao_plenaria.data_inicio
def clean(self):
cleaned_data = self.cleaned_data
sessao = self.instance.sessao_plenaria
try:
materia = MateriaLegislativa.objects.get(
numero=self.cleaned_data['numero_materia'],
@ -129,6 +154,14 @@ class OrdemDiaForm(ExpedienteMateriaForm):
else:
cleaned_data['materia'] = materia
ex = ExpedienteMateria.objects.filter(
sessao_plenaria=sessao,
materia=materia).count()
if ex >= 1:
msg = _('Essa matéria já foi cadastrada.')
raise ValidationError(msg)
return cleaned_data
def save(self, commit=False):

41
sapl/sessao/models.py

@ -7,7 +7,7 @@ from sapl.materia.models import MateriaLegislativa
from sapl.parlamentares.models import (CargoMesa, Legislatura, Parlamentar,
Partido, SessaoLegislativa)
from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation,
restringe_tipos_de_arquivo_txt)
restringe_tipos_de_arquivo_txt, texto_upload_path)
class CargoBancada(models.Model):
@ -31,7 +31,7 @@ class Bancada(models.Model):
legislatura = models.ForeignKey(Legislatura, verbose_name=_('Legislatura'))
nome = models.CharField(
max_length=80,
verbose_name=_('Nome da Bancada, Bloco, ou Frente'))
verbose_name=_('Nome da Bancada'))
partido = models.ForeignKey(Partido, blank=True, null=True,
verbose_name=_('Partido'))
data_criacao = models.DateField(blank=True, null=True,
@ -77,11 +77,13 @@ def get_sessao_media_path(instance, subpath, filename):
def pauta_upload_path(instance, filename):
return get_sessao_media_path(instance, 'pauta', filename)
return texto_upload_path(instance, filename, subpath='pauta')
# return get_sessao_media_path(instance, 'pauta', filename)
def ata_upload_path(instance, filename):
return get_sessao_media_path(instance, 'ata', filename)
return texto_upload_path(instance, filename, subpath='ata')
# return get_sessao_media_path(instance, 'ata', filename)
class SessaoPlenaria(models.Model):
@ -144,6 +146,37 @@ class SessaoPlenaria(models.Model):
# XXX check if it shouldn't be legislatura.numero
'legislatura_id': self.legislatura.numero}
def delete(self, using=None, keep_parents=False):
if self.upload_pauta:
self.upload_pauta.delete()
if self.upload_ata:
self.upload_ata.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):
if not self.pk and (self.upload_pauta or self.upload_ata):
upload_pauta = self.upload_pauta
upload_ata = self.upload_ata
self.upload_pauta = None
self.upload_ata = None
models.Model.save(self, force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
self.upload_pauta = upload_pauta
self.upload_ata = upload_ata
return models.Model.save(self, force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
class AbstractOrdemDia(models.Model):
TIPO_VOTACAO_CHOICES = Choices(

50
sapl/sessao/views.py

@ -3,7 +3,7 @@ from re import sub
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.core.urlresolvers import reverse
from django.forms.utils import ErrorList
from django.http import JsonResponse
@ -80,10 +80,14 @@ def reordernar_materias_ordem(request, pk):
@permission_required('sessao.change_expedientemateria')
def abrir_votacao_expediente_view(request, pk, spk):
existe_votacao_aberta = ExpedienteMateria.objects.filter(
existe_expediente_aberto = ExpedienteMateria.objects.filter(
sessao_plenaria_id=spk, votacao_aberta=True
).exists()
if existe_votacao_aberta:
existe_ordem_aberta = OrdemDia.objects.filter(
sessao_plenaria_id=spk, votacao_aberta=True
).exists()
if existe_expediente_aberto or existe_ordem_aberta:
msg = _('Já existe uma matéria com votação aberta. Para abrir '
'outra, termine ou feche a votação existente.')
messages.add_message(request, messages.INFO, msg)
@ -97,10 +101,14 @@ def abrir_votacao_expediente_view(request, pk, spk):
@permission_required('sessao.change_ordemdia')
def abrir_votacao_ordem_view(request, pk, spk):
existe_votacao_aberta = OrdemDia.objects.filter(
existe_ordem_aberta = OrdemDia.objects.filter(
sessao_plenaria_id=spk, votacao_aberta=True
).exists()
if existe_votacao_aberta:
existe_expediente_aberto = ExpedienteMateria.objects.filter(
sessao_plenaria_id=spk, votacao_aberta=True
).exists()
if existe_ordem_aberta or existe_expediente_aberto:
msg = _('Já existe uma matéria com votação aberta. Para abrir '
'outra, termine ou feche a votação existente.')
messages.add_message(request, messages.INFO, msg)
@ -125,6 +133,11 @@ class MateriaOrdemDiaCrud(MasterDetailCrud):
class CreateView(MasterDetailCrud.CreateView):
form_class = OrdemDiaForm
def get_initial(self):
self.initial['data_ordem'] = SessaoPlenaria.objects.get(
pk=self.kwargs['pk']).data_inicio.strftime('%d/%m/%Y')
return self.initial
def get_success_url(self):
return reverse('sapl.sessao:ordemdia_list',
kwargs={'pk': self.kwargs['pk']})
@ -323,6 +336,11 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
class CreateView(MasterDetailCrud.CreateView):
form_class = ExpedienteMateriaForm
def get_initial(self):
self.initial['data_ordem'] = SessaoPlenaria.objects.get(
pk=self.kwargs['pk']).data_inicio.strftime('%d/%m/%Y')
return self.initial
def get_success_url(self):
return reverse('sapl.sessao:expedientemateria_list',
kwargs={'pk': self.kwargs['pk']})
@ -1854,9 +1872,14 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin):
'ementa': expediente.observacao}
context.update({'materia': materia})
votacao = RegistroVotacao.objects.get(
materia_id=materia_id,
expediente_id=expediente_id)
try:
votacao = RegistroVotacao.objects.get(
materia_id=materia_id,
expediente_id=expediente_id)
except MultipleObjectsReturned:
votacao = RegistroVotacao.objects.filter(
materia_id=materia_id,
expediente_id=expediente_id).last()
votacao_existente = {'observacao': sub(
'&nbsp;', ' ', strip_tags(votacao.observacao)),
'tipo_resultado':
@ -1875,9 +1898,14 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin):
expediente_id = kwargs['mid']
if(int(request.POST['anular_votacao']) == 1):
RegistroVotacao.objects.get(
materia_id=materia_id,
expediente_id=expediente_id).delete()
try:
RegistroVotacao.objects.get(
materia_id=materia_id,
expediente_id=expediente_id).delete()
except MultipleObjectsReturned:
RegistroVotacao.objects.filter(
materia_id=materia_id,
expediente_id=expediente_id).last().delete()
expediente = ExpedienteMateria.objects.get(
sessao_plenaria_id=self.object.id,

4
sapl/settings.py

@ -32,6 +32,8 @@ SECRET_KEY = config('SECRET_KEY', default='')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=False, cast=bool)
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
ALLOWED_HOSTS = ['*']
LOGIN_REDIRECT_URL = '/'
@ -165,6 +167,8 @@ EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
EMAIL_USE_TLS = config('EMAIL_USE_TLS', cast=bool, default=True)
EMAIL_SEND_USER = config('EMAIL_SEND_USER', cast=str, default='')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='')
SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='')
MAX_DOC_UPLOAD_SIZE = 5 * 1024 * 1024 # 5MB
MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB

6
sapl/static/js/app.js

@ -13,9 +13,9 @@ function initTinymce(elements, readonly=false) {
}
if (readonly) {
config_tinymce.readonly = 1,
config_tinymce.menubar = false,
config_tinymce.toolbar = false
config_tinymce.readonly = 1;
config_tinymce.menubar = false;
config_tinymce.toolbar = false;
}
if (elements != null) {

4
sapl/static/js/compilacao.js

@ -22,12 +22,8 @@ function insertWaitAjax(element) {
jQuery(element).append('<div style="text-align:center;"><i style="font-size: 200%;"class="fa fa-refresh fa-spin"></i></div>');
}
function DispostivoSearch(opts) {
$(function() {
var container_ds = $('body').children("#container_ds");
if (container_ds.length > 0)
$(container_ds).remove();

8
sapl/static/js/compilacao_view.js

@ -142,6 +142,7 @@ function textoVigente(item, link) {
}
$(document).ready(function() {
setTimeout(function() {
var href = location.href.split('#')
if (href.length == 2) {
@ -155,7 +156,6 @@ $(document).ready(function() {
}
}, 100);
$("#btn_font_menos").click(function() {
$(".dpt").css("font-size", "-=1");
});
@ -169,4 +169,10 @@ $(document).ready(function() {
$(this).css('z-index', 15-nivel)
});
/*$(".indent").each(function() {
$(this).removeClass('indent');
$(this.parentElement).addClass('indent');
});*/
});

16
sapl/static/styles/compilacao.scss

@ -165,7 +165,6 @@ a:link:after, a:visited:after {
.cp {
.desativado {
.dtxt, .dtxt *, .dpt-link, .dpt-link * {
text-decoration: line-through;
color: #999 !important;
@ -180,6 +179,7 @@ a:link:after, a:visited:after {
text-decoration: none;
cursor: pointer;
}
.diff {
.desativado, .desativado * {
text-decoration: line-through;
@ -194,7 +194,9 @@ a:link:after, a:visited:after {
.dpt {
font-size:1em;
position: relative;
&.indent {
padding-left: 1em;
}
.ementa {
padding: 2em 0em 2em 35%;
font-weight: bold;
@ -259,26 +261,22 @@ a:link:after, a:visited:after {
}
.paragrafo {
padding-left: 1.5em;
font-size: 1.1em;
margin-top: 0.2222em;
}
.inciso {
font-size: 1.1em;
padding-left: 2.5em;
margin-top: 0.1667em;
}
.alinea {
font-size: 1.0em;
padding-left: 3.5em;
margin-top: 2px;
}
.item {
font-size: 1.0em;
padding-left: 4.5em;
margin-top: 2px;
}
@ -294,6 +292,7 @@ a:link:after, a:visited:after {
a, table, table td {
color: #018 !important;
}
}
.dn { /* Notas de Dispositivo*/
@ -376,7 +375,6 @@ a:link:after, a:visited:after {
&:hover {
display: block;
* {
display: block;
}
@ -524,6 +522,7 @@ a:link:after, a:visited:after {
color: #02baf2 !important;
}
}
.dpt {
display: block;
& > .dpt-actions-fixed {
@ -592,6 +591,7 @@ a:link:after, a:visited:after {
}
}
} /* fim .dpt */
.dpt-alts {
margin: 0;
margin-bottom: 1em;
@ -768,7 +768,7 @@ a:link:after, a:visited:after {
}
}
.cp.cpe1 {
.cp.cpe1_old_apagar {
margin-bottom: 15em;

6
sapl/templates/base.html

@ -94,7 +94,9 @@
</span>
</a>
</div>
{% block sections_nav %} {% subnav %} {% endblock sections_nav %}
<div class="hidden-print">
{% block sections_nav %} {% subnav %} {% endblock sections_nav %}
</div>
</div>
</header>
{% endblock main_header %}
@ -148,7 +150,7 @@
{% block footer_container %}
<footer id="footer" class="footer page__row">
<footer id="footer" class="footer page__row hidden-print">
<div class="container">
<div class="row">

2
sapl/templates/base/layouts.yaml

@ -27,7 +27,7 @@ TipoAutor:
Autor:
{% trans 'Autor' %}:
- tipo:3 nome
- user:6 cargo
- cargo
AutorCreate:
{% trans 'Cadastro de Usuários Autores' %}:

13
sapl/templates/base/login.html

@ -18,7 +18,7 @@
<div class="col-md-4 col-md-offset-4">
<div class="login-panel panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Entrar</h3>
<h3 class="panel-title"><center>Entrar</center></h3>
</div>
<div class="panel-body">
<form id="login-form" method="post" action="{% url 'sapl.base:login' %}">
@ -32,18 +32,19 @@
{% endif %}
<tr>
<td><b>Usuário</b></td>
<td>{{ form.username }}</td>
<p><b><center>Usuário</center></b></p>
{{ form.username }}
</tr>
<tr>
<td><b>Senha</b></td>
<td>{{ form.password }}</td>
<p><b><center>Senha</center></b></p>
{{ form.password }}
</tr>
</table>
</p>
<h5><a href="{% url 'sapl.base:recuperar_senha_email' %}"><center>Esqueceu sua senha?</center></a></h6>
<p class="bs-component">
<center>
<input class="btn btn-success btn-sm" type="submit" value="login" />
<input class="btn btn-lg btn-success btn-block" type="submit" value="login" />
</center>
</p>
<input type="hidden" name="next" value="{{ next }}" />

1
sapl/templates/base/nova_senha_form.html

@ -0,0 +1 @@
{% extends "crud/form.html" %}

14
sapl/templates/base/recupera_senha_email_enviado.html

@ -0,0 +1,14 @@
{% extends "crud/detail.html" %}
{% load i18n crispy_forms_tags %}
{% block base_content %}
<div class="container">
{% csrf_token %}
<div class="row">
<p>Foi enviado um e-mail para o endereço inserido. Verifique-o e siga as instruções.</p>
<p>Caso não receba, procure-o na caixa de spam.</p>
</div>
</div>
{%endblock%}

5
sapl/templates/base/recuperar_senha_completo.html

@ -0,0 +1,5 @@
{% extends "crud/detail.html" %}
{% block table_content %}
A sua senha foi alterada com sucesso! Clique <a href="{% url 'sapl.base:login' %}">aqui</a> e faça seu login.
{% endblock %}

13
sapl/templates/base/recuperar_senha_email.html

@ -0,0 +1,13 @@
<p>Você está recebendo este e-mail pois solicitou recuperação de senha de sua conta no site <b>SAPL</b>.</p></br></p>
<p>Por favor clique no link a seguir e redefina sua senha:</p></br>
<p><a href="{{ protocol }}://{{ domain }}{% url 'sapl.base:recuperar_senha_confirma' uidb64=uid token=token %}">
Clique aqui</a></p></br>
<p>Caso não tenha solicitado a alteração de sua senha, favor ignorar este e-mail.</p></br>
<p>Atenciosamente,</p>
<p>A equipe <b>SAPL</b></p>

1
sapl/templates/base/recuperar_senha_email_form.html

@ -0,0 +1 @@
{% extends "crud/form.html" %}

2
sapl/templates/compilacao/ajax_actions_dinamic_edit.html

@ -59,7 +59,7 @@
<div class="btn-group " role="group">
{% if not object.dispositivo_subsequente %}
{% for perfil in perfil_estrutural_list%}
<button type="button" class="btn-action btn-perfis btn btn-xs {%if request.session.perfil_estrutural == perfil.pk%}btn-primary{%else%}btn-default{%endif%}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" pk="{{object.pk}}" perfil_pk="{{perfil.pk}}" action="json_get_perfis" title="{{perfil.nome}}">
<button type="button" class="btn-action btn-perfis btn btn-xs {%if request.session.perfil_estrutural == perfil.pk %}btn-primary{%else%}btn-default{%endif%}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" pk="{{object.pk}}" perfil_pk="{{perfil.pk}}" action="json_get_perfis" title="{{perfil.nome}}">
{{perfil.sigla}}
</button>
{% endfor %}

2
sapl/templates/compilacao/text_edit.html

@ -11,7 +11,7 @@
{% endblock %}
{% block title%}
<h1 class="page-header">{{object }}. <small><i>{% trans 'Texto Multivigente em Edição' %}</i></small></h1>
<h2 class="page-header">{% if object.content_object.title_type %}{{object.content_object.title_type}}{%else%}{{object}}{% endif %}{% comment %}<small><i>{% trans 'Texto Multivigente em Edição' %}</i></small>{% endcomment %}</h2>
{% endblock %}
{% block actions %}

11
sapl/templates/compilacao/text_list.html

@ -9,6 +9,11 @@
{{block.super}}
<link rel="stylesheet" href="{% sass_src 'styles/compilacao.scss' %}" type="text/css">
{% endblock %}
{% block title %}
<h2 class="page-header">{% if object.content_object.title_type %}{{object.content_object.title_type}}{%else%}{{object}}{% endif %}{% comment %}<small><i>{% trans 'Texto Multivigente em Edição' %}</i></small>{% endcomment %}</h2>
{% endblock %}
{% block extra_sections_nav %}
<li><a href="{% url 'sapl.compilacao:ta_text' object.pk %}?print">{% trans 'Versão para Impressão' %}</a></li>
{% endblock %}
@ -108,13 +113,15 @@
</div>
{% endblock base_content %}
{% block foot_js %}
{{block.super}}
<script type="text/javascript" src="{% static 'js/compilacao.js' %}"></script>
<script type="text/javascript" src="{% static 'js/compilacao_view.js' %}"></script>
{% if perms.compilacao.add_nota %}
<script type="text/javascript" src="{% static 'js/compilacao_notas.js' %}"></script>
{% endif %}
{% endblock %}

2
sapl/templates/compilacao/text_list_bloco.html

@ -13,7 +13,7 @@
{% if forloop.first and not view|isinst:'TextView' %}
{% else %}
<div class="dpt {%if dpt.tipo_dispositivo.dispositivo_de_articulacao and dpt.tipo_dispositivo.dispositivo_de_alteracao%}{{dpt.tipo_dispositivo.class_css}}{% endif %}" nivel="{{dpt.nivel}}">
<div class="dpt {%if dpt.tipo_dispositivo.dispositivo_de_articulacao and dpt.tipo_dispositivo.dispositivo_de_alteracao%}{{dpt.tipo_dispositivo.class_css}}{% endif %}{% if 'indent' in dpt.tipo_dispositivo.class_css%}indent{% endif %}" nivel="{{dpt.nivel}}">
{% endif%}
{% spaceless %}

6
sapl/templates/compilacao/textoarticulado_detail.html

@ -31,7 +31,7 @@
{% block base_content %}
{% block actions %}
<div class="clearfix">
<div class="clearfix hidden-print">
<div class="actions btn-group pull-right" role="group">
{% if perms.compilacao.lock_unlock_textoarticulado and not object.editable_only_by_owners%}
@ -44,12 +44,12 @@
<a href="{% url 'sapl.compilacao:ta_text_edit' object.pk %}" class="btn btn-default">{% trans 'Editar Texto' %}</a>
{% endif %}
</div>
</div>
</div>
{% endblock actions %}
{% block detail_content %} {# TODO replace fieldset for something semantically correct, but with similar visual grouping style #}
<fieldset>
<fieldset class="hidden-print">
<legend>{%trans 'Identificação Básica'%}</legend>
<div class="row">
<div class="col-md-3">

12
sapl/templates/compilacao/tipotextoarticulado_detail.html

@ -56,6 +56,18 @@
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="div_id_ano" class="holder">
<label>{% field_verbose_name object 'perfis' %}</label>
<ul>
{% for perfil in object.perfis.all %}
<li>{{perfil}}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</fieldset>
{% endblock detail_content %}
{% endblock base_content %}

20
sapl/templates/materia/layouts.yaml

@ -75,7 +75,7 @@ Relatoria:
TipoProposicao:
{% trans 'Tipo Proposição' %}:
- descricao content_type
- tipo_conteudo_related
- tipo_conteudo_related perfis
Proposicao:
{% trans 'Proposição' %}:
@ -119,3 +119,21 @@ LegislacaoCitadaDetail:
- disposicoes parte livro titulo
- capitulo secao subsecao artigo
- paragrafo inciso alinea item
MateriaLegislativaDetail:
{% trans 'Identificação Básica' %}:
- tipo ano numero
- data_apresentacao numero_protocolo tipo_apresentacao
- texto_original
- numeracao_set
{% trans 'Outras Informações' %}:
- apelido dias_prazo polemica
- objeto regime_tramitacao em_tramitacao
- data_fim_prazo data_publicacao complementar
{% trans 'Origem Externa' %}:
- tipo_origem_externa numero_origem_externa ano_origem_externa
- local_origem_externa data_origem_externa
{% trans 'Dados Textuais' %}:
- ementa
- indexacao
- observacao

5
sapl/templates/materia/materialegislativa_filter.html

@ -48,7 +48,7 @@
<strong>Localização Atual:</strong> &nbsp;{{m.tramitacao_set.last.unidade_tramitacao_destino|default_if_none:"Não Informada"}}</br>
<strong>Status:</strong> &nbsp;{{m.tramitacao_set.last.status|default_if_none:"Não Informada"}}</br>
{% if m.registrovotacao_set.exists %}
<strong>Data Votação:</strong>
<strong>Data da última Votação:</strong>
{% if m.registrovotacao_set.last.ordem %}
<a href="{% url 'sapl.sessao:ordemdia_list' m.registrovotacao_set.last.ordem.sessao_plenaria_id %}">
{{ m.registrovotacao_set.last.ordem.data_ordem }}
@ -61,7 +61,8 @@
</br>
{% endif %}
<strong>Data da última Tramitação:</strong> &nbsp;{{m.tramitacao_set.last.data_tramitacao|default_if_none:"Não Informada"}}</br>
<strong>Ementa:</strong>&nbsp;{{ m.ementa|safe }}</br>
<strong>Ementa:</strong>&nbsp;{{ m.ementa|safe }}
{% if m.texto_original %}</br></br><strong><a href="{{m.texto_original.url}}">Texto Original</a></strong>{% endif %}
<p></p>
</tr>
{% endfor %}

9
sapl/templates/materia/prop_devolvidas_list.html

@ -14,7 +14,7 @@
<th>Tipo</th>
<th>Descrição</th>
<th>Autor</th>
<th>Vínculo</th>
<th>Motivo da Devolução</th>
</tr>
</thead>
<tbody>
@ -24,12 +24,7 @@
<td>{{ prop.tipo.descricao }}</td>
<td>{{ prop.descricao }}</td>
<td>{{ prop.autor }}</td>
<td>
{% if prop.materia_gerada %}
<a href="{% url 'sapl.materia:materialegislativa_detail' prop.materia_gerada.pk %}">{{ prop.materia_gerada.tipo.sigla }} {{ prop.materia_gerada.numero }}/{{ prop.materia_gerada.ano }}</a>
{% elif prop.documento_gerado %}
<a href="{% url 'sapl.materia:documentoacessorio_detail' prop.documento_gerado.pk %}">{{ prop.documento_gerado.materia.tipo.sigla }} {{ prop.documento_gerado.materia.numero }}/{{ prop.documento_gerado.materia.ano }}</a>
{% endif %}
<td>{{ prop.justificativa_devolucao}}
</td>
</tr>
{% endfor %}

16
sapl/templates/materia/proposicao_detail.html

@ -15,14 +15,16 @@
{% block editions %}
{% if object.data_envio %}
{% block editions_actions_return %}
<div class="actions btn-group" role="group">
<a class="btn btn-default" onclick="window.open('{% url 'sapl.materia:recibo-proposicao' object.pk %}','Recibo','width=1100, height=600, scrollbars=yes')">{% trans "Recibo de Envio" %}</a>
{% if not object.data_recebimento %}
<a href="{{ view.detail_url }}?action=return" class="btn btn-default btn-excluir">{% trans 'Retornar Proposição Enviada' %}</a>
{% endif %}
</div>
{% if user == object.autor.user %}
{% block editions_actions_return %}
<div class="actions btn-group" role="group">
<a class="btn btn-default" onclick="window.open('{% url 'sapl.materia:recibo-proposicao' object.pk %}','Recibo','width=1100, height=600, scrollbars=yes')">{% trans "Recibo de Envio" %}</a>
{% if not object.data_recebimento %}
<a href="{{ view.detail_url }}?action=return" class="btn btn-default btn-excluir">{% trans 'Retornar Proposição Enviada' %}</a>
{% endif %}
</div>
{% endblock %}
{% endif %}
{% else %}

21
sapl/templates/materia/proposicao_form.html

@ -13,6 +13,25 @@
$("#div_id_texto_original").addClass('hidden');
});
$("select[name=tipo]").change(function(event) {
if (this.selectedOptions[0].getAttribute('data-has-perfil') === "True") {
$("input[name=tipo_texto]").closest('label').removeClass('disabled');
$("input[name=tipo_texto]").closest('.form-group').parent().removeClass('hidden');
$("input[name=tipo_texto]").prop('disabled', false);
}
else {
$("input[name=tipo_texto]").closest('label').addClass('disabled');
$("input[name=tipo_texto]").closest('.form-group').parent().addClass('hidden');
$("input[name=tipo_texto]").prop('disabled', true);
}
if ($("input[name=tipo_texto]:checked").length == 0) {
$("input[name=tipo_texto]").first().prop('checked', true);
$("input[name=tipo_texto]").first().closest('label').addClass('checked');
}
});
$("select[name=tipo_materia], input[name=numero_materia], input[name=ano_materia]").change(function(event) {
var url = '{% url 'sapl.api:materialegislativa-list'%}'
@ -31,7 +50,7 @@
});
});
$("input[name=tipo_texto], select[name=tipo_materia]").trigger('change');
$("input[name=tipo_texto], select[name=tipo_materia], select[name=tipo]").trigger('change');
});
</script>

6
sapl/templates/materia/tipoproposicao_form.html

@ -6,7 +6,7 @@ aaa
<script type="text/javascript">
$(document).ready(function(){
var initial_select = $("input[name=tipo_conteudo_related]").val();
var initial_select = parseInt($("input[name=tipo_conteudo_related]").val());
$("input[name=tipo_conteudo_related]").remove();
$('#id_content_type').change(function(event) {
var pk = this[event.target.selectedIndex].value;
@ -18,8 +18,8 @@ $(document).ready(function(){
data.forEach(function (val, index) {
var html_radio = '<div class="radio'+(initial_select==val.value?' checked':'')+'"> <label><span class="icons"><span class="first-icon"></span><span class="second-icon"></span></span><input type="radio" name="tipo_conteudo_related" id="id_tipo_conteudo_related_'+index+'" value="'+val.value+'"'+(initial_select?' checked="checked"':'')+' style="display:none;">'+val.text+'</label></div>';
if (val === initial_select)
initial_select='';
if (val.value === initial_select)
initial_select=0;
radios.append(html_radio);
});
});

2
sapl/templates/navbar.yaml

@ -45,7 +45,7 @@
- title: {% trans 'Normas Jurídicas' %}
children:
- title: {% trans 'Pesquisar Normas Jurídicas' %}
url: sapl.norma:normajuridica_list
url: sapl.norma:norma_pesquisa
- title: {% trans 'Sistema' %}
check_permission: base.menu_sistemas

15
sapl/templates/norma/layouts.yaml

@ -49,3 +49,18 @@ LegislacaoCitadaDetail:
- disposicoes parte livro titulo
- capitulo secao subsecao artigo
- paragrafo inciso alinea item
VinculoNormaJuridica:
{% trans 'Tipo de Vínculo entre Normas Jurídicas' %}:
- sigla:2 descricao
NormaRelacionada:
{% trans 'Norma Relacionada' %}:
- tipo numero ano
- tipo_vinculo
- ementa
NormaRelacionadaDetail:
{% trans 'Norma Relacionada' %}:
- norma_relacionada
- tipo_vinculo

39
sapl/templates/norma/list_pesquisa.html

@ -1,39 +0,0 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load common_tags %}
{% block actions %}{% endblock %}
{% block detail_content %}
<div class="actions btn-group pull-right" role="group">
{% if perms.norma.add_normajuridica %}
<a href="{% url 'sapl.norma:normajuridica_create' %}" class="btn btn-default">Adicionar Norma Jurídica</a>
{% endif %}
</div>
<br /><br /><br />
{% if object_list %}
<table class="table table-striped">
<thead class="thead-default">
<tr>
<th>Tipo da Norma Juridica</th>
<th>Número</th>
<th>Ano</th>
<th>Data</th>
<th>Ementa</th>
</tr>
</thead>
{% for obj in object_list %}
<tr>
<td><a href="{% url 'sapl.norma:normajuridica_detail' obj.id %}">{{obj.tipo}}</a></td>
<td>{{obj.numero}}</td>
<td>{{obj.ano}}</td>
<td>{{obj.data}}</td>
<td>{{obj.ementa|safe}}</td>
</tr>
{% endfor %}
</table>
{% else %}
<h2>Nenhum Registro recuperado</h2>
{% endif %}
{% endblock detail_content %}

34
sapl/templates/norma/normajuridica_detail.html

@ -0,0 +1,34 @@
{% extends "crud/detail.html" %}
{% load i18n common_tags%}
{% block detail_content %}
{% for fieldset in view.layout_display %}
<h2 class="legend">{{ fieldset.legend }}</h2>
{% for row in fieldset.rows %}
<div class="row-fluid">
{% for column in row %}
<div class="col-sm-{{ column.span }}">
<div id="div_id_{{ column.id }}" class="form-group">
<p class="control-label">{{ column.verbose_name }}</p>
<div class="controls">
{% comment %}TODO Transformar os links em URLs diretamente no CRUD{% endcomment %}
{% if column.text|url %}
<div class="form-control-static"><a href="{{ column.text|safe }}"> {{ column.text|safe }} </a></div>
{% elif column.verbose_name == 'Matéria' %}
{% if object.materia.id %}
<div class="form-control-static"><a href="{% url 'sapl.materia:materialegislativa_detail' object.materia.id %}"> {{ column.text|safe }} </a></div>
{% else %}
<div class="form-control-static">{{ column.text|safe }}</div>
{% endif %}
{% else %}
<div class="form-control-static">{{ column.text|safe }}</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
{% endfor %}
{% endblock detail_content %}

62
sapl/templates/norma/normajuridica_filter.html

@ -0,0 +1,62 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block actions %}
<div class="actions btn-group pull-right" role="group">
{% if perms.norma.add_normajuridica %}
<a href="{% url 'sapl.norma:normajuridica_create' %}" class="btn btn-default">
{% blocktrans with verbose_name=view.verbose_name %} Adicionar Norma Jurídica {% endblocktrans %}
</a>
{% endif %}
{% if filter_url %}
<a href="{% url 'sapl.norma:norma_pesquisa' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
{% endif %}
</div>
<br /><br />
{% endblock %}
{% block detail_content %}
{% if not filter_url %}
{% crispy filter.form %}
{% endif %}
{% if filter_url %}
{% if page_obj|length %}
<br />
{% if page_obj|length > 1 %}
<h3 style="text-align:right;">Pesquisa concluída com sucesso! Foram encontradas {{paginator.count}} normas.</h3>
{% elif page_obj|length == 1 %}
<h3 style="text-align:right;">{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 norma.'%}</h3>
{% endif %}
<br />
<table class="table table-striped">
<thead class="thead-default">
<tr>
<th>Tipo</th>
<th>Número</th>
<th>Ano</th>
<th>Data</th>
<th>Ementa</th>
</tr>
</thead>
{% for n in page_obj %}
<tr>
<td><a href="{% url 'sapl.norma:normajuridica_detail' n.id %}">{{n.tipo}}</a></td>
<td>{{n.numero}}</td>
<td>{{n.ano}}</td>
<td>{{n.data}}</td>
<td>{{n.ementa|safe}}</td>
</tr>
{% endfor %}
</table>
{% include "paginacao.html" %}
{% else %}
<br /><br />
<h2>Nenhuma norma encontrada com essas especificações</h2>
{% endif %}
{% endif %}
{% endblock detail_content %}
{% block table_content %}
{% endblock table_content %}

27
sapl/templates/norma/normarelacionada_form.html

@ -0,0 +1,27 @@
{% extends "crud/form.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load common_tags %}
{% block extra_js %}
<script language="Javascript">
function recuperar_norma() {
var tipo = $("#id_tipo").val()
var numero = $("#id_numero").val()
var ano = $("#id_ano").val()
if (tipo && numero && ano) {
$.get("/norma/recuperar-norma",{tipo: tipo,
numero: numero,
ano: ano},
function(data, status) {
$("#id_ementa").val(data.ementa);
});
}
}
var fields = ["#id_tipo", "#id_numero", "#id_ano"]
for (i = 0; i < fields.length; i++) {
$(fields[i]).change(recuperar_norma);
}
</script>
{% endblock %}

20
sapl/templates/norma/pesquisa.html

@ -1,20 +0,0 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load common_tags %}
{% block base_content %}
{% block actions %}
<div class="actions btn-group pull-right" role="group">
{% if perms.norma.add_normajuridica %}
<a href="{% url 'sapl.norma:normajuridica_create' %}" class="btn btn-default">{% trans 'Adicionar Norma Juridica' %}</a>
{% endif %}
</div>
<br /><br />
{% endblock %}
{% block detail_content %}
{% crispy form %}
{% endblock %}
{% endblock %}

2
sapl/templates/norma/subnav.yaml

@ -2,6 +2,8 @@
- title: {% trans 'Início' %}
url: normajuridica_detail
- title: {% trans 'Normas Relacionadas' %}
url: normarelacionada_list
# Opção adicionada para chamar o TextoArticulado da norma.
# para integração foram necessárias apenas criar a url norma_ta em urls.py

234
sapl/templates/painel/index.html

@ -1,5 +1,5 @@
{% load i18n %}
{% load staticfiles %}
{% load staticfiles sass_tags %}
<!DOCTYPE HTML>
<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!--[if gt IE 8]><!-->
@ -8,6 +8,7 @@
<head>
<meta charset="UTF-8">
<!-- TODO: does it need this head_title here? -->
<link rel="stylesheet" href="{% sass_src 'bootstrap-sass/assets/stylesheets/_bootstrap.scss' %}" type="text/css">
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript" src="{% static 'jquery/dist/jquery.js' %}"></script>
@ -16,12 +17,11 @@
<style type="text/css">
@media screen {
body {
background: #2B2B2A;
background: #1c1b1b;
}
ul, li {
list-style-type: none;
}
#sessao_plenaria, #sessao_plenaria_data, #sessao_plenaria_hora_inicio, #message, #cronometro_discurso, #cronometro_aparte, #cronometro_ordem, #relogio, #parlamentares, #votacao, #materia_legislativa_texto, #observacao_materia, #resultado_votacao{
font-family: Verdana;
}
@ -29,66 +29,77 @@
</style>
</head>
<body>
<audio type="hidden" id="audio" src="{% static 'audio/ring.mp3' %}"> </audio>
<h1 id="title"></h1>
<input id="json_url" type="hidden" value="{% url 'sapl.painel:dados_painel' sessao_id %}">
<h3><font color="#4FA64D"><p align="center"><span id="sessao_plenaria"></span></p></font></h3>
<h1><b><font color="#4FA64D"><p align="center"><span id="sessao_plenaria"></span></p></font></b></h1>
<table style="width:100%">
<tr>
<th style="text-align:center"><font color="white" size="2"><span id="sessao_plenaria_data"></span></font></th>
<th style="text-align:center"><font color="white" size="2"><span id="sessao_plenaria_hora_inicio"></span></font></th>
<th style="text-align:center"><font color="white" size="4"><span id="sessao_plenaria_data"></span></font></th>
<th style="text-align:center"><font color="white" size="4"><span id="sessao_plenaria_hora_inicio"></span></font></th>
</tr>
</table>
<h2><font color="red"><p align="center"><span id="message"></span></p></font></h2>
<h3><font color="white"><p align="center">________________________________________________</p></font></h3>
<h3><font color="white"><p align="center"><span id="relogio"></span></p></font></h3>
<h3><font color="white"><p align="center">________________________________________________</p></font></h3>
<h3><font color="#459170"><p style="font-family:Verdana" align="center">Cronômetros</p></font></h3>
<table style="width:100%">
<tr>
<th style="text-align:center; font-family:Verdana"><font color="white">Discurso: <span id="cronometro_discurso"></span></font></th>
</tr>
<tr>
<th style="text-align:center; font-family:Verdana"><font color="white">Aparte: <span id="cronometro_aparte"></span></font></th>
</tr>
<tr>
<th style="text-align:center; font-family:Verdana"><font color="white">Questão de Ordem: <span id="cronometro_ordem"></span></font></th>
</tr>
</table>
<h3><font color="white"><p align="center">________________________________________________</p></font></h3>
<h3><font color="#459170"><p style="font-family:Verdana" align="center">Parlamentares e Votos</p></font></h3>
<table style="width:60%" align="center">
<tr>
<th style="text-align:left"><font color="white" align="left"><span id="parlamentares"></span></font></th>
<th style="text-align:left"><font color="white"><span id="votacao"></span></font></th>
</tr>
</table>
<h3><font color="white"><p align="center">________________________________________________</p></font></h3>
<h3><font color="#459170"><p align="center" style="font-family:Verdana">Matéria em Votação</p></font></h3>
<table style="width:100%; border:1px;">
<tr><th style="text-align:center"><font color="white"><span id="materia_legislativa_texto"></span></font></th></tr>
<tr><th style="text-align:center"><font color="white"><span id="observacao_materia"></span></font></th></tr>
<h1><font color="white"><p align="center"><span id="relogio"></span></p></font></h1>
<div class="row container-detail clearfix">
<div class="row-fluid">
<div class="col-md-4">
<h2><font color="#459170"><p align="center" style="font-family:Verdana">Parlamentares</p></b></font></h2>
<table align="center">
<tr>
<th><h4><font color="white"><span id="parlamentares"></span></h4></font></th>
</tr>
</table>
</div>
<div class="col-md-4" >
<h2><font color="#459170"><p align="center" style="font-family:Verdana">Cronômetros</p></font></h2>
<table align="center">
<tr>
<th style="font-family:Verdana; text-align:center;"><font size="5" color="white">Discurso: <span id="cronometro_discurso"></span></font></th>
</tr>
<tr>
<th style="font-family:Verdana; text-align:center;"><font size="5" color="white">Aparte: <span id="cronometro_aparte"></span></font></th>
</tr>
<tr>
<th style="font-family:Verdana; text-align:center;"><font size="5" color="white">Questão de Ordem: <span id="cronometro_ordem"></span></font></th>
</tr>
</table>
</div>
<div class="col-md-4">
<h2><font color="#459170"><p align="center" style="font-family:Verdana">Resultado</p></font></h2>
<table align="center">
<tr>
<th><h4><font color="white"><span id="votacao"></span></h4></font></th>
</tr>
</table>
</div>
</div>
</div>
</br>
<h2><font color="#459170"><p align="center" style="font-family:Verdana">Matéria em Votação</p></font></h2>
<table style="width:75%; border:1px;" align="center">
<tr><th style="text-align:center"><h4><font color="white"><span id="materia_legislativa_texto"></span></font></th></tr>
<tr><th style="text-align:center"><h4><font color="white"><span id="observacao_materia"></span></font></th></tr>
<tr><th style="text-align:center"><font color="#45919D"><span id="resultado_votacao"></span></font></th></tr>
</table>
</body>
<script type="text/javascript">
$(document).ready(function() {
//TODO: replace by a fancy jQuery clock
function checkTime(i) {
if (i<10) {i = "0" + i}; // add zero in front of numbers < 10
return i;
}
function startTime() {
var today=new Date();
var h=today.getHours();
@ -101,15 +112,18 @@
startTime()
}, 500);
}
startTime();
var audioAlertFinish = document.getElementById("audio");
$('#cronometro_discurso').runner({
autostart: false,
countdown: true,
startAt: 5 * 60 * 1000, // 5 minutes
stopAt: 0,
milliseconds: false
}).on('runnerFinish', function(eventObject, info){
audioAlertFinish.play();
});
$('#cronometro_aparte').runner({
@ -118,6 +132,8 @@
startAt: 3 * 60 * 1000, // 3 minutes
stopAt: 0,
milliseconds: false
}).on('runnerFinish', function(eventObject, info){
audioAlertFinish.play();
});
$('#cronometro_ordem').runner({
@ -126,11 +142,13 @@
startAt: 2 * 60 * 1000, // 2 minutes
stopAt: 0,
milliseconds: false
}).on('runnerFinish', function(eventObject, info){
audioAlertFinish.play();
});
var discurso_previous = '';
var aparte_previous = '';
var ordem_previous = '';
var discurso_previous;
var ordem_previous;
var aparte_previous;
var counter = 1;
(function poll() {
@ -138,82 +156,118 @@
url: $("#json_url").val(),
type: "GET",
success: function(data) {
$("#sessao_plenaria").text(data["sessao_plenaria"])
$("#sessao_plenaria_data").text("Data Início: " + data["sessao_plenaria_data"])
$("#sessao_plenaria_hora_inicio").text("Hora Início: " + data["sessao_plenaria_hora_inicio"])
if (data["status_painel"] === "FECHADO") {
if (data["status_painel"] == "FECHADO") {
$("#message").text("PAINEL ENCONTRA-SE FECHADO");
return;
}
else{
$("#message").text("");
}
var presentes = $("#parlamentares");
var votacao = $("#votacao");
$("#votacao").text('');
presentes.children().remove();
votacao.children().remove()
if (data['materia_legislativa_texto']){
if (data["presentes_ordem_dia"] != null) {
presentes_ordem_dia = data["presentes_ordem_dia"];
}
else if (data["presentes_expediente"] != null){
presentes_ordem_dia = data["presentes_expediente"]
}
if( (data["tipo_resultado"] == "Aprovado por unanimidade") || (data["tipo_resultado"] == "Aprovado por maioria") || (data["tipo_resultado"] == "Rejeitado")){
if(data["tipo_votacao"] == "Nominal") {
jQuery.each(data["votos"], function(index, parlamentar) {
$('<li />', {text: parlamentar.parlamentar + ' - ' + parlamentar.partido + ' - Voto: ' + parlamentar.voto}).appendTo(presentes);
});
}
else{
jQuery.each(presentes_ordem_dia, function(index, parlamentar) {
$('<li />', {text: parlamentar.nome + ' - ' + parlamentar.partido}).appendTo(presentes);
});
}
}
if (data["presentes_ordem_dia"] != null) {
presentes_ordem_dia = data["presentes_ordem_dia"];
}
else if (data["presentes_expediente"] != null){
presentes_ordem_dia = data["presentes_expediente"]
}
if( (data["tipo_resultado"] == "Aprovado por unanimidade") || (data["tipo_resultado"] == "Aprovado por maioria") || (data["tipo_resultado"] == "Rejeitado")){
if(data["tipo_votacao"] == "Nominal") {
jQuery.each(data["votos"], function(index, parlamentar) {
$('<li />', {text: parlamentar.parlamentar + ' - ' + parlamentar.partido + ' - Voto: ' + parlamentar.voto}).appendTo(presentes);
else{
jQuery.each(presentes_ordem_dia, function(index, parlamentar) {
$('<li />', {text: parlamentar.nome + ' - ' + parlamentar.partido}).appendTo(presentes);
});
}
}else{
jQuery.each(presentes_ordem_dia, function(index, parlamentar) {
$('<li />', {text: parlamentar.nome + ' - ' + parlamentar.partido}).appendTo(presentes);
});
}
//console.debug(presentes_ordem_dia)
var votacao = $("#votacao")
if (data["num_presentes_ordem_dia"] != null) {
num_presentes_ordem_dia = data["num_presentes_ordem_dia"];
}
else if (data["num_presentes_expediente"] != null){
num_presentes_ordem_dia = data["num_presentes_expediente"]
}
votacao.append("<li>Sim: " + data["numero_votos_sim"] + "</li>")
votacao.append("<li>Não: " + data["numero_votos_nao"] + "</li>")
votacao.append("<li>Abstenções: " + data["numero_abstencoes"] + "</li>")
votacao.append("<li>Presentes: " + num_presentes_ordem_dia + "</li>")
votacao.append("<li>Total votos: " + data["total_votos"] + "</li>")
}
//console.debug(presentes_ordem_dia)
var votacao = $("#votacao")
if (data["num_presentes_ordem_dia"] != null) {
num_presentes_ordem_dia = data["num_presentes_ordem_dia"];
}
else if (data["num_presentes_expediente"] != null){
num_presentes_ordem_dia = data["num_presentes_expediente"]
}
votacao.children().remove()
votacao.append("<li>Sim: " + data["numero_votos_sim"] + "</li>")
votacao.append("<li>Não: " + data["numero_votos_nao"] + "</li>")
votacao.append("<li>Abstenções: " + data["numero_abstencoes"] + "</li>")
votacao.append("<li>Presentes: " + num_presentes_ordem_dia + "</li>")
votacao.append("<li>Total votos: " + data["total_votos"] + "</li>")
else{
$("#votacao").text('Não há votação, pois não há nenhuma matéria aberta ou já votada.');
}
var discurso_current = data["cronometro_discurso"];
if (!discurso_previous){
discurso_previous = ''
}
if (discurso_current != discurso_previous) {
$('#cronometro_discurso').runner(discurso_current);
discurso_previous = discurso_current;
}
var aparte_current = data["cronometro_aparte"];
if (!aparte_previous){
aparte_previous = ''
}
if (aparte_current != aparte_previous) {
$('#cronometro_aparte').runner(aparte_current);
aparte_previous = aparte_current;
}
var ordem_current = data["cronometro_ordem"];
if (!ordem_previous){
ordem_previous = ''
}
if (ordem_current != ordem_previous) {
$('#cronometro_ordem').runner(ordem_current);
ordem_previous = ordem_current;
}
$("#materia_legislativa_texto").text(data["materia_legislativa_texto"])
$("#observacao_materia").text(data["observacao_materia"])
$("#resultado_votacao").text(data["tipo_resultado"])
if (data['materia_legislativa_texto']){
$("#materia_legislativa_texto").text(data["materia_legislativa_texto"]);
}
else{
$("#materia_legislativa_texto").text('Não há nehuma matéria votada ou para votação');
}
if (data['observacao_materia']){
$("#observacao_materia").text(data["observacao_materia"]);
}
else{
$("#observacao_materia").text('');
}
if (data['resultado_votacao']){
$("#resultado_votacao").text(data["tipo_resultado"]);
}
else{
$("#resultado_votacao").text('');
}
},
error: function(err) {
console.error(err);
@ -225,4 +279,4 @@
})();
});
</script>
</html>
</html>

8
sapl/templates/parlamentares/parlamentar_perfil_publico.html

@ -28,7 +28,7 @@
<p><b>Nome Completo: </b> &nbsp {{object.nome_completo}}</p>
</div>
</div>
<div class="col-sm-8">
<div id="div_data_nascimento" class="form-group">
<p><b>Partido: </b> &nbsp {{object.filiacao_set.first.partido|default_if_none:"Não informado"}}</p>
@ -65,7 +65,11 @@
</div>
</div>
<div class="col-sm-8">
<div id="div_biografia" class="form-group">
<p><b>Biografia: </b> &nbsp {{object.biografia|safe}}</p>
</div>
</div>
</div>
{% endblock detail_content %}

26
sapl/templates/protocoloadm/comprovante.html

@ -59,25 +59,27 @@
</tr>
<tr>
<th>Data / Horário</th>
<td>{{ protocolo.data|date:"d/m/Y" }} - {{ protocolo.timestamp|date:"H:m:s" }}</td>
</tr>
<tr>
<th>Ementa</th>
<td>{{ protocolo.assunto_ementa }}</td>
</tr>
<tr>
<th>Interessado</th>
<td>{{ protocolo.interessado }}</td>
<td>{{ protocolo.data|date:"d/m/Y" }} - {{ protocolo.timestamp|date:"H:i:s" }}</td>
</tr>
{% if protocolo.tipo_processo == 0 %}
<tr>
<th>Ementa</th>
<td>{{ protocolo.assunto_ementa }}</td>
</tr>
<tr>
<th>Interessado</th>
<td>{{ protocolo.interessado }}</td>
</tr>
{% endif %}
<tr>
<th>Natureza</th>
<td>
{% if protocolo.tipo_protocolo == 0 %} Administrativo {% elif protocolo.tipo_protocolo == 1 %} Matéria Legislativa {% endif %}
{% if protocolo.tipo_processo == 0 %} Administrativo {% elif protocolo.tipo_processo == 1 %} Legislativo {% endif %}
</td>
</tr>
<tr>
<th>Tipo Documento</th>
<td>{{ protocolo.tipo_documento }}</td>
<th>{% if protocolo.tipo_documento %} Tipo Documento {% else %} Tipo Matéria {% endif %}</th>
<td>{% if protocolo.tipo_documento %} {{protocolo.tipo_documento}} {% else %} {{protocolo.tipo_materia}} {% endif %}</td>
</tr>
<tr>
<th>Número Páginas</th>

16
sapl/templates/protocoloadm/documentoadministrativo_filter.html

@ -6,10 +6,11 @@
{% block sections_nav %} {% endblock %}
{% block actions %}
<h1><b>Documentos Administrativos</b></h1>
<div class="actions btn-group pull-right" role="group">
{% if perms|get_add_perm:view %}
<a href="docadm/create" class="btn btn-default">{% trans 'Adicionar Documento Administrativo' %}</a>
{% if perms.protocoloadm %}
<a href="{% url 'sapl.protocoloadm:documentoadministrativo_create' %}" class="btn btn-default">
{% blocktrans with verbose_name=view.verbose_name %} Adicionar Documento Administrativo {% endblocktrans %}
</a>
{% endif %}
</div>
{% if filter_url %}
@ -18,8 +19,10 @@
</div>
{% endif %}
{% endblock actions %}
{% block base_content %}
{% block detail_content %}
{% if not filter_url %} {% crispy filter.form %} {% endif %}
{% if filter_url %}
<table class="table table-striped table-bordered">
<thead class="thead-default"><tr><td><h3>Resultados</h3></td></tr></thead>
@ -45,4 +48,7 @@
</table>
{% include "paginacao.html" %}
{% endif %}
{% endblock base_content %}
{% endblock detail_content %}
{% block table_content %}
{% endblock table_content %}

10
sapl/templates/protocoloadm/protocolo_filter.html

@ -39,13 +39,19 @@
<td>
<strong>Protocolo:
<a href="{% url 'protocoloadm:protocolo_mostrar' p.pk %}">{{ p.numero|stringformat:'06d' }}/{{ p.ano }}</a></strong>&nbsp;&nbsp;<strong>-</strong>&nbsp;&nbsp;
<a href="{% url 'relatorios:relatorio_etiqueta_protocolo' p.numero p.ano %}"><img src="{% static 'img/etiqueta.png' %}" alt="Etiqueta Individual"></a></br>
<a href="{% url 'relatorios:relatorio_etiqueta_protocolo' p.numero p.ano %}"><img src="{% static 'img/etiqueta.png' %}" alt="Etiqueta Individual"></a>
{% if p.anulado %}<strong><font color="red">&nbsp;&nbsp;** NULO **</font></strong>{% endif %}
</br>
<strong>Assunto:</strong> {{ p.assunto_ementa|default_if_none:"Não Informado"}}</br>
<strong>Data Protocolo:</strong> {{ p.data|date:"d/m/Y"|default_if_none:"Não Informado" }} - Horário: {{ p.hora|date:"G:i:s" }}</br>
<strong>Interessado:</strong> {{ p.interessado }}</br>
<strong>Natureza do Processo:</strong>
{% if p.tipo_processo == 0 %} Administrativo {% elif p.tipo_processo == 1 %} Matéria Legislativa {% endif %}</br>
<strong>Classificação:</strong> {{ p.tipo_documento|default_if_none:"Não Informado" }} </br>
<strong>Classificação:</strong> {{ p.tipo_documento|default_if_none:p.tipo_materia }} </br>
{% if p.anulado %}
<strong>Anulado por: </strong>{{ p.user_anulacao }} - IP {{ p.ip_anulacao }}</br>
<strong>Motivo Anulação: </strong>{{ p.justificativa_anulacao }}</br>
{% endif %}
</td>
</tr>
{% endfor %}

8
sapl/templates/protocoloadm/protocolo_list.html

@ -28,7 +28,13 @@
{% elif p.tipo_processo == 1 %}
Matéria Legislativa
{% endif %}</br>
<strong>Classificação:</strong> {{ p.tipo_documento }} </br>
<strong>Classificação:</strong>
{% if p.tipo_processo == 0 %}
{{ p.tipo_documento }} </br>
{% elif p.tipo_processo == 1 %}
{{ p.tipo_materia }} </br>
{% endif %}</br>
<p></p>
</td>
</tr>

25
sapl/templates/protocoloadm/protocolo_mostrar.html

@ -3,17 +3,16 @@
{% load crispy_forms_tags %}
{% block detail_content %}
<strong>Protocolo:</strong>{{ protocolo.numero|stringformat:'06d' }}/{{ protocolo.ano }}</br>
<strong>Assunto:</strong> {{ protocolo.assunto_ementa }}</br>
<strong>Data Protocolo:</strong> {{ protocolo.data|date:"d/m/Y" }} - Horário: {{ protocolo.timestamp|date:"H:m:s" }}</br>
<strong>Interessado:</strong> {{ protocolo.interessado }}</br>
<strong>Protocolo: </strong>{{ protocolo.numero|stringformat:'06d' }}/{{ protocolo.ano }}</br>
<strong>Assunto: </strong> {{ protocolo.assunto_ementa|default:" Não informado." }}</br>
<strong>Data Protocolo: </strong> {{ protocolo.data|date:"d/m/Y" }} - Horário: {{ protocolo.hora|date:"H:i" }}</br>
<strong>Interessado: </strong> {{ protocolo.interessado|default:" Não informado." }}</br>
<!-- TODO: convert if-else to custom tag -->
<strong>Natureza do Processo:</strong>{% if protocolo.tipo_processo == 0 %} Administrativo {% elif protocolo.tipo_processo == 1 %} Matéria Legislativa {% endif %}</br>
<strong>Classificação:</strong> {{ protocolo.tipo_documento }} </br>
<strong>Número de Páginas:</strong> {{ protocolo.numero_paginas }} </br>
<strong>Observação:</strong>{{ protocolo.observacao|default:"Não há" }}</br>
<br />
<strong>Natureza do Processo: </strong>{% if protocolo.tipo_processo == 0 %} Administrativo {% elif protocolo.tipo_processo == 1 %} Legislativo {% endif %}</br>
<strong>Número de Páginas: </strong> {{ protocolo.numero_paginas }} </br>
<strong>Observação: </strong>{{ protocolo.observacao|default:" Não informado." }}</br>
<strong>Anulado: {% if protocolo.anulado %} <font color="red"> Sim {% else %} <font color="green"> Não {% endif %} </font></strong>
<br /><br />
<strong>Documento Vinculado:</strong>
@ -23,7 +22,7 @@
</br>
{% else %}
<br />
<a href="{% url 'sapl.protocoloadm:criar_documento' protocolo.pk %}" class="btn btn-primary">Criar Documento</a>
{% if not protocolo.anulado %} <a href="{% url 'sapl.protocoloadm:criar_documento' protocolo.pk %}" class="btn btn-primary">Criar Documento</a>{% endif %}
{% endif %}
{% elif protocolo.tipo_materia %}
{% if materia %}
@ -31,12 +30,12 @@
</br>
{% else %}
<br />
<a href="{% url 'sapl.materia:materia_create_simplificado' protocolo.pk %}" class="btn btn-primary">Criar Matéria</a>
{% if not protocolo.anulado %}<a href="{% url 'sapl.materia:materia_create_simplificado' protocolo.pk %}" class="btn btn-primary">Criar Matéria</a>{% endif %}
{% endif %}
{% endif %}
&nbsp;&nbsp;&nbsp;&nbsp;
<a target="popup" class="btn btn-primary" onclick="window.open('{% url 'sapl.protocoloadm:comprovante_protocolo' protocolo.pk%}','Comprovante','width=800, height=600')">Comprovante
<a target="popup" class="btn btn-primary" onclick="window.open('{% url 'sapl.protocoloadm:comprovante_protocolo' protocolo.pk%}','Comprovante','width=800, height=800')">Comprovante
</a>
{% endblock detail_content %}

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

Loading…
Cancel
Save