diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 000000000..cc23cbaee
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,50 @@
+
+3.1.163-RC16 / 2023-09-13
+=========================
+
+ * Conserta bug em relatórios com emenda longa. (#3674)
+ * fix: restring acesso ao prometheus metrics para apenas ips locais/invalidos (#3668)
+ * feat: adiciona filtro de autor no relatorio de tramitacao com data fim de prazo (#3671)
+ * fix: verifica se existe dispositivo atualizador ao tentar montar nota alteracao (#3669)
+ * Revert "Remove redirect de URLs (#3652)"
+
+3.1.163-RC15 / 2023-08-11
+=========================
+
+ * HOT-FIX: conserta geração de CHANGES.md
+ * fix: Cria novos campos para o model proposicao para salvar o usuario responsavel por cada acao (#3660)
+ * Simplificação da tela de pesquisa de Matéria Legislativa (#3662)
+ * bump pyyaml
+ * Adiciona controle de visibilidade no módulo de relatorios
+ * Adiciona coluna de justificativa de ausência (#3657)
+ * Adiciona coluna de justificativa de ausência
+ * Conserta lógica para embutir SAPL em iframe (#3653)
+ * Move relatorios para app de relatorios (#3656)
+ * Remove redirect de URLs (#3652)
+ * Hot-fix: endpoint do prometheus endpoint URL
+ * feat: Adiciona funcionalidade de baixar lista de documentos acessorios de um documento administrativo (#3650)
+
+3.1.163-RC14 / 2023-06-28
+=========================
+
+ * HOT-FIX: conserta changelog
+ * feat: Torna o campo Data Nascimento de Parlamentares sensivel (#3648)
+ * hot-fix: desconecta signal pre_save no migrate
+
+3.1.163-RC13 / 2023-06-18
+=========================
+
+ * Add options to abort release generation
+ * Adiciona documentação automática de mudanças
+ * Adiciona link para texto original em Sessão Plenária (#3644)
+ * Altera nome completo para nome parlamentar em ata (#3645)
+ * refactor: altera título do link e descrição de relatório
+ * feat: Script to find and extract codified images pasted into text fields using the tinyMCE editor (#3643)
+ * feat: adiciona a coluna assunto na list de correspondencias do expediente do dia (#3640)
+ * fix: força periodo de busca no relatorio audit log (#3639)
+ * add migrate de ano novo
+ * impl: add campo para script do google analytics
+ * refactor: corrige relatório alinhando a proposta da nomenclatura
+ * hot-fix: corrige inicialização de variável
+ * fix: ativa filtro que estava comentado para debug
+ * impl: captura de assinaturas eletrônicas em matérias
diff --git a/README.rst b/README.rst
index b7ead05d1..5481ba985 100644
--- a/README.rst
+++ b/README.rst
@@ -71,6 +71,9 @@ Orientações gerais sobre o GitHub
===================================
`Instruções para GitHub `_
+Suporte ao utilizadores
+===================================
+ `Sala do Discord "Somos Interlegis" sobre SAPL `_
Perguntas Frequentes
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
index 4ad60bd78..5bae23442 100644
--- a/docker/docker-compose.yaml
+++ b/docker/docker-compose.yaml
@@ -25,14 +25,14 @@ services:
labels:
NAME: "solr"
volumes:
- - solr_data:/opt/solr/server/solr
+ - solr_data:/var/solr
- solr_configsets:/opt/solr/server/solr/configsets
ports:
- "8983:8983"
networks:
- sapl-net
sapl:
- image: interlegis/sapl:3.1.163-RC7
+ image: interlegis/sapl:3.1.163-RC16
# build:
# context: ../
# dockerfile: ./docker/Dockerfile
diff --git a/docker/start.sh b/docker/start.sh
index 57b83b484..558b7d7b5 100755
--- a/docker/start.sh
+++ b/docker/start.sh
@@ -114,6 +114,9 @@ if [ $lack_pwd -eq 0 ]; then
# return -1
fi
+# Backfilling AuditLog's JSON field
+time ./manage.py backfill_auditlog &
+
echo "-------------------------------------"
echo "| ███████╗ █████╗ ██████╗ ██╗ |"
echo "| ██╔════╝██╔══██╗██╔══██╗██║ |"
diff --git a/drfautoapi/drfautoapi.py b/drfautoapi/drfautoapi.py
index 5d1518d06..e0fd0c348 100644
--- a/drfautoapi/drfautoapi.py
+++ b/drfautoapi/drfautoapi.py
@@ -2,17 +2,19 @@ from collections import OrderedDict
import importlib
import inspect
import logging
+import re
from django.apps.config import AppConfig
from django.apps.registry import apps
from django.conf import settings
from django.contrib.postgres.fields.jsonb import JSONField
from django.db.models.base import ModelBase
+from django.db.models.fields import TextField, CharField
from django.db.models.fields.files import FileField
from django.template.defaultfilters import capfirst
from django.utils.translation import ugettext_lazy as _
import django_filters
-from django_filters.constants import ALL_FIELDS
+from django_filters.constants import ALL_FIELDS, EMPTY_VALUES
from django_filters.filters import CharFilter
from django_filters.filterset import FilterSet
from django_filters.rest_framework.backends import DjangoFilterBackend
@@ -26,6 +28,43 @@ from rest_framework.viewsets import ModelViewSet
logger = logging.getLogger(__name__)
+class SplitStringCharFilter(django_filters.CharFilter):
+ _re = re.compile(r'("[^"]+"| +|[^"]+)')
+
+ def filter(self, qs, value):
+ if value in EMPTY_VALUES:
+ return qs
+ if self.distinct:
+ qs = qs.distinct()
+ lookup = '%s__%s' % (self.field_name, self.lookup_expr)
+
+ values = [value]
+ if self.lookup_expr == 'icontains':
+ if not '"' in value:
+ values = value.split(' ')
+ else:
+ values = list(
+ filter(
+ lambda x: x and x != ' ' and x[0] != '"',
+ self._re.findall(value)
+ )
+ ) + list(
+ map(
+ lambda x: x[1:-1],
+ filter(
+ lambda x: x and x[0] == '"',
+ self._re.findall(value)
+ )
+ )
+ )
+
+ if not isinstance(values, list):
+ values = [values]
+ for v in values:
+ qs = self.get_method(qs)(**{lookup: v})
+ return qs
+
+
class ApiFilterSetMixin(FilterSet):
o = CharFilter(method='filter_o')
@@ -39,6 +78,12 @@ class ApiFilterSetMixin(FilterSet):
'lookup_expr': 'exact',
},
},
+ CharField: {
+ 'filter_class': SplitStringCharFilter,
+ },
+ TextField: {
+ 'filter_class': SplitStringCharFilter,
+ },
JSONField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
@@ -81,16 +126,16 @@ class ApiFilterSetMixin(FilterSet):
r = []
for lk, lv in cl.items():
- if lk == 'contained_by':
+ if lk in ('contained_by', 'trigram_similar', 'unaccent', 'search'):
continue
sflk = f'{sub_f}{"__" if sub_f else ""}{lk}'
r.append(sflk)
- if hasattr(lv, 'class_lookups'):
- r += get_keys_lookups(lv.class_lookups, sflk)
+ if hasattr(lv, 'get_lookups'):
+ r += get_keys_lookups(lv.get_lookups(), sflk)
- if hasattr(lv, 'output_field') and hasattr(lv, 'output_field.class_lookups'):
+ if hasattr(lv, 'output_field') and hasattr(lv, 'output_field.get_lookups'):
r.append(f'{sflk}{"__" if sflk else ""}range')
r += get_keys_lookups(lv.output_field.class_lookups, sflk)
@@ -98,7 +143,7 @@ class ApiFilterSetMixin(FilterSet):
return r
fields[f_str] = list(
- set(fields[f_str] + get_keys_lookups(f.class_lookups, '')))
+ set(fields[f_str] + get_keys_lookups(f.get_lookups(), '')))
# Remove excluded fields
exclude = exclude or []
diff --git a/frontend/src/__apps/compilacao/js/old/compilacao_edit.js b/frontend/src/__apps/compilacao/js/old/compilacao_edit.js
index 7eabb0d7a..8ab884033 100644
--- a/frontend/src/__apps/compilacao/js/old/compilacao_edit.js
+++ b/frontend/src/__apps/compilacao/js/old/compilacao_edit.js
@@ -245,7 +245,7 @@ window.DispositivoEdit = function () {
if (editortype !== 'construct') {
dpt_form.html(data)
if (editortype === 'tinymce') {
- window.initTextRichEditor(null, false, true)
+ window.initTextRichEditor(null, false, false)
}
// OptionalCustomFrontEnd().init()
}
diff --git a/frontend/src/__apps/compilacao/scss/compilacao.scss b/frontend/src/__apps/compilacao/scss/compilacao.scss
index e4c564b4c..82c52f7f6 100644
--- a/frontend/src/__apps/compilacao/scss/compilacao.scss
+++ b/frontend/src/__apps/compilacao/scss/compilacao.scss
@@ -444,6 +444,14 @@ a:link:after, a:visited:after {
.texto_n_estruturado{
margin-top: 0.3333em;
font-size: 1.15em;
+
+ .dtxt {
+ display: inline;
+
+ & > :not(table):first-child {
+ display: inline !important;
+ }
+ }
}
diff --git a/frontend/src/__global/js/tinymce/index.js b/frontend/src/__global/js/tinymce/index.js
index bfb854396..99f94c14e 100644
--- a/frontend/src/__global/js/tinymce/index.js
+++ b/frontend/src/__global/js/tinymce/index.js
@@ -14,12 +14,13 @@ import 'tinymce/plugins/table'
import './langs/pt_BR.js'
window.tinymce = tinymce
-window.initTextRichEditor = function (elements, readonly = false) {
+window.initTextRichEditor = function (elements, readonly = false, paste_as_text = false) {
const configTinymce = {
selector: elements === null || elements === undefined ? 'textarea' : elements,
language: 'pt_BR',
branding: false,
forced_root_block: 'p',
+ paste_as_text,
plugins: 'table lists advlist link code',
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link | code',
menubar: 'file edit view insert format table'
diff --git a/frontend/webpack-stats.json b/frontend/webpack-stats.json
index 94e46cd84..a32e5c1e7 100644
--- a/frontend/webpack-stats.json
+++ b/frontend/webpack-stats.json
@@ -1,16 +1,16 @@
{
"status": "done",
"assets": {
- "fonts/fa-brands-400.f5defc2e.ttf": {
- "name": "fonts/fa-brands-400.f5defc2e.ttf",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-brands-400.f5defc2e.ttf",
- "publicPath": "/static/sapl/frontend/fonts/fa-brands-400.f5defc2e.ttf"
- },
"fonts/fa-brands-400.86c7e1fa.woff2": {
"name": "fonts/fa-brands-400.86c7e1fa.woff2",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-brands-400.86c7e1fa.woff2",
"publicPath": "/static/sapl/frontend/fonts/fa-brands-400.86c7e1fa.woff2"
},
+ "fonts/fa-brands-400.f5defc2e.ttf": {
+ "name": "fonts/fa-brands-400.f5defc2e.ttf",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-brands-400.f5defc2e.ttf",
+ "publicPath": "/static/sapl/frontend/fonts/fa-brands-400.f5defc2e.ttf"
+ },
"fonts/fa-regular-400.e0550912.woff2": {
"name": "fonts/fa-regular-400.e0550912.woff2",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-regular-400.e0550912.woff2",
@@ -41,10 +41,10 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/css/global.45591136.css",
"publicPath": "/static/sapl/frontend/css/global.45591136.css"
},
- "js/global.e8c9c610.js": {
- "name": "js/global.e8c9c610.js",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.e8c9c610.js",
- "publicPath": "/static/sapl/frontend/js/global.e8c9c610.js"
+ "js/global.f01dd32a.js": {
+ "name": "js/global.f01dd32a.js",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.f01dd32a.js",
+ "publicPath": "/static/sapl/frontend/js/global.f01dd32a.js"
},
"css/parlamentar.cd5dc5a8.css": {
"name": "css/parlamentar.cd5dc5a8.css",
@@ -71,10 +71,10 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/css/compilacao.f4baf459.css",
"publicPath": "/static/sapl/frontend/css/compilacao.f4baf459.css"
},
- "js/compilacao.1c9473f1.js": {
- "name": "js/compilacao.1c9473f1.js",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/compilacao.1c9473f1.js",
- "publicPath": "/static/sapl/frontend/js/compilacao.1c9473f1.js"
+ "js/compilacao.d68d2b28.js": {
+ "name": "js/compilacao.d68d2b28.js",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/compilacao.d68d2b28.js",
+ "publicPath": "/static/sapl/frontend/js/compilacao.d68d2b28.js"
},
"css/chunk-vendors.9904f9d0.css": {
"name": "css/chunk-vendors.9904f9d0.css",
@@ -476,21 +476,16 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt",
"publicPath": "/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt"
},
- "js/global.e8c9c610.js.LICENSE.txt": {
- "name": "js/global.e8c9c610.js.LICENSE.txt",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.e8c9c610.js.LICENSE.txt",
- "publicPath": "/static/sapl/frontend/js/global.e8c9c610.js.LICENSE.txt"
+ "js/global.f01dd32a.js.LICENSE.txt": {
+ "name": "js/global.f01dd32a.js.LICENSE.txt",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.f01dd32a.js.LICENSE.txt",
+ "publicPath": "/static/sapl/frontend/js/global.f01dd32a.js.LICENSE.txt"
},
"fonts/fa-v4compatibility.7e7e1dad.ttf.gz": {
"name": "fonts/fa-v4compatibility.7e7e1dad.ttf.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-v4compatibility.7e7e1dad.ttf.gz",
"publicPath": "/static/sapl/frontend/fonts/fa-v4compatibility.7e7e1dad.ttf.gz"
},
- "js/global.e8c9c610.js.gz": {
- "name": "js/global.e8c9c610.js.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.e8c9c610.js.gz",
- "publicPath": "/static/sapl/frontend/js/global.e8c9c610.js.gz"
- },
"js/parlamentar.25e7f0fa.js.gz": {
"name": "js/parlamentar.25e7f0fa.js.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/parlamentar.25e7f0fa.js.gz",
@@ -506,10 +501,15 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/painel.7aa779e9.js.gz",
"publicPath": "/static/sapl/frontend/js/painel.7aa779e9.js.gz"
},
- "js/compilacao.1c9473f1.js.gz": {
- "name": "js/compilacao.1c9473f1.js.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/compilacao.1c9473f1.js.gz",
- "publicPath": "/static/sapl/frontend/js/compilacao.1c9473f1.js.gz"
+ "js/compilacao.d68d2b28.js.gz": {
+ "name": "js/compilacao.d68d2b28.js.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/compilacao.d68d2b28.js.gz",
+ "publicPath": "/static/sapl/frontend/js/compilacao.d68d2b28.js.gz"
+ },
+ "js/global.f01dd32a.js.gz": {
+ "name": "js/global.f01dd32a.js.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/global.f01dd32a.js.gz",
+ "publicPath": "/static/sapl/frontend/js/global.f01dd32a.js.gz"
},
"css/compilacao.f4baf459.css.gz": {
"name": "css/compilacao.f4baf459.css.gz",
@@ -521,16 +521,16 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/img/down_arrow_select.jpg.gz",
"publicPath": "/static/sapl/frontend/img/down_arrow_select.jpg.gz"
},
- "js/skins/content/dark/content.min.css.gz": {
- "name": "js/skins/content/dark/content.min.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/dark/content.min.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/content/dark/content.min.css.gz"
- },
"js/skins/content/dark/content.css.gz": {
"name": "js/skins/content/dark/content.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/dark/content.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/content/dark/content.css.gz"
},
+ "js/skins/content/dark/content.min.css.gz": {
+ "name": "js/skins/content/dark/content.min.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/dark/content.min.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/content/dark/content.min.css.gz"
+ },
"js/skins/content/default/content.min.css.gz": {
"name": "js/skins/content/default/content.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/default/content.min.css.gz",
@@ -566,31 +566,31 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/tinymce-5-dark/content.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/content/tinymce-5-dark/content.css.gz"
},
- "js/skins/content/tinymce-5-dark/content.min.css.gz": {
- "name": "js/skins/content/tinymce-5-dark/content.min.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/tinymce-5-dark/content.min.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/content/tinymce-5-dark/content.min.css.gz"
- },
"js/skins/content/writer/content.css.gz": {
"name": "js/skins/content/writer/content.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/writer/content.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/content/writer/content.css.gz"
},
+ "js/skins/content/tinymce-5-dark/content.min.css.gz": {
+ "name": "js/skins/content/tinymce-5-dark/content.min.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/tinymce-5-dark/content.min.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/content/tinymce-5-dark/content.min.css.gz"
+ },
"js/skins/content/writer/content.min.css.gz": {
"name": "js/skins/content/writer/content.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/content/writer/content.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/content/writer/content.min.css.gz"
},
- "js/skins/ui/oxide/content.css.gz": {
- "name": "js/skins/ui/oxide/content.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/content.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/oxide/content.css.gz"
- },
"js/skins/ui/oxide/content.inline.css.gz": {
"name": "js/skins/ui/oxide/content.inline.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/content.inline.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide/content.inline.css.gz"
},
+ "js/skins/ui/oxide/content.css.gz": {
+ "name": "js/skins/ui/oxide/content.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/content.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/oxide/content.css.gz"
+ },
"js/skins/ui/oxide/content.min.css.gz": {
"name": "js/skins/ui/oxide/content.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/content.min.css.gz",
@@ -631,91 +631,86 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/content.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/content.min.css.gz"
},
- "js/skins/ui/oxide/skin.css.gz": {
- "name": "js/skins/ui/oxide/skin.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/skin.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/oxide/skin.css.gz"
+ "js/skins/ui/oxide/skin.min.css.gz": {
+ "name": "js/skins/ui/oxide/skin.min.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/skin.min.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/oxide/skin.min.css.gz"
},
"js/skins/ui/oxide-dark/skin.shadowdom.css.gz": {
"name": "js/skins/ui/oxide-dark/skin.shadowdom.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.css.gz"
},
- "js/skins/ui/oxide/skin.min.css.gz": {
- "name": "js/skins/ui/oxide/skin.min.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/skin.min.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/oxide/skin.min.css.gz"
+ "js/skins/ui/oxide/skin.css.gz": {
+ "name": "js/skins/ui/oxide/skin.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide/skin.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/oxide/skin.css.gz"
},
"js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz": {
"name": "js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.shadowdom.min.css.gz"
},
- "js/skins/ui/tinymce-5/content.css.gz": {
- "name": "js/skins/ui/tinymce-5/content.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/content.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/content.css.gz"
+ "js/skins/ui/tinymce-5/content.inline.min.css.gz": {
+ "name": "js/skins/ui/tinymce-5/content.inline.min.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/content.inline.min.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/content.inline.min.css.gz"
},
"js/skins/ui/tinymce-5/content.inline.css.gz": {
"name": "js/skins/ui/tinymce-5/content.inline.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/content.inline.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/content.inline.css.gz"
},
- "js/skins/ui/tinymce-5/content.inline.min.css.gz": {
- "name": "js/skins/ui/tinymce-5/content.inline.min.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/content.inline.min.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/content.inline.min.css.gz"
+ "js/skins/ui/tinymce-5/content.css.gz": {
+ "name": "js/skins/ui/tinymce-5/content.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/content.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/content.css.gz"
},
"js/skins/ui/tinymce-5/content.min.css.gz": {
"name": "js/skins/ui/tinymce-5/content.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/content.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/content.min.css.gz"
},
- "js/skins/ui/oxide-dark/skin.min.css.gz": {
- "name": "js/skins/ui/oxide-dark/skin.min.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.min.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.min.css.gz"
- },
- "js/skins/ui/oxide-dark/skin.css.gz": {
- "name": "js/skins/ui/oxide-dark/skin.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.css.gz"
- },
"js/skins/ui/tinymce-5/skin.shadowdom.css.gz": {
"name": "js/skins/ui/tinymce-5/skin.shadowdom.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/skin.shadowdom.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/skin.shadowdom.css.gz"
},
+ "js/skins/ui/oxide-dark/skin.min.css.gz": {
+ "name": "js/skins/ui/oxide-dark/skin.min.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.min.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.min.css.gz"
+ },
"js/skins/ui/tinymce-5/skin.shadowdom.min.css.gz": {
"name": "js/skins/ui/tinymce-5/skin.shadowdom.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/skin.shadowdom.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/skin.shadowdom.min.css.gz"
},
- "js/skins/ui/tinymce-5-dark/content.css.gz": {
- "name": "js/skins/ui/tinymce-5-dark/content.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.css.gz"
- },
"js/skins/ui/tinymce-5-dark/content.inline.css.gz": {
"name": "js/skins/ui/tinymce-5-dark/content.inline.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.css.gz"
},
- "js/skins/ui/tinymce-5-dark/content.min.css.gz": {
- "name": "js/skins/ui/tinymce-5-dark/content.min.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.min.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.min.css.gz"
+ "js/skins/ui/oxide-dark/skin.css.gz": {
+ "name": "js/skins/ui/oxide-dark/skin.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/oxide-dark/skin.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/oxide-dark/skin.css.gz"
},
- "js/skins/ui/tinymce-5/skin.css.gz": {
- "name": "js/skins/ui/tinymce-5/skin.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/skin.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/skin.css.gz"
+ "js/skins/ui/tinymce-5-dark/content.css.gz": {
+ "name": "js/skins/ui/tinymce-5-dark/content.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.css.gz"
},
"js/skins/ui/tinymce-5-dark/content.inline.min.css.gz": {
"name": "js/skins/ui/tinymce-5-dark/content.inline.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.inline.min.css.gz"
},
+ "js/skins/ui/tinymce-5-dark/content.min.css.gz": {
+ "name": "js/skins/ui/tinymce-5-dark/content.min.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.min.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/content.min.css.gz"
+ },
"js/skins/ui/tinymce-5-dark/skin.shadowdom.css.gz": {
"name": "js/skins/ui/tinymce-5-dark/skin.shadowdom.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.shadowdom.css.gz",
@@ -726,26 +721,31 @@
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.shadowdom.min.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.shadowdom.min.css.gz"
},
- "js/skins/ui/tinymce-5/skin.min.css.gz": {
- "name": "js/skins/ui/tinymce-5/skin.min.css.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/skin.min.css.gz",
- "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/skin.min.css.gz"
+ "js/skins/ui/tinymce-5/skin.css.gz": {
+ "name": "js/skins/ui/tinymce-5/skin.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/skin.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/skin.css.gz"
},
"js/chunk-vendors.874df7f4.js.LICENSE.txt.gz": {
"name": "js/chunk-vendors.874df7f4.js.LICENSE.txt.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt.gz",
"publicPath": "/static/sapl/frontend/js/chunk-vendors.874df7f4.js.LICENSE.txt.gz"
},
- "fonts/fa-regular-400.3edb9004.ttf.gz": {
- "name": "fonts/fa-regular-400.3edb9004.ttf.gz",
- "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-regular-400.3edb9004.ttf.gz",
- "publicPath": "/static/sapl/frontend/fonts/fa-regular-400.3edb9004.ttf.gz"
+ "js/skins/ui/tinymce-5/skin.min.css.gz": {
+ "name": "js/skins/ui/tinymce-5/skin.min.css.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5/skin.min.css.gz",
+ "publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5/skin.min.css.gz"
},
"js/skins/ui/tinymce-5-dark/skin.css.gz": {
"name": "js/skins/ui/tinymce-5-dark/skin.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.css.gz",
"publicPath": "/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.css.gz"
},
+ "fonts/fa-regular-400.3edb9004.ttf.gz": {
+ "name": "fonts/fa-regular-400.3edb9004.ttf.gz",
+ "path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/fonts/fa-regular-400.3edb9004.ttf.gz",
+ "publicPath": "/static/sapl/frontend/fonts/fa-regular-400.3edb9004.ttf.gz"
+ },
"js/skins/ui/tinymce-5-dark/skin.min.css.gz": {
"name": "js/skins/ui/tinymce-5-dark/skin.min.css.gz",
"path": "/home/leandro/desenvolvimento/envs/sapl/sapl/static/sapl/frontend/js/skins/ui/tinymce-5-dark/skin.min.css.gz",
@@ -782,7 +782,7 @@
"css/chunk-vendors.9904f9d0.css",
"js/chunk-vendors.874df7f4.js",
"css/global.45591136.css",
- "js/global.e8c9c610.js"
+ "js/global.f01dd32a.js"
],
"parlamentar": [
"css/chunk-vendors.9904f9d0.css",
@@ -800,7 +800,7 @@
"css/chunk-vendors.9904f9d0.css",
"js/chunk-vendors.874df7f4.js",
"css/compilacao.f4baf459.css",
- "js/compilacao.1c9473f1.js"
+ "js/compilacao.d68d2b28.js"
]
},
"publicPath": "/static/sapl/frontend/"
diff --git a/release.sh b/release.sh
index 80051e9d1..bcb98908c 100755
--- a/release.sh
+++ b/release.sh
@@ -4,6 +4,10 @@
## Versioning info: [major].[minor].[patch][-RC[num]], example: 3.1.159, 3.1.159-RC1
##
+## IMPORTANT: requires gh and git-extras commands installed
+## Currently only runs on MacOS because of sed issue on lines 41 to 47 (see double quotes after -i)
+##
+
# TODO: verificar porque só pega versões superiores (3.1.200 ao invés de 3.1.200-RC9)
# VERSION=`git describe --tags --abbrev=0`
@@ -11,7 +15,12 @@ VERSION_PATTERN='([0-9]+)\.([0-9]+)\.([0-9]+)(-RC[0-9]+)?'
SED_AWKWARD_PATTERN="[0-9]+\.[0-9]+\.[0-9]+(-RC[0-9]+){0,1}"
-LATEST_VERSION=$(git tag | egrep $VERSION_PATTERN | sort --version-sort | tail -1)
+# Define colors
+green_color='\033[0;32m'
+red_color='\033[0;31m'
+reset_color='\033[0m'
+
+LATEST_VERSION=$(git tag | egrep $VERSION_PATTERN | sort --version-sort -r | head -1)
MAJOR_VERSION=$(echo $LATEST_VERSION | cut -d"-" -f1)
MAJOR_TAG_CREATED=$(git tag | egrep $MAJOR_VERSION"$")
@@ -30,9 +39,10 @@ FINAL_VERSION=
function change_files {
+ # TODO: figure out better way of getting latest version
OLD_VERSION=$(grep -E 'interlegis/sapl:'$VERSION_PATTERN docker/docker-compose.yaml | cut -d':' -f3)
- echo "Atualizando de "$OLD_VERSION" para "$FINAL_VERSION
+ echo "Updating from "$OLD_VERSION" to "$FINAL_VERSION""
sed -E -i "" "s|$OLD_VERSION|$FINAL_VERSION|g" docker/docker-compose.yaml
@@ -41,6 +51,7 @@ function change_files {
sed -E -i "" "s|$OLD_VERSION|$FINAL_VERSION|g" sapl/templates/base.html
sed -E -i "" "s|$OLD_VERSION|$FINAL_VERSION|g" sapl/settings.py
+
}
function set_major_version {
@@ -61,37 +72,68 @@ function set_rc_version {
fi
FINAL_VERSION=$NEXT_RC_VERSION
+## DEBUG
+# echo "OLD_VERSION: $OLD_VERSION"
+# echo "FINAL_VERSION: $FINAL_VERSION"
+}
- echo "OLD_VERSION: $OLD_VERSION"
- echo "FINAL_VERSION: $FINAL_VERSION"
+# Function to display Yes/No prompt with colored message
+prompt_yes_no() {
+ while true; do
+ echo -e "${green_color}$1 (y/n): ${reset_color}\c"
+ read answer
+ case $answer in
+ [Yy]* ) return 0;;
+ [Nn]* ) return 1;;
+ * ) echo -e "${red_color}Please answer 'yes' or 'no'.${reset_color}";;
+ esac
+ done
}
function commit_and_push {
- echo "committing..."
+ echo -e "${green_color}Committing new release $FINAL_VERSION...${color_reset}"
git add docker/docker-compose.yaml setup.py sapl/settings.py sapl/templates/base.html
- git commit -m "Release: $FINAL_VERSION"
- git tag $FINAL_VERSION
-
- echo "================================================================================"
- echo " Versão criada e gerada localmente."
- echo "Para enviar pro github execute..."
- echo "git push origin 3.1.x"
- echo "git push origin "$FINAL_VERSION
- echo "================================================================================"
-
- echo "done."
+ git changelog --tag $FINAL_VERSION --prune-old -x > latest_changes.md
+ cat latest_changes.md CHANGES.md > CHANGES.tmp
+ mv CHANGES.tmp CHANGES.md
+ git add CHANGES.md
+ rm latest_changes.md
+
+ if prompt_yes_no "${green_color}Do you want to commit SAPL $FINAL_VERSION release locally?${reset_color}"; then
+ git commit -m "Release: $FINAL_VERSION"
+ git tag $FINAL_VERSION
+ echo -e "${green_color}Commit and tag created locally!${color_reset}"
+ else
+ git reset --hard HEAD
+ echo -e "${red_color}Aborting release creation!${color_reset}"
+ return
+ fi
+
+ echo -e "${red_color}### BEFORE PROCEEDING, MAKE SURE THE NEW VERSION NUMBER AND CHANGES ARE CORRECT!${color_reset}"
+ echo -e "${green_color}Release: $FINAL_VERSION${reset_color}"
+ if prompt_yes_no "${green_color}Do you want to publish SAPL $FINAL_VERSION release on Github?${reset_color}"; then
+ echo -e "${green_color}Publishing $FINAL_VERSION on Github...${reset_color}"
+ current_date=$(date +%Y-%m-%d)
+ git push origin 3.1.x
+ gh release create $FINAL_VERSION --repo interlegis/sapl --title "Release: $FINAL_VERSION" --notes "Release notes for $FINAL_VERSION in CHANGES.md file. Release date: $current_date"
+ echo -e "${green_color}Done.${reset_color}"
+ else
+ echo -e "${red_color}Publishing aborted.${reset_color}"
+ fi
+ echo "${green_color}Done.${green_color}"
+ echo -e "${green_color}================================================================================${color_reset}"
}
case "$1" in
--latest)
git fetch
- echo $LATEST_VERSION
+ echo -e "${green_color}$LATEST_VERSION${reset_color}"
exit 0
;;
--major)
git fetch
set_major_version
- echo "generating major release: "$FINAL_VERSION
+ echo -e "${green_color}Creating MAJOR release: "$FINAL_VERSION"${reset_color}"
# git tag $FINAL_VERSION
change_files
commit_and_push
@@ -100,14 +142,14 @@ case "$1" in
--rc)
git fetch
set_rc_version
- echo "generating release candidate: "$FINAL_VERSION
+ echo -e "${green_color}Creating RELEASE CANDIDATE (RC): "$FINAL_VERSION"${reset_color}"
# git tag $FINAL_VERSION
change_files
commit_and_push
exit 0
;;
--top)
- git tag | sort --version-sort | tail "-$2"
+ git tag | sort --version-sort -r | head "-$2"
exit 0
;;
diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt
index 64308a0b2..2d38a76cd 100644
--- a/requirements/dev-requirements.txt
+++ b/requirements/dev-requirements.txt
@@ -7,4 +7,3 @@ ipdb==0.13.3
pdbpp==0.9.2
pip-review==0.4
pipdeptree==0.10.1
-pygraphviz==1.3.1
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index 6092f67f4..7405e8c42 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -17,20 +17,24 @@ django-ratelimit==3.0.1
easy-thumbnails==2.5
python-decouple==3.1
psycopg2-binary==2.8.6
-pyyaml==5.4
+pyyaml==6.0.1
pytz==2019.3
python-magic==0.4.15
unipath==1.1
WeasyPrint==51
-Pillow==9.0.1
+Pillow==9.3.0
gunicorn==19.9.0
more-itertools==8.2.0
pysolr==3.6.0
PyPDF4==1.27.0
-pyoai==2.5.0
+#pyoai==2.5.1
+git+https://github.com/infrae/pyoai@5ff2f15e869869e70d8139e4c37b7832854d7049
Unidecode==1.1.1
whitenoise==5.1.0
kazoo==2.8.0
+django-prometheus==2.2.0
+
+asn1crypto==1.5.1
git+https://github.com/interlegis/trml2pdf
git+https://github.com/interlegis/django-admin-bootstrapped
diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py
index 3fae6240d..903051d10 100644
--- a/sapl/api/serializers.py
+++ b/sapl/api/serializers.py
@@ -86,7 +86,7 @@ class ParlamentarSerializerPublic(SaplSerializerMixin):
class Meta:
model = Parlamentar
- exclude = ["cpf", "rg", "fax",
+ exclude = ["cpf", "rg", "fax", "data_nascimento",
"endereco_residencia", "municipio_residencia",
"uf_residencia", "cep_residencia", "situacao_militar",
"telefone_residencia", "titulo_eleitor", "fax_residencia"]
@@ -104,6 +104,7 @@ class ParlamentarSerializerVerbose(SaplSerializerMixin):
import os
if not obj.fotografia or not os.path.exists(obj.fotografia.path):
return thumbnail_url
+ self.logger.warning(f"Iniciando cropping da imagem {obj.fotografia}")
thumbnail_url = get_backend().get_thumbnail_url(
obj.fotografia,
{
@@ -113,6 +114,7 @@ class ParlamentarSerializerVerbose(SaplSerializerMixin):
'detail': True,
}
)
+ self.logger.warning(f"Cropping da imagem {obj.fotografia} realizado com sucesso")
except Exception as e:
self.logger.error(e)
self.logger.error('erro processando arquivo: %s' %
diff --git a/sapl/api/views_materia.py b/sapl/api/views_materia.py
index a2cca4a09..0d5356b0b 100644
--- a/sapl/api/views_materia.py
+++ b/sapl/api/views_materia.py
@@ -100,7 +100,7 @@ class _MateriaLegislativaViewSet:
ultima_tramitacao = materia.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
- serializer_class = MateriaApiViewSetConstrutor.get_viewset_for_model(
+ serializer_class = ApiViewSetConstrutor.get_viewset_for_model(
Tramitacao).serializer_class(ultima_tramitacao)
return Response(serializer_class.data)
diff --git a/sapl/audiencia/forms.py b/sapl/audiencia/forms.py
index 72f3e9c35..211db9855 100755
--- a/sapl/audiencia/forms.py
+++ b/sapl/audiencia/forms.py
@@ -1,5 +1,7 @@
import logging
+from datetime import datetime
+
from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import transaction
@@ -13,6 +15,7 @@ from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.parlamentares.models import Parlamentar
from sapl.utils import timezone, FileFieldCheckMixin, validar_arquivo
+
class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
logger = logging.getLogger(__name__)
data_atual = timezone.now()
@@ -53,7 +56,7 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
class Meta:
model = AudienciaPublica
- fields = ['tipo', 'numero', 'nome',
+ fields = ['tipo', 'numero', 'ano', 'nome',
'tema', 'data', 'hora_inicio', 'hora_fim',
'observacao', 'audiencia_cancelada', 'parlamentar_autor', 'requerimento', 'url_audio',
'url_video', 'upload_pauta', 'upload_ata',
@@ -85,6 +88,26 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
parlamentar_autor = cleaned_data["parlamentar_autor"]
requerimento = cleaned_data["requerimento"]
+ if cleaned_data["ano"] != cleaned_data["data"].year:
+ raise ValidationError(f"Ano da audiência ({cleaned_data['ano']}) difere "
+ f"do ano no campo data ({cleaned_data['data'].year})")
+
+ #
+ # TODO: converter hora_inicio e hora_fim para TimeField
+ #
+ # valida hora inicio
+ try:
+ datetime.strptime(cleaned_data["hora_inicio"], '%H:%M').time()
+ except ValueError:
+ raise ValidationError(f"Formato de horário de início inválido: {cleaned_data['hora_inicio']}")
+
+ # valida hora fim
+ if cleaned_data["hora_fim"]:
+ try:
+ datetime.strptime(cleaned_data["hora_fim"], '%H:%M').time()
+ except ValueError:
+ raise ValidationError(f"Formato de horário de fim inválido: {cleaned_data['hora_fim']}")
+
if materia and ano_materia and tipo_materia:
try:
self.logger.debug("Tentando obter MateriaLegislativa %s nº %s/%s." % (tipo_materia, materia, ano_materia))
@@ -115,14 +138,14 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
raise ValidationError(msg)
if not cleaned_data['numero']:
- ultima_audiencia = AudienciaPublica.objects.all().order_by('numero').last()
+ ultima_audiencia = AudienciaPublica.objects.all().order_by('ano', 'numero').last()
if ultima_audiencia:
cleaned_data['numero'] = ultima_audiencia.numero + 1
else:
cleaned_data['numero'] = 1
else:
- if AudienciaPublica.objects.filter(numero=cleaned_data['numero']).exclude(pk=self.instance.pk).exists():
- raise ValidationError(f"Já existe uma audiência com a numeração {cleaned_data['numero']}.")
+ if AudienciaPublica.objects.filter(numero=cleaned_data['numero'], ano=cleaned_data['ano']).exclude(pk=self.instance.pk).exists():
+ raise ValidationError(f"Já existe uma audiência pública com a numeração {str(cleaned_data['numero']).rjust(3, '0')}/{cleaned_data['ano']}.")
if self.cleaned_data['hora_inicio'] and self.cleaned_data['hora_fim']:
if self.cleaned_data['hora_fim'] < self.cleaned_data['hora_inicio']:
diff --git a/sapl/audiencia/migrations/0017_audienciapublica_ano.py b/sapl/audiencia/migrations/0017_audienciapublica_ano.py
new file mode 100644
index 000000000..958d82fc5
--- /dev/null
+++ b/sapl/audiencia/migrations/0017_audienciapublica_ano.py
@@ -0,0 +1,27 @@
+# Generated by Django 2.2.28 on 2023-01-31 03:01
+
+from django.db import migrations, models
+
+
+def preencher_ano(apps, schema_editor):
+ AudienciaPublica = apps.get_model('audiencia', 'AudienciaPublica')
+ for audiencia in AudienciaPublica.objects.all():
+ audiencia.ano = audiencia.data.year
+ audiencia.save()
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('audiencia', '0016_auto_20201013_1126'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='audienciapublica',
+ name='ano',
+ field=models.PositiveSmallIntegerField(choices=[(2024, 2024), (2023, 2023), (2022, 2022), (2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=2000, verbose_name='Ano'),
+ preserve_default=False,
+ ),
+ migrations.RunPython(preencher_ano),
+ ]
diff --git a/sapl/audiencia/migrations/0018_auto_20230529_1641.py b/sapl/audiencia/migrations/0018_auto_20230529_1641.py
new file mode 100644
index 000000000..98df068ec
--- /dev/null
+++ b/sapl/audiencia/migrations/0018_auto_20230529_1641.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.2.28 on 2023-05-29 19:41
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('audiencia', '0017_audienciapublica_ano'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='audienciapublica',
+ options={'ordering': ['ano', 'numero', 'nome', 'tipo'], 'verbose_name': 'Audiência Pública', 'verbose_name_plural': 'Audiências Públicas'},
+ ),
+ ]
diff --git a/sapl/audiencia/models.py b/sapl/audiencia/models.py
index d74f0b6e3..e5d011762 100755
--- a/sapl/audiencia/models.py
+++ b/sapl/audiencia/models.py
@@ -5,7 +5,7 @@ from model_utils import Choices
from sapl.materia.models import MateriaLegislativa
from sapl.parlamentares.models import (CargoMesa, Parlamentar)
-from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation,
+from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericRelation,
restringe_tipos_de_arquivo_txt, texto_upload_path,
OverwriteStorage)
@@ -39,7 +39,6 @@ class TipoAudienciaPublica(models.Model):
tipo = models.CharField(
max_length=1, verbose_name=_('Tipo de Audiência Pública'), choices=TIPO_AUDIENCIA_CHOICES, default='A')
-
class Meta:
verbose_name = _('Tipo de Audiência Pública')
verbose_name_plural = _('Tipos de Audiência Pública')
@@ -50,6 +49,7 @@ class TipoAudienciaPublica(models.Model):
class AudienciaPublica(models.Model):
+
materia = models.ForeignKey(
MateriaLegislativa,
on_delete=models.PROTECT,
@@ -62,6 +62,8 @@ class AudienciaPublica(models.Model):
blank=True,
verbose_name=_('Tipo de Audiência Pública'))
numero = models.PositiveIntegerField(blank=True, verbose_name=_('Número'))
+ ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'),
+ choices=RANGE_ANOS)
nome = models.CharField(
max_length=100, verbose_name=_('Nome da Audiência Pública'))
tema = models.CharField(
@@ -123,7 +125,7 @@ class AudienciaPublica(models.Model):
class Meta:
verbose_name = _('Audiência Pública')
verbose_name_plural = _('Audiências Públicas')
- ordering = ['nome', 'numero', 'tipo']
+ ordering = ['ano', 'numero', 'nome', 'tipo']
def __str__(self):
return self.nome
diff --git a/sapl/audiencia/views.py b/sapl/audiencia/views.py
index a35add9c2..443fc81e8 100755
--- a/sapl/audiencia/views.py
+++ b/sapl/audiencia/views.py
@@ -19,8 +19,8 @@ class AudienciaCrud(Crud):
public = [RP_LIST, RP_DETAIL, ]
class BaseMixin(Crud.BaseMixin):
- list_field_names = [ 'nome', 'tipo', 'materia', 'data']
- ordering = '-data', 'nome', 'numero', 'tipo'
+ list_field_names = ['numero', 'nome', 'tipo', 'materia', 'data']
+ ordering = '-ano', '-numero', '-data', 'nome', 'tipo'
class ListView(Crud.ListView):
paginate_by = 10
@@ -28,20 +28,20 @@ class AudienciaCrud(Crud):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- audiencia_materia = { str(a.id): (a.materia, a.numero) for a in context['object_list'] }
+ audiencia_materia = {str(a.id): (a.materia, a.numero, a.ano) for a in context['object_list']}
for row in context['rows']:
audiencia_id = row[0][1].split('/')[-1]
- tema = str(audiencia_materia[audiencia_id][1]) + ' - ' + row[0][0]
+ tema = str(audiencia_materia[audiencia_id][1]).rjust(3, '0') + '/' + str(audiencia_materia[audiencia_id][2])
row[0] = (tema, row[0][1])
- coluna_materia = row[2] # Se mudar a ordem de listagem, mudar aqui.
+ coluna_materia = row[3] # Se mudar a ordem de listagem, mudar aqui.
if coluna_materia[0]:
materia = audiencia_materia[audiencia_id][0]
if materia is not None:
url_materia = reverse('sapl.materia:materialegislativa_detail', kwargs={'pk': materia.id})
else:
url_materia = None
- row[2] = (coluna_materia[0], url_materia) # Se mudar a ordem de listagem, mudar aqui.
+ row[3] = (coluna_materia[0], url_materia) # Se mudar a ordem de listagem, mudar aqui.
return context
class CreateView(Crud.CreateView):
diff --git a/sapl/base/forms.py b/sapl/base/forms.py
index a0a88ac10..8c4b629ae 100644
--- a/sapl/base/forms.py
+++ b/sapl/base/forms.py
@@ -21,7 +21,7 @@ import django_filters
from haystack.forms import ModelSearchForm
from sapl.audiencia.models import AudienciaPublica
-from sapl.base.models import Autor, TipoAutor, OperadorAutor
+from sapl.base.models import Autor, AuditLog, TipoAutor, OperadorAutor
from sapl.comissoes.models import Reuniao
from sapl.crispy_layout_mixin import (form_actions, to_column, to_row,
SaplFormHelper, SaplFormLayout)
@@ -29,7 +29,7 @@ from sapl.materia.models import (DocumentoAcessorio, MateriaEmTramitacao,
MateriaLegislativa, UnidadeTramitacao,
StatusTramitacao)
from sapl.norma.models import NormaJuridica, NormaEstatisticas
-from sapl.parlamentares.models import Partido, SessaoLegislativa,\
+from sapl.parlamentares.models import Partido, SessaoLegislativa, \
Parlamentar, Votante
from sapl.protocoloadm.models import DocumentoAdministrativo
from sapl.rules import SAPL_GROUP_AUTOR, SAPL_GROUP_VOTANTE
@@ -44,13 +44,11 @@ from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField,
from .models import AppConfig, CasaLegislativa
-
ACTION_CREATE_USERS_AUTOR_CHOICE = [
('A', _('Associar um usuário existente')),
('N', _('Autor sem Usuário de Acesso ao Sapl')),
]
-
STATUS_USER_CHOICE = [
('R', _('Apenas retirar Perfil de Autor do Usuário que está sendo'
' desvinculado')),
@@ -61,7 +59,6 @@ STATUS_USER_CHOICE = [
class UserAdminForm(ModelForm):
-
is_active = forms.TypedChoiceField(label=_('Usuário Ativo'),
choices=YES_NO_CHOICES,
coerce=lambda x: x == 'True')
@@ -195,7 +192,8 @@ class UserAdminForm(ModelForm):
] + [
(g.id, g) for g in Group.objects.exclude(
user=self.instance).exclude(
- name__in=['Autor', 'Votante']
+ name__in=[
+ 'Autor', 'Votante']
).order_by('name')
]
@@ -365,7 +363,7 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm):
# existente
terceiro_caso = Q(data_inicio__range=(
data_inicio, data_fim), data_fim__gt=data_fim)
- sessoes_existentes = SessaoLegislativa.objects.filter(primeiro_caso | segundo_caso | terceiro_caso).\
+ sessoes_existentes = SessaoLegislativa.objects.filter(primeiro_caso | segundo_caso | terceiro_caso). \
exclude(pk=pk)
if sessoes_existentes:
@@ -373,7 +371,7 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm):
'inserida, favor verificar as Sessões existentes antes de criar uma '
'nova Sessão Legislativa')
- #sessoes_legislativas = SessaoLegislativa.objects.filter(legislatura=legislatura).exclude(pk=pk)
+ # sessoes_legislativas = SessaoLegislativa.objects.filter(legislatura=legislatura).exclude(pk=pk)
# if sessoes_legislativas:
# numeracoes = [n.numero for n in sessoes_legislativas]
@@ -476,7 +474,6 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm):
class TipoAutorForm(ModelForm):
-
class Meta:
model = TipoAutor
fields = ['descricao']
@@ -538,7 +535,7 @@ class AutorForm(ModelForm):
q = forms.CharField(
max_length=120, required=False,
label='Pesquise o nome do Autor com o '
- 'tipo Selecionado e marque o escolhido.')
+ 'tipo Selecionado e marque o escolhido.')
autor_related = ChoiceWithoutValidationField(label='',
required=False,
@@ -741,14 +738,63 @@ class AutorFilterSet(django_filters.FilterSet):
form_actions(label='Pesquisar')))
-class OperadorAutorForm(ModelForm):
+def get_username():
+ try:
+ return [(u, u) for u in
+ get_user_model().objects.all().order_by('username').values_list('username', flat=True)]
+ except:
+ return []
+
+
+def get_models():
+ return [(m, m) for m in
+ AuditLog.objects.distinct('model_name').order_by('model_name').values_list('model_name', flat=True)]
+
+
+class AuditLogFilterSet(django_filters.FilterSet):
+ OPERATION_CHOICES = (
+ ('U', 'Atualizado'),
+ ('C', 'Criado'),
+ ('D', 'Excluído'),
+ )
+
+ username = django_filters.ChoiceFilter(
+ choices=get_username(), label=_('Usuário'))
+ object_id = django_filters.NumberFilter(label=_('Id'))
+ operation = django_filters.ChoiceFilter(
+ choices=OPERATION_CHOICES, label=_('Operação'))
+ model_name = django_filters.ChoiceFilter(
+ choices=get_models, label=_('Tipo de Registro'))
+ timestamp = django_filters.DateRangeFilter(label=_('Período'))
+
+ class Meta:
+ model = AuditLog
+ fields = ['username', 'operation',
+ 'model_name', 'timestamp', 'object_id']
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ row0 = to_row([('username', 2),
+ ('operation', 2),
+ ('model_name', 4),
+ ('object_id', 2),
+ ('timestamp', 2)])
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Filtros'),
+ row0,
+ form_actions(label='Aplicar Filtro')))
+
+
+class OperadorAutorForm(ModelForm):
class Meta:
model = OperadorAutor
fields = ['user', ]
def __init__(self, *args, **kwargs):
-
row = to_row([('user', 12)])
self.helper = SaplFormHelper()
@@ -773,162 +819,7 @@ class OperadorAutorForm(ModelForm):
self.fields['user'].widget = forms.RadioSelect()
-class RelatorioDocumentosAcessoriosFilterSet(django_filters.FilterSet):
-
- @property
- def qs(self):
- parent = super(RelatorioDocumentosAcessoriosFilterSet, self).qs
- return parent.distinct().order_by('-data')
-
- class Meta(FilterOverridesMetaMixin):
- model = DocumentoAcessorio
- fields = ['tipo', 'materia__tipo', 'data']
-
- def __init__(self, *args, **kwargs):
-
- super(
- RelatorioDocumentosAcessoriosFilterSet, self
- ).__init__(*args, **kwargs)
-
- self.filters['tipo'].label = 'Tipo de Documento'
- self.filters['materia__tipo'].label = 'Tipo de Matéria do Documento'
- self.filters['data'].label = 'Período (Data Inicial - Data Final)'
-
- self.form.fields['tipo'].required = True
-
- row0 = to_row([('tipo', 6),
- ('materia__tipo', 6)])
-
- row1 = to_row([('data', 12)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Pesquisa'),
- row0, row1,
- buttons)
- )
-
-
-class RelatorioAtasFilterSet(django_filters.FilterSet):
-
- class Meta(FilterOverridesMetaMixin):
- model = SessaoPlenaria
- fields = ['data_inicio']
-
- @property
- def qs(self):
- parent = super(RelatorioAtasFilterSet, self).qs
- return parent.distinct().prefetch_related('tipo').exclude(
- upload_ata='').order_by('-data_inicio', 'tipo', 'numero')
-
- def __init__(self, *args, **kwargs):
- super(RelatorioAtasFilterSet, self).__init__(
- *args, **kwargs)
-
- self.filters['data_inicio'].label = 'Período de Abertura (Inicial - Final)'
- self.form.fields['data_inicio'].required = False
-
- row1 = to_row([('data_inicio', 12)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Atas das Sessões Plenárias'),
- row1, buttons, )
- )
-
-
-def ultimo_ano_com_norma():
- anos_normas = choice_anos_com_normas()
-
- if anos_normas:
- return anos_normas[0]
- return ''
-
-
-class RelatorioNormasMesFilterSet(django_filters.FilterSet):
-
- ano = django_filters.ChoiceFilter(required=True,
- label='Ano da Norma',
- choices=choice_anos_com_normas,
- initial=ultimo_ano_com_norma)
-
- tipo = django_filters.ChoiceFilter(required=False,
- label='Tipo Norma',
- choices=choice_tipos_normas,
- initial=0)
-
- class Meta:
- model = NormaJuridica
- fields = ['ano']
-
- def __init__(self, *args, **kwargs):
- super(RelatorioNormasMesFilterSet, self).__init__(
- *args, **kwargs)
-
- self.filters['ano'].label = 'Ano'
- self.form.fields['ano'].required = True
-
- row1 = to_row([('ano', 6), ('tipo', 6)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Normas por mês do ano.'),
- row1, buttons, )
- )
-
- @property
- def qs(self):
- parent = super(RelatorioNormasMesFilterSet, self).qs
- return parent.distinct().order_by('data')
-
-
class EstatisticasAcessoNormasForm(Form):
-
ano = forms.ChoiceField(required=True,
label='Ano de acesso',
choices=RANGE_ANOS,
@@ -946,6 +837,8 @@ class EstatisticasAcessoNormasForm(Form):
(10, '010 mais acessadas'),
(50, '050 mais acessadas'),
(100, '100 mais acessadas'),
+ (500, '500 mais acessadas'),
+ (1000, '1000 mais acessadas'),
],
initial=5)
@@ -980,7 +873,7 @@ class EstatisticasAcessoNormasForm(Form):
)
self.fields['ano'].choices = NormaEstatisticas.objects.order_by(
'-ano').distinct().values_list('ano', 'ano') or [
- (timezone.now().year, timezone.now().year)
+ (timezone.now().year, timezone.now().year)
]
def clean(self):
@@ -989,509 +882,7 @@ class EstatisticasAcessoNormasForm(Form):
return self.cleaned_data
-class RelatorioNormasVigenciaFilterSet(django_filters.FilterSet):
-
- ano = django_filters.ChoiceFilter(required=True,
- label='Ano da Norma',
- choices=choice_anos_com_normas,
- initial=ultimo_ano_com_norma)
-
- tipo = django_filters.ChoiceFilter(required=False,
- label='Tipo Norma',
- choices=choice_tipos_normas,
- initial=0)
-
- vigencia = forms.ChoiceField(
- label=_('Vigência'),
- choices=[(True, "Vigente"), (False, "Não vigente")],
- widget=forms.RadioSelect(),
- required=True,
- initial=True)
-
- def __init__(self, *args, **kwargs):
- super(RelatorioNormasVigenciaFilterSet, self).__init__(
- *args, **kwargs)
-
- self.filters['ano'].label = 'Ano'
- self.form.fields['ano'].required = True
- self.form.fields['vigencia'] = self.vigencia
-
- row1 = to_row([('ano', 6), ('tipo', 6)])
- row2 = to_row([('vigencia', 12)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Normas por vigência.'),
- row1, row2,
- buttons, )
- )
-
- @property
- def qs(self):
- return qs_override_django_filter(self)
-
-
-class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
-
- class Meta(FilterOverridesMetaMixin):
- model = SessaoPlenaria
- fields = ['data_inicio',
- 'sessao_legislativa',
- 'tipo',
- 'legislatura']
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- self.form.fields['exibir_ordem_dia'] = forms.BooleanField(
- required=False, label='Exibir presença das Ordens do Dia')
- self.form.initial['exibir_ordem_dia'] = True
-
- self.form.fields['exibir_somente_titular'] = forms.BooleanField(
- required=False, label='Exibir somente parlamentares titulares')
- self.form.initial['exibir_somente_titular'] = False
-
- self.form.fields['exibir_somente_ativo'] = forms.BooleanField(
- required=False, label='Exibir somente parlamentares ativos')
- self.form.initial['exibir_somente_ativo'] = False
-
- self.form.fields['legislatura'].required = True
-
- self.filters['data_inicio'].label = 'Período (Inicial - Final)'
-
- tipo_sessao_ordinaria = self.filters['tipo'].queryset.filter(
- nome='Ordinária')
- if tipo_sessao_ordinaria:
- self.form.initial['tipo'] = tipo_sessao_ordinaria.first()
-
- row1 = to_row([('legislatura', 4),
- ('sessao_legislativa', 4),
- ('tipo', 4)])
- row2 = to_row([('exibir_ordem_dia', 12),
- ('exibir_somente_titular', 12),
- ('exibir_somente_ativo', 12)])
- row3 = to_row([('data_inicio', 12)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Presença dos parlamentares nas sessões plenárias'),
- row1, row2, row3, buttons, )
- )
-
- @property
- def qs(self):
- return qs_override_django_filter(self)
-
-
-class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
-
- autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
-
- @property
- def qs(self):
- parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs
- return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
-
- class Meta(FilterOverridesMetaMixin):
- model = MateriaLegislativa
- fields = ['tipo', 'tramitacao__status', 'tramitacao__data_tramitacao',
- 'tramitacao__unidade_tramitacao_local', 'tramitacao__unidade_tramitacao_destino']
-
- def __init__(self, *args, **kwargs):
- super(RelatorioHistoricoTramitacaoFilterSet, self).__init__(
- *args, **kwargs)
-
- self.filters['tipo'].label = 'Tipo de Matéria'
- self.filters['tramitacao__status'].label = _('Status')
- self.filters['tramitacao__unidade_tramitacao_local'].label = _(
- 'Unidade Local (Origem)')
- self.filters['tramitacao__unidade_tramitacao_destino'].label = _(
- 'Unidade Destino')
-
- row1 = to_row([('tramitacao__data_tramitacao', 12)])
- row2 = to_row([('tramitacao__unidade_tramitacao_local', 6),
- ('tramitacao__unidade_tramitacao_destino', 6)])
- row3 = to_row(
- [('tipo', 6),
- ('tramitacao__status', 6)])
-
- row4 = to_row([
- ('autoria__autor', 0),
- (Button('pesquisar',
- 'Pesquisar Autor',
- css_class='btn btn-primary btn-sm'), 2),
- (Button('limpar',
- 'Limpar Autor',
- css_class='btn btn-primary btn-sm'), 2)
- ])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Pesquisar'),
- row1, row2, row3, row4,
- HTML(autor_label),
- HTML(autor_modal),
- buttons, )
- )
-
-
-class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
-
- ano = django_filters.ChoiceFilter(required=False,
- label='Ano da Matéria',
- choices=choice_anos_com_materias)
-
- @property
- def qs(self):
- parent = super(RelatorioDataFimPrazoTramitacaoFilterSet, self).qs
- return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
-
- class Meta(FilterOverridesMetaMixin):
- model = MateriaLegislativa
- fields = ['tipo', 'tramitacao__unidade_tramitacao_local',
- 'tramitacao__unidade_tramitacao_destino',
- 'tramitacao__status', 'tramitacao__data_fim_prazo']
-
- def __init__(self, *args, **kwargs):
- super(RelatorioDataFimPrazoTramitacaoFilterSet, self).__init__(
- *args, **kwargs)
-
- self.filters['tipo'].label = 'Tipo de Matéria'
- self.filters[
- 'tramitacao__unidade_tramitacao_local'].label = 'Unidade Local (Origem)'
- self.filters['tramitacao__unidade_tramitacao_destino'].label = 'Unidade Destino'
- self.filters['tramitacao__status'].label = 'Status de tramitação'
-
- row1 = to_row([('ano', 12)])
- row2 = to_row([('tramitacao__data_fim_prazo', 12)])
- row3 = to_row([('tramitacao__unidade_tramitacao_local', 6),
- ('tramitacao__unidade_tramitacao_destino', 6)])
- row4 = to_row(
- [('tipo', 6),
- ('tramitacao__status', 6)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Tramitações'),
- row1, row2, row3, row4,
- buttons, )
- )
-
-
-class RelatorioReuniaoFilterSet(django_filters.FilterSet):
-
- @property
- def qs(self):
- parent = super(RelatorioReuniaoFilterSet, self).qs
- return parent.distinct().order_by('-data', 'comissao')
-
- class Meta:
- model = Reuniao
- fields = ['comissao', 'data',
- 'nome', 'tema']
-
- def __init__(self, *args, **kwargs):
- super(RelatorioReuniaoFilterSet, self).__init__(
- *args, **kwargs)
-
- row1 = to_row([('data', 12)])
- row2 = to_row(
- [('comissao', 4),
- ('nome', 4),
- ('tema', 4)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Reunião de Comissão'),
- row1, row2,
- buttons, )
- )
-
-
-class RelatorioAudienciaFilterSet(django_filters.FilterSet):
-
- @property
- def qs(self):
- parent = super(RelatorioAudienciaFilterSet, self).qs
- return parent.distinct().order_by('-data', 'tipo')
-
- class Meta:
- model = AudienciaPublica
- fields = ['tipo', 'data',
- 'nome']
-
- def __init__(self, *args, **kwargs):
- super(RelatorioAudienciaFilterSet, self).__init__(
- *args, **kwargs)
-
- row1 = to_row([('data', 12)])
- row2 = to_row(
- [('tipo', 4),
- ('nome', 4)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Audiência Pública'),
- row1, row2,
- buttons, )
- )
-
-
-class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet):
-
- materia__ano = django_filters.ChoiceFilter(required=True,
- label='Ano da Matéria',
- choices=choice_anos_com_materias)
-
- tramitacao__unidade_tramitacao_destino = django_filters.ModelChoiceFilter(
- queryset=UnidadeTramitacao.objects.all(),
- label=_('Unidade Atual'))
-
- tramitacao__status = django_filters.ModelChoiceFilter(
- queryset=StatusTramitacao.objects.all(),
- label=_('Status Atual'))
-
- materia__autores = django_filters.ModelChoiceFilter(
- label='Autor da Matéria',
- queryset=Autor.objects.all())
-
- @property
- def qs(self):
- parent = super(RelatorioMateriasTramitacaoFilterSet, self).qs
- return parent.distinct().order_by(
- '-materia__ano', 'materia__tipo', '-materia__numero'
- )
-
- class Meta:
- model = MateriaEmTramitacao
- fields = ['materia__ano', 'materia__tipo',
- 'tramitacao__unidade_tramitacao_destino',
- 'tramitacao__status', 'materia__autores']
-
- def __init__(self, *args, **kwargs):
- super(RelatorioMateriasTramitacaoFilterSet, self).__init__(
- *args, **kwargs)
-
- self.filters['materia__tipo'].label = 'Tipo de Matéria'
-
- row1 = to_row([('materia__ano', 12)])
- row2 = to_row([('materia__tipo', 12)])
- row3 = to_row([('tramitacao__unidade_tramitacao_destino', 12)])
- row4 = to_row([('tramitacao__status', 12)])
- row5 = to_row([('materia__autores', 12)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Pesquisa de Matéria em Tramitação'),
- row1, row2, row3, row4, row5,
- buttons,)
- )
-
-
-class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
-
- ano = django_filters.ChoiceFilter(required=True,
- label='Ano da Matéria',
- choices=choice_anos_com_materias)
-
- class Meta:
- model = MateriaLegislativa
- fields = ['ano']
-
- def __init__(self, *args, **kwargs):
- super(RelatorioMateriasPorAnoAutorTipoFilterSet, self).__init__(
- *args, **kwargs)
-
- row1 = to_row(
- [('ano', 12)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Pesquisa de Matéria por Ano Autor Tipo'),
- row1,
- buttons, )
- )
-
-
-class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
-
- autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
-
- @property
- def qs(self):
- parent = super().qs
- return parent.distinct().order_by('-ano', '-numero', 'tipo', 'autoria__autor', '-autoria__primeiro_autor')
-
- class Meta(FilterOverridesMetaMixin):
- model = MateriaLegislativa
- fields = ['tipo', 'data_apresentacao']
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- self.filters['tipo'].label = 'Tipo de Matéria'
-
- row1 = to_row(
- [('tipo', 12)])
- row2 = to_row(
- [('data_apresentacao', 12)])
- row3 = to_row(
- [('autoria__autor', 0),
- (Button('pesquisar',
- 'Pesquisar Autor',
- css_class='btn btn-primary btn-sm'), 2),
- (Button('limpar',
- 'Limpar Autor',
- css_class='btn btn-primary btn-sm'), 10)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Pesquisa de Matéria por Autor'),
- row1, row2,
- HTML(autor_label),
- HTML(autor_modal),
- row3,
- buttons, )
- )
-
-
class CasaLegislativaForm(FileFieldCheckMixin, ModelForm):
-
class Meta:
model = CasaLegislativa
@@ -1516,7 +907,7 @@ class CasaLegislativaForm(FileFieldCheckMixin, ModelForm):
# O campo fax foi ocultado porque não é utilizado.
'fax': forms.HiddenInput(),
# 'fax': forms.TextInput(attrs={'class': 'telefone'}),
- 'logotipo': ImageThumbnailFileInput,
+ 'logotipo': ImageThumbnailFileInput,
'informacao_geral': forms.Textarea(
attrs={'id': 'texto-rico'})
}
@@ -1555,6 +946,12 @@ class ConfiguracoesAppForm(ModelForm):
label=_('Mostrar brasão da Casa no painel?'),
required=False)
+ mostrar_voto = forms.BooleanField(
+ help_text=_('Se selecionado, exibe qual é o voto, e não apenas a indicação de que já votou, '
+ 'com a votação ainda aberta.'),
+ label=_('Mostrar voto do Parlamentar no painel durante a votação?'),
+ required=False)
+
google_recaptcha_site_key = forms.CharField(
label=AppConfig._meta.get_field(
'google_recaptcha_site_key').verbose_name,
@@ -1576,6 +973,12 @@ class ConfiguracoesAppForm(ModelForm):
max_length=256,
required=False)
+ google_analytics_id_metrica = forms.CharField(
+ label=AppConfig._meta.get_field(
+ 'google_analytics_id_metrica').verbose_name,
+ max_length=256,
+ required=False)
+
class Meta:
model = AppConfig
fields = ['documentos_administrativos',
@@ -1594,6 +997,7 @@ class ConfiguracoesAppForm(ModelForm):
'cronometro_ordem',
'cronometro_consideracoes',
'mostrar_brasao_painel',
+ 'mostrar_voto',
'receber_recibo_proposicao',
'assinatura_ata',
'estatisticas_acesso_normas',
@@ -1603,8 +1007,10 @@ class ConfiguracoesAppForm(ModelForm):
'tramitacao_documento',
'google_recaptcha_site_key',
'google_recaptcha_secret_key',
+ 'google_analytics_id_metrica',
'sapl_as_sapn',
- 'identificacao_de_documentos']
+ 'identificacao_de_documentos',
+ ]
def __init__(self, *args, **kwargs):
super(ConfiguracoesAppForm, self).__init__(*args, **kwargs)
@@ -1639,18 +1045,15 @@ class ConfiguracoesAppForm(ModelForm):
class RecuperarSenhaForm(GoogleRecapthaMixin, PasswordResetForm):
-
logger = logging.getLogger(__name__)
def __init__(self, *args, **kwargs):
-
kwargs['title_label'] = _('Insira o e-mail cadastrado com a sua conta')
kwargs['action_label'] = _('Enviar')
super().__init__(*args, **kwargs)
def clean(self):
-
super(RecuperarSenhaForm, self).clean()
email_existente = get_user_model().objects.filter(
@@ -1774,7 +1177,6 @@ class AlterarSenhaForm(Form):
class PartidoForm(FileFieldCheckMixin, ModelForm):
-
class Meta:
model = Partido
exclude = []
@@ -1817,118 +1219,6 @@ class PartidoForm(FileFieldCheckMixin, ModelForm):
return cleaned_data
-class RelatorioHistoricoTramitacaoAdmFilterSet(django_filters.FilterSet):
-
- @property
- def qs(self):
- parent = super(RelatorioHistoricoTramitacaoAdmFilterSet, self).qs
- return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
-
- class Meta(FilterOverridesMetaMixin):
- model = DocumentoAdministrativo
- fields = ['tipo', 'tramitacaoadministrativo__status',
- 'tramitacaoadministrativo__data_tramitacao',
- 'tramitacaoadministrativo__unidade_tramitacao_local',
- 'tramitacaoadministrativo__unidade_tramitacao_destino']
-
- def __init__(self, *args, **kwargs):
- super(RelatorioHistoricoTramitacaoAdmFilterSet, self).__init__(
- *args, **kwargs)
-
- self.filters['tipo'].label = 'Tipo de Documento'
- self.filters['tramitacaoadministrativo__status'].label = _('Status')
- self.filters['tramitacaoadministrativo__unidade_tramitacao_local'].label = _(
- 'Unidade Local (Origem)')
- self.filters['tramitacaoadministrativo__unidade_tramitacao_destino'].label = _(
- 'Unidade Destino')
-
- row1 = to_row([('tramitacaoadministrativo__data_tramitacao', 12)])
- row2 = to_row([('tramitacaoadministrativo__unidade_tramitacao_local', 6),
- ('tramitacaoadministrativo__unidade_tramitacao_destino', 6)])
- row3 = to_row(
- [('tipo', 6),
- ('tramitacaoadministrativo__status', 6)])
-
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_(''),
- row1, row2, row3,
- buttons, )
- )
-
-
-class RelatorioNormasPorAutorFilterSet(django_filters.FilterSet):
-
- autorianorma__autor = django_filters.CharFilter(widget=forms.HiddenInput())
-
- @property
- def qs(self):
- parent = super().qs
- return parent.distinct().filter(autorianorma__primeiro_autor=True)\
- .order_by('autorianorma__autor', '-autorianorma__primeiro_autor', 'tipo', '-ano', '-numero')
-
- class Meta(FilterOverridesMetaMixin):
- model = NormaJuridica
- fields = ['tipo', 'data']
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
-
- self.filters['tipo'].label = 'Tipo de Norma'
-
- row1 = to_row(
- [('tipo', 12)])
- row2 = to_row(
- [('data', 12)])
- row3 = to_row(
- [('autorianorma__autor', 0),
- (Button('pesquisar',
- 'Pesquisar Autor',
- css_class='btn btn-primary btn-sm'), 2),
- (Button('limpar',
- 'Limpar Autor',
- css_class='btn btn-primary btn-sm'), 10)])
- buttons = FormActions(
- *[
- HTML('''
-
-
- Gerar relatório PDF
-
- ''')
- ],
- Submit('pesquisar', _('Pesquisar'), css_class='float-right',
- onclick='return true;'),
- css_class='form-group row justify-content-between',
- )
-
- self.form.helper = SaplFormHelper()
- self.form.helper.form_method = 'GET'
- self.form.helper.layout = Layout(
- Fieldset(_('Pesquisar'),
- row1, row2,
- HTML(autor_label),
- HTML(autor_modal),
- row3,
- form_actions(label='Pesquisar'))
- )
-
-
class SaplSearchForm(ModelSearchForm):
def search(self):
diff --git a/sapl/base/management/commands/backfill_auditlog.py b/sapl/base/management/commands/backfill_auditlog.py
new file mode 100644
index 000000000..ba8bb6037
--- /dev/null
+++ b/sapl/base/management/commands/backfill_auditlog.py
@@ -0,0 +1,38 @@
+import json
+import logging
+
+from django.core.management.base import BaseCommand
+from sapl.base.models import AuditLog
+
+logger = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ def handle(self, **options):
+ print("Backfilling AuditLog JSON Field...")
+ logs = AuditLog.objects.filter(data__isnull=True)
+ error_counter = 0
+ if logs:
+ update_list = []
+ for log in logs:
+ try:
+ obj = log.object[1:-1] \
+ if log.object.startswith('[') else log.object
+ data = json.loads(obj)
+ log.data = data
+ except Exception as e:
+ error_counter += 1
+ logging.error(e)
+ log.data = None
+ else:
+ update_list.append(log)
+ if len(update_list) == 1000:
+ AuditLog.objects.bulk_update(update_list, ['data'])
+ update_list = []
+ if update_list:
+ AuditLog.objects.bulk_update(update_list, ['data'])
+ print(f"Logs backfilled: {len(logs) - error_counter}")
+ print(f"Logs with errors: {error_counter}")
+ print("Finished backfilling")
+
+
diff --git a/sapl/base/migrations/0055_appconfig_mostrar_voto.py b/sapl/base/migrations/0055_appconfig_mostrar_voto.py
new file mode 100644
index 000000000..1d4527639
--- /dev/null
+++ b/sapl/base/migrations/0055_appconfig_mostrar_voto.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.28 on 2022-10-05 18:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('base', '0054_auto_20220921_1217'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='appconfig',
+ name='mostrar_voto',
+ field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Exibir voto do Parlamentar antes de encerrar a votação?'),
+ ),
+ ]
diff --git a/sapl/base/migrations/0056_auto_20221118_1330.py b/sapl/base/migrations/0056_auto_20221118_1330.py
new file mode 100644
index 000000000..d1d6d0036
--- /dev/null
+++ b/sapl/base/migrations/0056_auto_20221118_1330.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.2.28 on 2022-11-18 16:30
+
+import django.contrib.postgres.fields.jsonb
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('base', '0055_appconfig_mostrar_voto'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='auditlog',
+ options={'ordering': ('-id', '-timestamp'), 'verbose_name': 'AuditLog', 'verbose_name_plural': 'AuditLogs'},
+ ),
+ migrations.AddField(
+ model_name='auditlog',
+ name='data',
+ field=django.contrib.postgres.fields.jsonb.JSONField(null=True, verbose_name='data'),
+ ),
+ ]
diff --git a/sapl/base/migrations/0057_appconfig_google_analytics_id_metrica.py b/sapl/base/migrations/0057_appconfig_google_analytics_id_metrica.py
new file mode 100644
index 000000000..7f6623fc6
--- /dev/null
+++ b/sapl/base/migrations/0057_appconfig_google_analytics_id_metrica.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.28 on 2023-05-29 19:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('base', '0056_auto_20221118_1330'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='appconfig',
+ name='google_analytics_id_metrica',
+ field=models.CharField(default='', max_length=256, verbose_name='ID da Métrica do Google Analytics'),
+ ),
+ ]
diff --git a/sapl/base/models.py b/sapl/base/models.py
index 8dc7b7c70..43b8ab349 100644
--- a/sapl/base/models.py
+++ b/sapl/base/models.py
@@ -233,6 +233,11 @@ class AppConfig(models.Model):
default=False,
verbose_name=_('Mostrar brasão da Casa no painel?'))
+ mostrar_voto = models.BooleanField(
+ verbose_name=_(
+ 'Exibir voto do Parlamentar antes de encerrar a votação?'),
+ choices=YES_NO_CHOICES, default=False)
+
# MÓDULO ESTATÍSTICAS DE ACESSO
estatisticas_acesso_normas = models.CharField(
max_length=1,
@@ -255,6 +260,10 @@ class AppConfig(models.Model):
verbose_name=_('Chave privada gerada pelo Google Recaptcha'),
max_length=256, default='')
+ google_analytics_id_metrica = models.CharField(
+ verbose_name=_('ID da Métrica do Google Analytics'),
+ max_length=256, default='')
+
class Meta:
verbose_name = _('Configurações da Aplicação')
verbose_name_plural = _('Configurações da Aplicação')
@@ -415,12 +424,15 @@ class AuditLog(models.Model):
db_index=True)
timestamp = models.DateTimeField(verbose_name=_('timestamp'),
db_index=True)
+ # DEPRECATED FIELD! TO BE REMOVED (EVENTUALLY)
object = models.CharField(max_length=MAX_DATA_LENGTH,
blank=True,
verbose_name=_('object'))
+ data = JSONField(null=True, verbose_name=_('data'))
object_id = models.PositiveIntegerField(verbose_name=_('object_id'),
db_index=True)
- model_name = models.CharField(max_length=100, verbose_name=_('model'),
+ model_name = models.CharField(max_length=100,
+ verbose_name=_('model'),
db_index=True)
app_name = models.CharField(max_length=100,
verbose_name=_('app'),
@@ -429,7 +441,7 @@ class AuditLog(models.Model):
class Meta:
verbose_name = _('AuditLog')
verbose_name_plural = _('AuditLogs')
- ordering = ('-id',)
+ ordering = ('-id', '-timestamp')
def __str__(self):
return "[%s] %s %s.%s %s" % (self.timestamp,
diff --git a/sapl/base/receivers.py b/sapl/base/receivers.py
index 0813ccf9a..4f5c052f5 100644
--- a/sapl/base/receivers.py
+++ b/sapl/base/receivers.py
@@ -1,23 +1,30 @@
+from datetime import datetime
import inspect
import logging
+from PyPDF4.pdf import PdfFileReader
+from asn1crypto import cms
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core import serializers
+from django.core.files.uploadedfile import InMemoryUploadedFile, UploadedFile
+from django.db.models.fields.files import FileField
from django.db.models.signals import post_delete, post_save, \
- post_migrate
+ post_migrate, pre_save, pre_migrate
from django.db.utils import DEFAULT_DB_ALIAS
from django.dispatch import receiver
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from sapl.base.email_utils import do_envia_email_tramitacao
-from sapl.base.models import AuditLog, TipoAutor, Autor
+from sapl.base.models import AuditLog, TipoAutor, Autor, Metadata
from sapl.decorators import receiver_multi_senders
from sapl.materia.models import Tramitacao
+from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.utils import get_base_url, models_with_gr_for_model
+
models_with_gr_for_autor = models_with_gr_for_model(Autor)
@@ -120,10 +127,15 @@ def audit_log_function(sender, **kwargs):
model_name = instance.__class__.__name__
app_name = instance._meta.app_label
object_id = instance.id
- data = serializers.serialize('json', [instance])
-
- if len(data) > AuditLog.MAX_DATA_LENGTH:
- data = data[:AuditLog.MAX_DATA_LENGTH]
+ try:
+ import json
+ # [1:-1] below removes the surrounding square brackets
+ str_data = serializers.serialize('json', [instance])[1:-1]
+ data = json.loads(str_data)
+ except:
+ # old version capped string at AuditLog.MAX_DATA_LENGTH
+ # so there can be invalid json fields in Prod.
+ data = None
if user:
username = user.username
@@ -136,7 +148,8 @@ def audit_log_function(sender, **kwargs):
app_name=app_name,
timestamp=timezone.now(),
object_id=object_id,
- object=data)
+ object='',
+ data=data)
except Exception as e:
logger.error('Error saving auditing log object')
logger.error(e)
@@ -186,3 +199,274 @@ def cria_models_tipo_autor(app_config=None, verbosity=2, interactive=True,
post_migrate.connect(receiver=cria_models_tipo_autor)
+
+
+def signed_files_extraction_function(sender, instance, **kwargs):
+
+ def run_signed_name_and_date_via_fields(fields):
+ signs = []
+
+ for key, field in fields.items():
+
+ if '/FT' not in field and field['/FT'] != '/Sig':
+ continue
+ if '/V' not in field:
+ continue
+
+ content_sign = field['/V']['/Contents']
+ nome = 'Nome do assinante não localizado.'
+ oname = ''
+ try:
+ info = cms.ContentInfo.load(content_sign)
+ signed_data = info['content']
+ oun_old = []
+ for cert in signed_data['certificates']:
+ subject = cert.native['tbs_certificate']['subject']
+ issuer = cert.native['tbs_certificate']['issuer']
+ oname = issuer.get('organization_name', '')
+
+ if oname == 'Gov-Br':
+ nome = subject['common_name'].split(':')[0]
+ continue
+
+ oun = subject['organizational_unit_name']
+
+ if isinstance(oun, str):
+ continue
+
+ if len(oun) > len(oun_old):
+ oun_old = oun
+ nome = subject['common_name'].split(':')[0]
+
+ if oun and isinstance(oun, list) and len(oun) == 4:
+ oname += ' - ' + oun[3]
+ break
+
+ except:
+ if '/Name' in field['/V']:
+ nome = field['/V']['/Name']
+
+ fd = None
+ try:
+ data = str(field['/V']['/M'])
+
+ if 'D:' not in data:
+ data = None
+ else:
+ if not data.endswith('Z'):
+ data = data.replace('Z', '+')
+ data = data.replace("'", '')
+
+ fd = datetime.strptime(data[2:], '%Y%m%d%H%M%S%z')
+ except:
+ pass
+
+ signs.append((nome, [fd, oname]))
+
+ return signs
+
+ def run_signed_name_and_date_extract(file):
+ signs = []
+ fields = {}
+ pdfdata = file.read()
+
+ # se não tem byterange então não é assinado
+ byterange = []
+ n = -1
+ while True:
+ n = pdfdata.find(b"/ByteRange", n + 1)
+ if n == -1:
+ break
+ byterange.append(n)
+
+ if not byterange:
+ return signs
+
+ # tenta extrair via /Fields
+ try:
+ pdf = PdfFileReader(file)
+ fields = pdf.getFields()
+ except Exception as e:
+ try:
+ pdf = PdfFileReader(file, strict=False)
+ fields = pdf.getFields()
+ except Exception as ee:
+ fields = ee
+
+ try:
+ # se a extração via /Fields ocorrer sem erros e forem capturadas
+ # tantas assinaturas quanto byteranges
+ if isinstance(fields, dict):
+ signs = run_signed_name_and_date_via_fields(fields)
+ if len(signs) == len(byterange):
+ return signs
+
+ for n in byterange:
+
+ start = pdfdata.find(b"[", n)
+ stop = pdfdata.find(b"]", start)
+ assert n != -1 and start != -1 and stop != -1
+ n += 1
+
+ br = [int(i, 10) for i in pdfdata[start + 1: stop].split()]
+ contents = pdfdata[br[0] + br[1] + 1: br[2] - 1]
+ bcontents = bytes.fromhex(contents.decode("utf8"))
+ data1 = pdfdata[br[0]: br[0] + br[1]]
+ data2 = pdfdata[br[2]: br[2] + br[3]]
+ #signedData = data1 + data2
+
+ nome = 'Nome do assinante não localizado.'
+ oname = ''
+ try:
+ info = cms.ContentInfo.load(bcontents)
+ signed_data = info['content']
+
+ oun_old = []
+ for cert in signed_data['certificates']:
+ subject = cert.native['tbs_certificate']['subject']
+ issuer = cert.native['tbs_certificate']['issuer']
+ oname = issuer.get('organization_name', '')
+
+ if oname == 'Gov-Br':
+ nome = subject['common_name'].split(':')[0]
+ continue
+
+ oun = subject['organizational_unit_name']
+
+ if isinstance(oun, str):
+ continue
+
+ if len(oun) > len(oun_old):
+ oun_old = oun
+ nome = subject['common_name'].split(':')[0]
+
+ if oun and isinstance(oun, list) and len(oun) == 4:
+ oname += ' - ' + oun[3]
+ break
+
+ except Exception as e:
+ pass
+
+ fd = None
+ signs.append((nome, [fd, oname]))
+
+ except Exception as e:
+ pass
+
+ return signs
+
+ def signed_name_and_date_extract(file):
+
+ try:
+ signs = run_signed_name_and_date_extract(file)
+ except:
+ return {}
+
+ signs = sorted(signs, key=lambda sign: (
+ sign[0], sign[1][1], sign[1][0]))
+
+ signs_dict = {}
+
+ for s in signs:
+ if s[0] not in signs_dict or 'ICP' in s[1][1] and 'ICP' not in signs_dict[s[0]][1]:
+ signs_dict[s[0]] = s[1]
+
+ signs = sorted(signs_dict.items(), key=lambda sign: (
+ sign[0], sign[1][1], sign[1][0]))
+
+ sr = []
+
+ for s in signs:
+ tt = s[0].title().split(' ')
+ for idx, t in enumerate(tt):
+ if t in ('Dos', 'De', 'Da', 'Do', 'Das', 'E'):
+ tt[idx] = t.lower()
+ sr.append((' '.join(tt), s[1]))
+
+ signs = sr
+
+ meta_signs = {
+ 'autores': [],
+ 'admin': []
+ }
+
+ for s in signs:
+ # cn = # settings.CERT_PRIVATE_KEY_NAME
+ #meta_signs['admin' if s[0] == cn else 'autores'].append(s)
+ meta_signs['autores'].append(s)
+ return meta_signs
+
+ def filefield_from_model(m):
+ fields = m._meta.get_fields()
+ fields = tuple(map(lambda f: f.name, filter(
+ lambda x: isinstance(x, FileField), fields)))
+ return fields
+
+ FIELDFILE_NAME = filefield_from_model(instance)
+
+ if not FIELDFILE_NAME:
+ return
+
+ try:
+ md = Metadata.objects.get(
+ content_type=ContentType.objects.get_for_model(
+ instance._meta.model),
+ object_id=instance.id,).metadata
+ except:
+ md = {}
+
+ for fn in FIELDFILE_NAME: # fn -> field_name
+ ff = getattr(instance, fn) # ff -> file_field
+
+ if md and 'signs' in md and \
+ fn in md['signs'] and\
+ md['signs'][fn]:
+ md['signs'][fn] = {}
+
+ if not ff:
+ continue
+
+ try:
+ file = ff.file.file
+ meta_signs = {}
+ if not isinstance(ff.file, UploadedFile):
+ absolute_path = ff.path
+ with open(absolute_path, "rb") as file:
+ meta_signs = signed_name_and_date_extract(file)
+ file.close()
+ else:
+ file.seek(0)
+ meta_signs = signed_name_and_date_extract(file)
+
+ if not meta_signs or not meta_signs['autores'] and not meta_signs['admin']:
+ continue
+
+ if not md:
+ md = {'signs': {}}
+
+ if 'signs' not in md:
+ md['signs'] = {}
+
+ md['signs'][fn] = meta_signs
+ except Exception as e:
+ # print(e)
+ pass
+
+ if md:
+ metadata = Metadata.objects.get_or_create(
+ content_type=ContentType.objects.get_for_model(
+ instance._meta.model),
+ object_id=instance.id,)
+ metadata[0].metadata = md
+ metadata[0].save()
+
+
+@receiver(pre_save, dispatch_uid='signed_files_extraction_pre_save_signal')
+def signed_files_extraction_pre_save_signal(sender, instance, **kwargs):
+
+ signed_files_extraction_function(sender, instance, **kwargs)
+
+
+@receiver(pre_migrate, dispatch_uid='disconnect_signals_pre_migrate')
+def disconnect_signals_pre_migrate(*args, **kwargs):
+ pre_save.disconnect(dispatch_uid='signed_files_extraction_pre_save_signal')
diff --git a/sapl/base/templatetags/common_tags.py b/sapl/base/templatetags/common_tags.py
index f5b9c2c36..7cd751d1d 100644
--- a/sapl/base/templatetags/common_tags.py
+++ b/sapl/base/templatetags/common_tags.py
@@ -2,6 +2,7 @@ import re
from django import template
from django.template.defaultfilters import stringfilter
+from django.utils.dateparse import parse_datetime as django_parse_datetime
from django.utils.safestring import mark_safe
from webpack_loader import utils
@@ -10,7 +11,7 @@ from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, Proposic
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Filiacao
from sapl.sessao.models import SessaoPlenaria
-from sapl.utils import filiacao_data, SEPARADOR_HASH_PROPOSICAO
+from sapl.utils import filiacao_data, SEPARADOR_HASH_PROPOSICAO, is_report_allowed
register = template.Library()
@@ -29,6 +30,17 @@ def define(arg):
return arg
+@register.simple_tag
+def describe_operation(value):
+ if value == "C":
+ return "Criar"
+ elif value == "D":
+ return "Apagar"
+ elif value == "U":
+ return "Atualizar"
+ return ""
+
+
@register.simple_tag
def field_verbose_name(instance, field_name):
return instance._meta.get_field(field_name).verbose_name
@@ -51,6 +63,25 @@ def model_verbose_name_plural(class_name):
model = get_class(class_name)
return model._meta.verbose_name_plural
+
+@register.filter
+def obfuscate_value(value, key):
+ if key in ["hash", "google_recaptcha_secret_key", "password", "google_recaptcha_site_key", "hash_code"]:
+ return "***************"
+ return value
+
+
+@register.filter
+def desc_operation(value):
+ if value == "C":
+ return "Criado"
+ elif value == "D":
+ return "Excluido"
+ elif value == "U":
+ return "Atualizado"
+ return ""
+
+
@register.filter
def format_user(user):
if user.first_name:
@@ -58,6 +89,7 @@ def format_user(user):
else:
return user.username
+
@register.filter
def meta_model_value(instance, attr):
try:
@@ -368,3 +400,12 @@ def dont_break_out(value):
_safe = mark_safe(_safe)
return _safe
+
+@register.filter(expects_localtime=True)
+def parse_datetime(value):
+ return django_parse_datetime(value)
+
+
+@register.filter
+def is_report_visible(request, url_path=None):
+ return is_report_allowed(request, url_path)
\ No newline at end of file
diff --git a/sapl/base/urls.py b/sapl/base/urls.py
index 058264049..6733a25ec 100644
--- a/sapl/base/urls.py
+++ b/sapl/base/urls.py
@@ -3,31 +3,22 @@ import os
from django.conf.urls import include, url
from django.contrib.auth import views
from django.contrib.auth.decorators import permission_required
-
from django.views.generic.base import RedirectView, TemplateView
from sapl.base.views import (AutorCrud, ConfirmarEmailView, TipoAutorCrud, get_estatistica,
RecuperarSenhaEmailView, RecuperarSenhaFinalizadoView,
- RecuperarSenhaConfirmaView, RecuperarSenhaCompletoView, RelatorioMateriaAnoAssuntoView,
- IndexView, UserCrud)
+ RecuperarSenhaConfirmaView, RecuperarSenhaCompletoView, IndexView, UserCrud)
from sapl.settings import MEDIA_URL, LOGOUT_REDIRECT_URL
-
from .apps import AppConfig
-from .forms import LoginForm
from .views import (LoginSapl, AlterarSenha, AppConfigCrud, CasaLegislativaCrud,
- HelpTopicView, LogotipoView, RelatorioAtasView,
- RelatorioAudienciaView, RelatorioDataFimPrazoTramitacaoView, RelatorioHistoricoTramitacaoView,
- RelatorioMateriasPorAnoAutorTipoView, RelatorioMateriasPorAutorView,
- RelatorioMateriasTramitacaoView, RelatorioPresencaSessaoView, RelatorioReuniaoView, SaplSearchView,
- RelatorioNormasPublicadasMesView, RelatorioNormasVigenciaView,
- EstatisticasAcessoNormas, RelatoriosListView, ListarInconsistenciasView,
+ HelpTopicView, LogotipoView, PesquisarAuditLogView,
+ SaplSearchView,
+ ListarInconsistenciasView,
ListarProtocolosDuplicadosView, ListarProtocolosComMateriasView, ListarMatProtocoloInexistenteView,
ListarParlamentaresDuplicadosView, ListarFiliacoesSemDataFiliacaoView,
ListarMandatoSemDataInicioView, ListarParlMandatosIntersecaoView, ListarParlFiliacoesIntersecaoView,
ListarAutoresDuplicadosView, ListarBancadaComissaoAutorExternoView, ListarLegislaturaInfindavelView,
- ListarAnexadasCiclicasView, ListarAnexadosCiclicosView, pesquisa_textual,
- RelatorioHistoricoTramitacaoAdmView, RelatorioDocumentosAcessoriosView, RelatorioNormasPorAutorView)
-
+ ListarAnexadasCiclicasView, ListarAnexadosCiclicosView, pesquisa_textual)
app_name = AppConfig.name
@@ -68,53 +59,6 @@ urlpatterns = [
name="casa_legislativa"),
url(r'^sistema/app-config/', include(AppConfigCrud.get_urls())),
- # TODO mover estas telas para a app 'relatorios'
- url(r'^sistema/relatorios/$',
- RelatoriosListView.as_view(), name='relatorios_list'),
- url(r'^sistema/relatorios/materia-por-autor$',
- RelatorioMateriasPorAutorView.as_view(), name='materia_por_autor'),
- url(r'^sistema/relatorios/relatorio-por-mes$',
- RelatorioNormasPublicadasMesView.as_view(), name='normas_por_mes'),
- url(r'^sistema/relatorios/relatorio-por-vigencia$',
- RelatorioNormasVigenciaView.as_view(), name='normas_por_vigencia'),
- url(r'^sistema/relatorios/estatisticas-acesso$',
- EstatisticasAcessoNormas.as_view(), name='estatisticas_acesso'),
- url(r'^sistema/relatorios/materia-por-ano-autor-tipo$',
- RelatorioMateriasPorAnoAutorTipoView.as_view(),
- name='materia_por_ano_autor_tipo'),
- url(r'^sistema/relatorios/materia-por-tramitacao$',
- RelatorioMateriasTramitacaoView.as_view(),
- name='materia_por_tramitacao'),
- url(r'^sistema/relatorios/materia-por-assunto$',
- RelatorioMateriaAnoAssuntoView.as_view(),
- name='materia_por_ano_assunto'),
- url(r'^sistema/relatorios/historico-tramitacoes$',
- RelatorioHistoricoTramitacaoView.as_view(),
- name='historico_tramitacoes'),
- url(r'^sistema/relatorios/data-fim-prazo-tramitacoes$',
- RelatorioDataFimPrazoTramitacaoView.as_view(),
- name='data_fim_prazo_tramitacoes'),
- url(r'^sistema/relatorios/presenca$',
- RelatorioPresencaSessaoView.as_view(),
- name='presenca_sessao'),
- url(r'^sistema/relatorios/atas$',
- RelatorioAtasView.as_view(),
- name='atas'),
- url(r'^sistema/relatorios/reuniao$',
- RelatorioReuniaoView.as_view(),
- name='reuniao'),
- url(r'^sistema/relatorios/audiencia$',
- RelatorioAudienciaView.as_view(),
- name='audiencia'),
- url(r'^sistema/relatorios/historico-tramitacoesadm$',
- RelatorioHistoricoTramitacaoAdmView.as_view(),
- name='historico_tramitacoes_adm'),
- url(r'^sistema/relatorios/documentos_acessorios$',
- RelatorioDocumentosAcessoriosView.as_view(),
- name='relatorio_documentos_acessorios'),
- url(r'^sistema/relatorios/normas-por-autor$',
- RelatorioNormasPorAutorView.as_view(), name='normas_por_autor'),
-
url(r'^email/validate/(?P[0-9A-Za-z_\-]+)/'
'(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})$',
ConfirmarEmailView.as_view(), name='confirmar_email'),
@@ -179,6 +123,8 @@ urlpatterns = [
url(r'^sistema/search/', SaplSearchView(), name='haystack_search'),
+ url(r'^sistema/auditlog/$', PesquisarAuditLogView.as_view(), name='pesquisar_auditlog'),
+
# Folhas XSLT e extras referenciadas por documentos migrados do sapl 2.5
url(r'^(sapl/)?XSLT/HTML/(?P.*)$', RedirectView.as_view(
url=os.path.join(MEDIA_URL, 'sapl/public/XSLT/HTML/%(path)s'),
diff --git a/sapl/base/views.py b/sapl/base/views.py
index 035951e40..dd8f7ef27 100644
--- a/sapl/base/views.py
+++ b/sapl/base/views.py
@@ -1,10 +1,8 @@
-from collections import OrderedDict
import collections
-import datetime
+import collections
import itertools
import logging
import os
-import re
from django.apps.registry import apps
from django.contrib import messages
@@ -14,11 +12,9 @@ from django.contrib.auth.models import Group
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.views import (PasswordResetView, PasswordResetConfirmView, PasswordResetCompleteView,
PasswordResetDoneView)
-from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError
+from django.core.exceptions import PermissionDenied, ValidationError
from django.core.mail import send_mail
-from django.db import connection
-from django.db.models import Count, Q, Max, F
-from django.forms.utils import ErrorList
+from django.db.models import Count, Q, Max
from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect
from django.template import TemplateDoesNotExist
@@ -37,46 +33,26 @@ from haystack.views import SearchView
from ratelimit.decorators import ratelimit
from sapl import settings
-from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
-from sapl.base.forms import (AutorForm, TipoAutorForm, AutorFilterSet, RecuperarSenhaForm,
- NovaSenhaForm, UserAdminForm,
- OperadorAutorForm, LoginForm, SaplSearchForm)
-from sapl.base.models import Autor, TipoAutor, OperadorAutor
-from sapl.comissoes.models import Comissao, Reuniao
-from sapl.crud.base import CrudAux, make_pagination, Crud,\
- ListWithSearchForm, MasterDetailCrud
-from sapl.materia.models import (Anexada, Autoria, DocumentoAcessorio, MateriaEmTramitacao, MateriaLegislativa,
- Proposicao, StatusTramitacao, TipoDocumento, TipoMateriaLegislativa, UnidadeTramitacao,
- MateriaAssunto)
-from sapl.norma.models import NormaJuridica, TipoNormaJuridica,\
- NormaEstatisticas, ViewNormasEstatisticas
+from sapl.base.forms import (AutorForm, TipoAutorForm, RecuperarSenhaForm,
+ NovaSenhaForm, UserAdminForm, AuditLogFilterSet,
+ LoginForm, SaplSearchForm)
+from sapl.base.models import AuditLog, Autor, TipoAutor
+from sapl.comissoes.models import Comissao
+from sapl.crud.base import CrudAux, make_pagination, Crud, \
+ ListWithSearchForm
+from sapl.materia.models import (Anexada, MateriaLegislativa,
+ Proposicao)
+from sapl.norma.models import NormaJuridica, ViewNormasEstatisticas
from sapl.parlamentares.models import (
- Filiacao, Legislatura, Mandato, Parlamentar, SessaoLegislativa)
-from sapl.protocoloadm.models import (Anexado, DocumentoAdministrativo, Protocolo, StatusTramitacaoAdministrativo,
- TipoDocumentoAdministrativo)
-from sapl.relatorios.views import (relatorio_materia_em_tramitacao, relatorio_materia_por_autor,
- relatorio_materia_por_ano_autor, relatorio_presenca_sessao,
- relatorio_historico_tramitacao, relatorio_fim_prazo_tramitacao, relatorio_atas,
- relatorio_audiencia, relatorio_normas_mes, relatorio_normas_vigencia,
- relatorio_historico_tramitacao_adm, relatorio_reuniao,
- relatorio_estatisticas_acesso_normas, relatorio_normas_por_autor,
- relatorio_documento_acessorio)
-from sapl.sessao.models import (
- Bancada, PresencaOrdemDia, SessaoPlenaria, SessaoPlenariaPresenca, TipoSessaoPlenaria)
+ Filiacao, Legislatura, Mandato, Parlamentar)
+from sapl.protocoloadm.models import (Anexado, Protocolo)
+from sapl.relatorios.views import (relatorio_estatisticas_acesso_normas)
+from sapl.sessao.models import (Bancada, SessaoPlenaria)
from sapl.settings import EMAIL_SEND_USER
-from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured, parlamentares_ativos,
- SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, num_materias_por_tipo,
- google_recaptcha_configured, sapl_as_sapn,
- groups_remove_user, groups_add_user, get_client_ip)
-
-from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, RelatorioAtasFilterSet,
- RelatorioAudienciaFilterSet, RelatorioDataFimPrazoTramitacaoFilterSet,
- RelatorioHistoricoTramitacaoFilterSet, RelatorioMateriasPorAnoAutorTipoFilterSet,
- RelatorioMateriasPorAutorFilterSet, RelatorioMateriasTramitacaoFilterSet,
- RelatorioPresencaSessaoFilterSet, RelatorioReuniaoFilterSet,
- RelatorioNormasMesFilterSet, RelatorioNormasVigenciaFilterSet, EstatisticasAcessoNormasForm,
- RelatorioHistoricoTramitacaoAdmFilterSet, RelatorioDocumentosAcessoriosFilterSet,
- RelatorioNormasPorAutorFilterSet)
+from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured,
+ SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, google_recaptcha_configured, sapl_as_sapn,
+ get_client_ip)
+from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, EstatisticasAcessoNormasForm)
from .models import AppConfig, CasaLegislativa
@@ -240,15 +216,15 @@ class AutorCrud(CrudAux):
url_base = full_url[:full_url.find('sistema') - 1]
mensagem = (
- "Este e-mail foi utilizado para fazer cadastro no " +
- "SAPL com o perfil de Autor. Agora você pode " +
- "criar/editar/enviar Proposições.\n" +
- "Seu nome de usuário é: " +
- self.request.POST['username'] + "\n"
- "Caso você não tenha feito este cadastro, por favor " +
- "ignore esta mensagem. Caso tenha, clique " +
- "no link abaixo\n" + url_base +
- reverse('sapl.base:confirmar_email', kwargs=kwargs))
+ "Este e-mail foi utilizado para fazer cadastro no " +
+ "SAPL com o perfil de Autor. Agora você pode " +
+ "criar/editar/enviar Proposições.\n" +
+ "Seu nome de usuário é: " +
+ self.request.POST['username'] + "\n"
+ "Caso você não tenha feito este cadastro, por favor " +
+ "ignore esta mensagem. Caso tenha, clique " +
+ "no link abaixo\n" + url_base +
+ reverse('sapl.base:confirmar_email', kwargs=kwargs))
remetente = settings.EMAIL_SEND_USER
destinatario = [user.email]
send_mail(assunto, mensagem, remetente, destinatario,
@@ -337,855 +313,6 @@ class AutorCrud(CrudAux):
return qs.distinct('nome', 'id').order_by('nome', 'id')
-class RelatoriosListView(TemplateView):
- template_name = 'base/relatorios_list.html'
-
- def get_context_data(self, **kwargs):
- context = super(TemplateView, self).get_context_data(**kwargs)
- estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas
- context['estatisticas_acesso_normas'] = True if estatisticas_acesso_normas == 'S' else False
-
- return context
-
-
-class RelatorioMixin:
- def get(self, request, *args, **kwargs):
- super(RelatorioMixin, self).get(request)
-
- is_relatorio = request.GET.get('relatorio')
- context = self.get_context_data(filter=self.filterset)
-
- if is_relatorio:
- return self.relatorio(request, context)
- else:
- return self.render_to_response(context)
-
-
-class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView):
- model = DocumentoAcessorio
- filterset_class = RelatorioDocumentosAcessoriosFilterSet
- template_name = 'base/RelatorioDocumentosAcessorios_filter.html'
- relatorio = relatorio_documento_acessorio
-
- def get_context_data(self, **kwargs):
- context = super(
- RelatorioDocumentosAcessoriosView, self
- ).get_context_data(**kwargs)
-
- context['title'] = _('Documentos Acessórios das Matérias Legislativas')
-
- if not self.filterset.form.is_valid():
- return context
-
- query_dict = self.request.GET.copy()
- context['show_results'] = show_results_filter_set(query_dict)
-
- context['tipo_documento'] = str(
- TipoDocumento.objects.get(pk=self.request.GET['tipo'])
- )
-
- tipo_materia = self.request.GET['materia__tipo']
- if tipo_materia:
- context['tipo_materia'] = str(
- TipoMateriaLegislativa.objects.get(pk=tipo_materia)
- )
- else:
- context['tipo_materia'] = "Não selecionado"
-
- data_inicial = self.request.GET['data_0']
- data_final = self.request.GET['data_1']
- if not data_inicial:
- data_inicial = "Data Inicial não definida"
- if not data_final:
- data_final = "Data Final não definida"
- context['periodo'] = (
- data_inicial + ' - ' + data_final
- )
-
- return context
-
-
-class RelatorioAtasView(RelatorioMixin, FilterView):
- model = SessaoPlenaria
- filterset_class = RelatorioAtasFilterSet
- template_name = 'base/RelatorioAtas_filter.html'
- relatorio = relatorio_atas
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['title'] = _('Atas das Sessões Plenárias')
-
- # Verifica se os campos foram preenchidos
- if not self.filterset.form.is_valid():
- return context
-
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
- context['periodo'] = (
- self.request.GET['data_inicio_0'] +
- ' - ' + self.request.GET['data_inicio_1'])
-
- return context
-
-
-class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
- logger = logging.getLogger(__name__)
- model = SessaoPlenaria
- filterset_class = RelatorioPresencaSessaoFilterSet
- template_name = 'base/RelatorioPresencaSessao_filter.html'
- relatorio = relatorio_presenca_sessao
-
- def get_context_data(self, **kwargs):
-
- context = super().get_context_data(**kwargs)
- context['title'] = _('Presença dos parlamentares nas sessões')
-
- # Verifica se os campos foram preenchidos
- if not self.filterset.form.is_valid():
- return context
-
- cd = self.filterset.form.cleaned_data
- if not cd['data_inicio'] and not cd['sessao_legislativa'] \
- and not cd['legislatura']:
- msg = _(
- "Formulário inválido! Preencha pelo menos algum dos campos Período, Legislatura ou Sessão Legislativa.")
- messages.error(self.request, msg)
- return context
-
- # Caso a data tenha sido preenchida, verifica se foi preenchida
- # corretamente
- if self.request.GET.get('data_inicio_0') and not self.request.GET.get('data_inicio_1'):
- msg = _("Formulário inválido! Preencha a data do Período Final.")
- messages.error(self.request, msg)
- return context
-
- if not self.request.GET.get('data_inicio_0') and self.request.GET.get('data_inicio_1'):
- msg = _("Formulário inválido! Preencha a data do Período Inicial.")
- messages.error(self.request, msg)
- return context
-
- param0 = {}
-
- legislatura_pk = self.request.GET.get('legislatura')
- if legislatura_pk:
- param0['sessao_plenaria__legislatura_id'] = legislatura_pk
- legislatura = Legislatura.objects.get(id=legislatura_pk)
- context['legislatura'] = legislatura
-
- sessao_legislativa_pk = self.request.GET.get('sessao_legislativa')
- if sessao_legislativa_pk:
- param0['sessao_plenaria__sessao_legislativa_id'] = sessao_legislativa_pk
- sessao_legislativa = SessaoLegislativa.objects.get(
- id=sessao_legislativa_pk)
- context['sessao_legislativa'] = sessao_legislativa
-
- tipo_sessao_plenaria_pk = self.request.GET.get('tipo')
- context['tipo'] = ''
- if tipo_sessao_plenaria_pk:
- param0['sessao_plenaria__tipo_id'] = tipo_sessao_plenaria_pk
- context['tipo'] = TipoSessaoPlenaria.objects.get(
- id=tipo_sessao_plenaria_pk)
-
- _range = []
-
- if ('data_inicio_0' in self.request.GET) and self.request.GET['data_inicio_0'] and \
- ('data_inicio_1' in self.request.GET) and self.request.GET['data_inicio_1']:
- where = context['object_list'].query.where
- _range = where.children[0].rhs
-
- elif legislatura_pk and not sessao_legislativa_pk:
- _range = [legislatura.data_inicio, legislatura.data_fim]
-
- elif sessao_legislativa_pk:
- _range = [sessao_legislativa.data_inicio,
- sessao_legislativa.data_fim]
-
- param0.update({'sessao_plenaria__data_inicio__range': _range})
-
- # Parlamentares com Mandato no intervalo de tempo (Ativos)
- parlamentares_qs = parlamentares_ativos(
- _range[0], _range[1]).order_by('nome_parlamentar')
- parlamentares_id = parlamentares_qs.values_list('id', flat=True)
-
- # Presenças de cada Parlamentar em Sessões
- presenca_sessao = SessaoPlenariaPresenca.objects.filter(
- **param0).values_list('parlamentar_id').annotate(sessao_count=Count('id'))
-
- # Presenças de cada Ordem do Dia
- presenca_ordem = PresencaOrdemDia.objects.filter(
- **param0).values_list('parlamentar_id').annotate(sessao_count=Count('id'))
-
- total_ordemdia = PresencaOrdemDia.objects.filter(
- **param0).distinct('sessao_plenaria__id').order_by('sessao_plenaria__id').count()
-
- total_sessao = context['object_list'].count()
-
- username = self.request.user.username
-
- context['exibir_somente_titular'] = self.request.GET.get(
- 'exibir_somente_titular') == 'on'
- context['exibir_somente_ativo'] = self.request.GET.get(
- 'exibir_somente_ativo') == 'on'
-
- # Completa o dicionario as informacoes parlamentar/sessao/ordem
- parlamentares_presencas = []
- for p in parlamentares_qs:
- parlamentar = {}
- m = p.mandato_set.filter(Q(data_inicio_mandato__lte=_range[0], data_fim_mandato__gte=_range[1]) |
- Q(data_inicio_mandato__lte=_range[0], data_fim_mandato__isnull=True) |
- Q(data_inicio_mandato__gte=_range[0], data_fim_mandato__lte=_range[1]) |
- # mandato suplente
- Q(data_inicio_mandato__gte=_range[0], data_fim_mandato__lte=_range[1]))
-
- m = m.last()
-
- if not context['exibir_somente_titular'] and not context['exibir_somente_ativo']:
- parlamentar = {
- 'parlamentar': p,
- 'titular': m.titular if m else False,
- 'sessao_porc': 0,
- 'ordemdia_porc': 0
- }
- elif context['exibir_somente_titular'] and not context['exibir_somente_ativo']:
- if m and m.titular:
- parlamentar = {
- 'parlamentar': p,
- 'titular': m.titular if m else False,
- 'sessao_porc': 0,
- 'ordemdia_porc': 0
- }
- else:
- continue
- elif not context['exibir_somente_titular'] and context['exibir_somente_ativo']:
- if p.ativo:
- parlamentar = {
- 'parlamentar': p,
- 'titular': m.titular if m else False,
- 'sessao_porc': 0,
- 'ordemdia_porc': 0
- }
- else:
- continue
- elif context['exibir_somente_titular'] and context['exibir_somente_ativo']:
- if m and m.titular and p.ativo:
- parlamentar = {
- 'parlamentar': p,
- 'titular': m.titular if m else False,
- 'sessao_porc': 0,
- 'ordemdia_porc': 0
- }
- else:
- continue
- else:
- continue
-
- try:
- self.logger.debug(
- F'user={username}. Tentando obter presença do parlamentar (pk={p.id}).')
- sessao_count = presenca_sessao.get(parlamentar_id=p.id)[1]
- except ObjectDoesNotExist as e:
- self.logger.error(
- F'user={username}. Erro ao obter presença do parlamentar (pk={p.id}). Definido como 0. {str(e)}')
- sessao_count = 0
- try:
- # Presenças de cada Ordem do Dia
- self.logger.info(
- F'user={username}. Tentando obter PresencaOrdemDia para o parlamentar pk={p.id}.')
- ordemdia_count = presenca_ordem.get(parlamentar_id=p.id)[1]
- except ObjectDoesNotExist:
- self.logger.error(
- F'user={username}. Erro ao obter PresencaOrdemDia para o parlamentar pk={p.id}. Definido como 0.')
- ordemdia_count = 0
-
- parlamentar.update({
- 'sessao_count': sessao_count,
- 'ordemdia_count': ordemdia_count
- })
-
- if total_sessao != 0:
- parlamentar.update({'sessao_porc': round(
- sessao_count * 100 / total_sessao, 2)})
- if total_ordemdia != 0:
- parlamentar.update({'ordemdia_porc': round(
- ordemdia_count * 100 / total_ordemdia, 2)})
-
- parlamentares_presencas.append(parlamentar)
-
- context['date_range'] = _range
- context['total_ordemdia'] = total_ordemdia
- context['total_sessao'] = context['object_list'].count()
- context['parlamentares'] = parlamentares_presencas
- context['periodo'] = f"{self.request.GET['data_inicio_0']} - {self.request.GET['data_inicio_1']}"
- context['sessao_legislativa'] = ''
- context['legislatura'] = ''
- context['exibir_ordem'] = self.request.GET.get(
- 'exibir_ordem_dia') == 'on'
-
- if sessao_legislativa_pk:
- context['sessao_legislativa'] = SessaoLegislativa.objects.get(
- id=sessao_legislativa_pk)
- if legislatura_pk:
- context['legislatura'] = Legislatura.objects.get(id=legislatura_pk)
- # =====================================================================
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
-
- return context
-
-
-class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView):
- model = MateriaLegislativa
- filterset_class = RelatorioHistoricoTramitacaoFilterSet
- template_name = 'base/RelatorioHistoricoTramitacao_filter.html'
- relatorio = relatorio_historico_tramitacao
-
- def get_context_data(self, **kwargs):
- context = super(RelatorioHistoricoTramitacaoView,
- self).get_context_data(**kwargs)
- context['title'] = _(
- 'Histórico de Tramitações de Matérias Legislativas')
- if not self.filterset.form.is_valid():
- return context
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
- context['data_tramitacao'] = (self.request.GET['tramitacao__data_tramitacao_0'] + ' - ' +
- self.request.GET['tramitacao__data_tramitacao_1'])
- if self.request.GET['tipo']:
- tipo = self.request.GET['tipo']
- context['tipo'] = (
- str(TipoMateriaLegislativa.objects.get(id=tipo)))
- else:
- context['tipo'] = ''
-
- if self.request.GET['tramitacao__status']:
- tramitacao_status = self.request.GET['tramitacao__status']
- context['tramitacao__status'] = (
- str(StatusTramitacao.objects.get(id=tramitacao_status)))
- else:
- context['tramitacao__status'] = ''
-
- if self.request.GET['tramitacao__unidade_tramitacao_local']:
- context['tramitacao__unidade_tramitacao_local'] = \
- (str(UnidadeTramitacao.objects.get(
- id=self.request.GET['tramitacao__unidade_tramitacao_local'])))
- else:
- context['tramitacao__unidade_tramitacao_local'] = ''
-
- if self.request.GET['tramitacao__unidade_tramitacao_destino']:
- context['tramitacao__unidade_tramitacao_destino'] = \
- (str(UnidadeTramitacao.objects.get(
- id=self.request.GET['tramitacao__unidade_tramitacao_destino'])))
- else:
- context['tramitacao__unidade_tramitacao_destino'] = ''
-
- if self.request.GET['autoria__autor']:
- context['autoria__autor'] = \
- (str(Autor.objects.get(
- id=self.request.GET['autoria__autor'])))
- else:
- context['autoria__autor'] = ''
-
- return context
-
-
-class RelatorioDataFimPrazoTramitacaoView(RelatorioMixin, FilterView):
- model = MateriaLegislativa
- filterset_class = RelatorioDataFimPrazoTramitacaoFilterSet
- template_name = 'base/RelatorioDataFimPrazoTramitacao_filter.html'
- relatorio = relatorio_fim_prazo_tramitacao
-
- def get_context_data(self, **kwargs):
- context = super(RelatorioDataFimPrazoTramitacaoView,
- self).get_context_data(**kwargs)
- context['title'] = _('Relatório de Tramitações')
- if not self.filterset.form.is_valid():
- return context
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
-
- context['data_tramitacao'] = (self.request.GET['tramitacao__data_fim_prazo_0'] + ' - ' +
- self.request.GET['tramitacao__data_fim_prazo_1'])
-
- if self.request.GET['ano']:
- context['ano'] = self.request.GET['ano']
- else:
- context['ano'] = ''
-
- if self.request.GET['tipo']:
- tipo = self.request.GET['tipo']
- context['tipo'] = (
- str(TipoMateriaLegislativa.objects.get(id=tipo)))
- else:
- context['tipo'] = ''
-
- if self.request.GET['tramitacao__status']:
- tramitacao_status = self.request.GET['tramitacao__status']
- context['tramitacao__status'] = (
- str(StatusTramitacao.objects.get(id=tramitacao_status)))
- else:
- context['tramitacao__status'] = ''
-
- if self.request.GET['tramitacao__unidade_tramitacao_local']:
- context['tramitacao__unidade_tramitacao_local'] = \
- (str(UnidadeTramitacao.objects.get(
- id=self.request.GET['tramitacao__unidade_tramitacao_local'])))
- else:
- context['tramitacao__unidade_tramitacao_local'] = ''
-
- if self.request.GET['tramitacao__unidade_tramitacao_destino']:
- context['tramitacao__unidade_tramitacao_destino'] = \
- (str(UnidadeTramitacao.objects.get(
- id=self.request.GET['tramitacao__unidade_tramitacao_destino'])))
- else:
- context['tramitacao__unidade_tramitacao_destino'] = ''
-
- return context
-
-
-class RelatorioReuniaoView(RelatorioMixin, FilterView):
- model = Reuniao
- filterset_class = RelatorioReuniaoFilterSet
- template_name = 'base/RelatorioReuniao_filter.html'
- relatorio = relatorio_reuniao
-
- def get_filterset_kwargs(self, filterset_class):
- super(RelatorioReuniaoView,
- self).get_filterset_kwargs(filterset_class)
-
- kwargs = {'data': self.request.GET or None}
- return kwargs
-
- def get_context_data(self, **kwargs):
- context = super(RelatorioReuniaoView,
- self).get_context_data(**kwargs)
- context['title'] = _('Reunião de Comissão')
- if not self.filterset.form.is_valid():
- return context
- qr = self.request.GET.copy()
-
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
-
- if self.request.GET['comissao']:
- comissao = self.request.GET['comissao']
- context['comissao'] = (str(Comissao.objects.get(id=comissao)))
- else:
- context['comissao'] = ''
-
- return context
-
-
-class RelatorioAudienciaView(RelatorioMixin, FilterView):
- model = AudienciaPublica
- filterset_class = RelatorioAudienciaFilterSet
- template_name = 'base/RelatorioAudiencia_filter.html'
- relatorio = relatorio_audiencia
-
- def get_filterset_kwargs(self, filterset_class):
- super(RelatorioAudienciaView,
- self).get_filterset_kwargs(filterset_class)
-
- kwargs = {'data': self.request.GET or None}
- return kwargs
-
- def get_context_data(self, **kwargs):
- context = super(RelatorioAudienciaView,
- self).get_context_data(**kwargs)
- context['title'] = _('Audiência Pública')
- if not self.filterset.form.is_valid():
- return context
-
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
-
- if self.request.GET['tipo']:
- tipo = self.request.GET['tipo']
- context['tipo'] = (str(TipoAudienciaPublica.objects.get(id=tipo)))
- else:
- context['tipo'] = ''
-
- return context
-
-
-class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
- model = MateriaEmTramitacao
- filterset_class = RelatorioMateriasTramitacaoFilterSet
- template_name = 'base/RelatorioMateriasPorTramitacao_filter.html'
- relatorio = relatorio_materia_em_tramitacao
-
- paginate_by = 100
-
- total_resultados_tipos = {}
-
- def get_filterset_kwargs(self, filterset_class):
- data = super().get_filterset_kwargs(filterset_class)
-
- if data['data']:
- qs = data['queryset']
-
- ano_materia = data['data']['materia__ano']
- tipo_materia = data['data']['materia__tipo']
- unidade_tramitacao_destino = data['data']['tramitacao__unidade_tramitacao_destino']
- status_tramitacao = data['data']['tramitacao__status']
- autor = data['data']['materia__autores']
-
- kwargs = {}
- if ano_materia:
- kwargs['materia__ano'] = ano_materia
- if tipo_materia:
- kwargs['materia__tipo'] = tipo_materia
- if unidade_tramitacao_destino:
- kwargs['tramitacao__unidade_tramitacao_destino'] = unidade_tramitacao_destino
- if status_tramitacao:
- kwargs['tramitacao__status'] = status_tramitacao
- if autor:
- kwargs['materia__autores'] = autor
-
- qs = qs.filter(**kwargs)
- data['queryset'] = qs
-
- self.total_resultados_tipos = num_materias_por_tipo(
- qs, "materia__tipo")
-
- return data
-
- def get_queryset(self):
- qs = super().get_queryset()
- qs = qs.select_related('materia__tipo').filter(
- materia__em_tramitacao=True
- ).exclude(
- tramitacao__status__indicador='F'
- ).order_by('-materia__ano', '-materia__numero')
- return qs
-
- def get_context_data(self, **kwargs):
- context = super(
- RelatorioMateriasTramitacaoView, self
- ).get_context_data(**kwargs)
-
- context['title'] = _('Matérias em Tramitação')
-
- if not self.filterset.form.is_valid():
- return context
-
- qr = self.request.GET.copy()
-
- context['qtdes'] = self.total_resultados_tipos
- context['ano'] = (self.request.GET['materia__ano'])
-
- if self.request.GET['materia__tipo']:
- tipo = self.request.GET['materia__tipo']
- context['tipo'] = (
- str(TipoMateriaLegislativa.objects.get(id=tipo))
- )
- else:
- context['tipo'] = ''
-
- if self.request.GET['tramitacao__status']:
- tramitacao_status = self.request.GET['tramitacao__status']
- context['tramitacao__status'] = (
- str(StatusTramitacao.objects.get(id=tramitacao_status))
- )
- else:
- context['tramitacao__status'] = ''
-
- if self.request.GET['tramitacao__unidade_tramitacao_destino']:
- context['tramitacao__unidade_tramitacao_destino'] = (
- str(UnidadeTramitacao.objects.get(
- id=self.request.GET['tramitacao__unidade_tramitacao_destino']
- ))
- )
- else:
- context['tramitacao__unidade_tramitacao_destino'] = ''
-
- if self.request.GET['materia__autores']:
- autor = self.request.GET['materia__autores']
- context['materia__autor'] = (
- str(Autor.objects.get(id=autor))
- )
- else:
- context['materia__autor'] = ''
- if 'page' in qr:
- del qr['page']
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
- context['show_results'] = show_results_filter_set(qr)
-
- paginator = context['paginator']
- page_obj = context['page_obj']
-
- context['page_range'] = make_pagination(
- page_obj.number, paginator.num_pages
- )
- context['NO_ENTRIES_MSG'] = 'Nenhum encontrado.'
-
- return context
-
-
-class RelatorioMateriasPorAnoAutorTipoView(RelatorioMixin, FilterView):
- model = MateriaLegislativa
- filterset_class = RelatorioMateriasPorAnoAutorTipoFilterSet
- template_name = 'base/RelatorioMateriasPorAnoAutorTipo_filter.html'
- relatorio = relatorio_materia_por_ano_autor
-
- def get_materias_autor_ano(self, ano, primeiro_autor):
-
- autorias = Autoria.objects.filter(materia__ano=ano, primeiro_autor=primeiro_autor).values(
- 'autor',
- 'materia__tipo__sigla',
- 'materia__tipo__descricao').annotate(
- total=Count('materia__tipo')).order_by(
- 'autor',
- 'materia__tipo')
-
- autores_ids = set([i['autor'] for i in autorias])
-
- autores = dict((a.id, a) for a in Autor.objects.filter(
- id__in=autores_ids))
-
- relatorio = []
- visitados = set()
- curr = None
-
- for a in autorias:
- # se mudou autor, salva atual, caso existente, e reinicia `curr`
- if a['autor'] not in visitados:
- if curr:
- relatorio.append(curr)
-
- curr = {}
- curr['autor'] = autores[a['autor']]
- curr['materia'] = []
- curr['total'] = 0
-
- visitados.add(a['autor'])
-
- # atualiza valores
- curr['materia'].append((a['materia__tipo__descricao'], a['total']))
- curr['total'] += a['total']
- # adiciona o ultimo
- relatorio.append(curr)
-
- return relatorio
-
- def get_filterset_kwargs(self, filterset_class):
- super(RelatorioMateriasPorAnoAutorTipoView,
- self).get_filterset_kwargs(filterset_class)
-
- kwargs = {'data': self.request.GET or None}
- return kwargs
-
- def get_context_data(self, **kwargs):
- context = super(RelatorioMateriasPorAnoAutorTipoView,
- self).get_context_data(**kwargs)
-
- context['title'] = _('Matérias por Ano, Autor e Tipo')
- if not self.filterset.form.is_valid():
- return context
- qs = context['object_list']
- context['qtdes'] = num_materias_por_tipo(qs)
-
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
- context['ano'] = self.request.GET['ano']
-
- if 'ano' in self.request.GET and self.request.GET['ano']:
- ano = int(self.request.GET['ano'])
- context['relatorio'] = self.get_materias_autor_ano(ano, True)
- context['corelatorio'] = self.get_materias_autor_ano(ano, False)
- else:
- context['relatorio'] = []
-
- return context
-
-
-class RelatorioMateriasPorAutorView(RelatorioMixin, FilterView):
- model = MateriaLegislativa
- filterset_class = RelatorioMateriasPorAutorFilterSet
- template_name = 'base/RelatorioMateriasPorAutor_filter.html'
- relatorio = relatorio_materia_por_autor
-
- def get_filterset_kwargs(self, filterset_class):
- super().get_filterset_kwargs(filterset_class)
- kwargs = {'data': self.request.GET or None}
- return kwargs
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
-
- context['title'] = _('Matérias por Autor')
- if not self.filterset.form.is_valid():
- return context
-
- qs = context['object_list']
- context['materias_resultado'] = list(OrderedDict.fromkeys(qs))
- context['qtdes'] = num_materias_por_tipo(qs)
-
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
- if self.request.GET['tipo']:
- tipo = int(self.request.GET['tipo'])
- context['tipo'] = (
- str(TipoMateriaLegislativa.objects.get(id=tipo)))
- else:
- context['tipo'] = ''
- if self.request.GET['autoria__autor']:
- autor = int(self.request.GET['autoria__autor'])
- context['autor'] = (str(Autor.objects.get(id=autor)))
- else:
- context['autor'] = ''
- context['periodo'] = (
- self.request.GET['data_apresentacao_0'] +
- ' - ' + self.request.GET['data_apresentacao_1'])
-
- return context
-
-
-class RelatorioMateriaAnoAssuntoView(ListView):
- template_name = 'base/RelatorioMateriasAnoAssunto.html'
-
- def get_queryset(self):
- return MateriaAssunto.objects.all().values(
- 'assunto_id',
- assunto_materia=F('assunto__assunto'),
- ano=F('materia__ano')).annotate(
- total=Count('assunto_id')).order_by('-materia__ano', 'assunto_id')
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['title'] = _('Matérias por Ano e Assunto')
-
- # In[10]: MateriaAssunto.objects.all().values(
- # ...: 'materia__ano').annotate(
- # ...: total = Count('materia__ano')).order_by('-materia__ano')
-
- mat = MateriaLegislativa.objects.filter(
- materiaassunto__isnull=True).values(
- 'ano').annotate(
- total=Count('ano')).order_by('-ano')
-
- context.update({"materias_sem_assunto": mat})
- return context
-
-
-class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView):
- model = NormaJuridica
- filterset_class = RelatorioNormasMesFilterSet
- template_name = 'base/RelatorioNormaMes_filter.html'
- relatorio = relatorio_normas_mes
-
- def get_context_data(self, **kwargs):
- context = super(RelatorioNormasPublicadasMesView,
- self).get_context_data(**kwargs)
- context['title'] = _('Normas')
-
- # Verifica se os campos foram preenchidos
- if not self.filterset.form.is_valid():
- return context
-
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
- context['ano'] = self.request.GET['ano']
-
- normas_mes = collections.OrderedDict()
- meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho',
- 7: 'Julho', 8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'}
- for norma in context['object_list']:
- if not meses[norma.data.month] in normas_mes:
- normas_mes[meses[norma.data.month]] = []
- normas_mes[meses[norma.data.month]].append(norma)
-
- context['normas_mes'] = normas_mes
-
- quant_normas_mes = {}
- for key in normas_mes.keys():
- quant_normas_mes[key] = len(normas_mes[key])
-
- context['quant_normas_mes'] = quant_normas_mes
-
- return context
-
-
-class RelatorioNormasVigenciaView(RelatorioMixin, FilterView):
- model = NormaJuridica
- filterset_class = RelatorioNormasVigenciaFilterSet
- template_name = 'base/RelatorioNormasVigencia_filter.html'
- relatorio = relatorio_normas_vigencia
-
- def get_filterset_kwargs(self, filterset_class):
- super(RelatorioNormasVigenciaView,
- self).get_filterset_kwargs(filterset_class)
-
- kwargs = {'data': self.request.GET or None}
- qs = self.get_queryset().order_by('data').distinct()
- if kwargs['data']:
- ano = kwargs['data']['ano']
- vigencia = kwargs['data']['vigencia']
- if ano:
- qs = qs.filter(ano=ano)
-
- if vigencia == 'True':
- qs_dt_not_null = qs.filter(data_vigencia__isnull=True)
- qs = (qs_dt_not_null | qs.filter(
- data_vigencia__gte=datetime.datetime.now().date())).distinct()
- else:
- qs = qs.filter(
- data_vigencia__lt=datetime.datetime.now().date())
-
- kwargs.update({
- 'queryset': qs
- })
- return kwargs
-
- def get_context_data(self, **kwargs):
- context = super(RelatorioNormasVigenciaView,
- self).get_context_data(**kwargs)
- context['title'] = _('Normas por vigência')
-
- # Verifica se os campos foram preenchidos
- if not self.filterset.form.is_valid():
- return context
-
- normas_totais = NormaJuridica.objects.filter(
- ano=self.request.GET['ano'])
-
- context['quant_total'] = len(normas_totais)
- if self.request.GET['vigencia'] == 'True':
- context['vigencia'] = 'Vigente'
- context['quant_vigente'] = len(context['object_list'])
- context['quant_nao_vigente'] = context['quant_total'] - \
- context['quant_vigente']
- else:
- context['vigencia'] = 'Não vigente'
- context['quant_nao_vigente'] = len(context['object_list'])
- context['quant_vigente'] = context['quant_total'] - \
- context['quant_nao_vigente']
-
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
- context['ano'] = self.request.GET['ano']
-
- return context
-
-
class EstatisticasAcessoNormas(TemplateView):
template_name = 'base/EstatisticasAcessoNormas_filter.html'
@@ -1219,6 +346,7 @@ class EstatisticasAcessoNormas(TemplateView):
**params
)
+ normas_count_mes = collections.OrderedDict()
normas_mes = collections.OrderedDict()
meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho',
7: 'Julho', 8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'}
@@ -1226,10 +354,15 @@ class EstatisticasAcessoNormas(TemplateView):
for norma in estatisticas:
if not meses[norma.mes_est] in normas_mes:
normas_mes[meses[norma.mes_est]] = []
+ normas_count_mes[meses[norma.mes_est]] = 0
+
+ normas_count_mes[meses[norma.mes_est]] += norma.norma_count
normas_mes[meses[norma.mes_est]].append(norma)
context['normas_mes'] = normas_mes
+ context['normas_count_mes'] = normas_count_mes
+
is_relatorio = request.GET.get('relatorio')
context['show_results'] = show_results_filter_set(
@@ -1680,7 +813,7 @@ class ListarParlMandatosIntersecaoView(PermissionRequiredMixin, ListView):
def parlamentares_duplicados():
return [parlamentar for parlamentar in Parlamentar.objects.values(
'nome_parlamentar').order_by('nome_parlamentar').annotate(count=Count(
- 'nome_parlamentar')).filter(count__gt=1)]
+ 'nome_parlamentar')).filter(count__gt=1)]
class ListarParlamentaresDuplicadosView(PermissionRequiredMixin, ListView):
@@ -1717,10 +850,10 @@ def get_estatistica(request):
datas = [
materias.order_by(
'-data_ultima_atualizacao').values_list('data_ultima_atualizacao', flat=True)
- .exclude(data_ultima_atualizacao__isnull=True).first(),
+ .exclude(data_ultima_atualizacao__isnull=True).first(),
normas.order_by(
'-data_ultima_atualizacao').values_list('data_ultima_atualizacao', flat=True)
- .exclude(data_ultima_atualizacao__isnull=True).first()
+ .exclude(data_ultima_atualizacao__isnull=True).first()
]
max_data = max(datas) if datas[0] and datas[1] else next(
@@ -2069,7 +1202,8 @@ class AppConfigCrud(CrudAux):
except ValidationError as e:
form.add_error('receber_recibo_proposicao', e)
msg = _(
- "Não foi possível mudar a configuração porque a Proposição {} não possui texto original vinculado!".format(prop))
+ "Não foi possível mudar a configuração porque a Proposição {} não possui texto original vinculado!".format(
+ prop))
messages.error(self.request, msg)
return super().form_invalid(form)
return super().form_valid(form)
@@ -2250,6 +1384,84 @@ class SaplSearchView(SearchView):
return context
+class PesquisarAuditLogView(PermissionRequiredMixin, FilterView):
+ model = AuditLog
+ filterset_class = AuditLogFilterSet
+ paginate_by = 20
+
+ permission_required = ('base.list_appconfig',)
+
+ def get_filterset_kwargs(self, filterset_class):
+ super(PesquisarAuditLogView, self).get_filterset_kwargs(
+ filterset_class
+ )
+
+ return ({
+ "data": self.request.GET or None,
+ "queryset": self.get_queryset().order_by("-id")
+ })
+
+ def get_context_data(self, **kwargs):
+ context = super(PesquisarAuditLogView, self).get_context_data(
+ **kwargs
+ )
+
+ paginator = context["paginator"]
+ page_obj = context["page_obj"]
+
+ qr = self.request.GET.copy()
+ if 'page' in qr:
+ del qr['page']
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+ context['show_results'] = show_results_filter_set(qr)
+
+ context.update({
+ "page_range": make_pagination(
+ page_obj.number, paginator.num_pages
+ ),
+ "NO_ENTRIES_MSG": "Nenhum registro de log encontrado!",
+ "title": _("Pesquisar Logs de Auditoria")
+ })
+
+ return context
+
+ def get(self, request, *args, **kwargs):
+ timefilter = request.GET.get('timestamp', None)
+
+ if not timefilter:
+ newgetrequest = request.GET.copy()
+ newgetrequest['timestamp'] = 'week'
+ request.GET = newgetrequest
+
+ super(PesquisarAuditLogView, self).get(request)
+
+ data = self.filterset.data
+
+ url = ''
+
+ if data:
+ url = '&' + str(self.request.META["QUERY_STRING"])
+ if url.startswith("&page"):
+ url = ''
+
+ resultados = self.object_list
+ # if 'page' in self.request.META['QUERY_STRING']:
+ # resultados = self.object_list
+ # else:
+ # resultados = []
+
+ context = self.get_context_data(filter=self.filterset,
+ object_list=resultados,
+ filter_url=url,
+ numero_res=len(resultados)
+ )
+
+ context['show_results'] = show_results_filter_set(
+ self.request.GET.copy())
+
+ return self.render_to_response(context)
+
+
class AlterarSenha(FormView):
from sapl.settings import LOGIN_URL
@@ -2284,7 +1496,6 @@ class LogotipoView(RedirectView):
def filtro_campos(dicionario):
-
chaves_desejadas = ['ementa',
'ano',
'numero',
@@ -2307,7 +1518,6 @@ def filtro_campos(dicionario):
def pesquisa_textual(request):
-
if 'q' not in request.GET:
return JsonResponse({'total': 0,
'resultados': []})
@@ -2337,102 +1547,3 @@ def pesquisa_textual(request):
json_dict['resultados'].append(sec_dict)
return JsonResponse(json_dict)
-
-
-class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView):
- model = DocumentoAdministrativo
- filterset_class = RelatorioHistoricoTramitacaoAdmFilterSet
- template_name = 'base/RelatorioHistoricoTramitacaoAdm_filter.html'
- relatorio = relatorio_historico_tramitacao_adm
-
- def get_context_data(self, **kwargs):
- context = super(RelatorioHistoricoTramitacaoAdmView,
- self).get_context_data(**kwargs)
- context['title'] = _(
- 'Histórico de Tramitações de Documento Administrativo')
- if not self.filterset.form.is_valid():
- return context
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
- context['data_tramitacao'] = (self.request.GET['tramitacaoadministrativo__data_tramitacao_0'] + ' - ' +
- self.request.GET['tramitacaoadministrativo__data_tramitacao_1'])
- if self.request.GET['tipo']:
- tipo = self.request.GET['tipo']
- context['tipo'] = (
- str(TipoDocumentoAdministrativo.objects.get(id=tipo)))
- else:
- context['tipo'] = ''
-
- if self.request.GET['tramitacaoadministrativo__status']:
- tramitacao_status = self.request.GET['tramitacaoadministrativo__status']
- context['tramitacaoadministrativo__status'] = (
- str(StatusTramitacaoAdministrativo.objects.get(id=tramitacao_status)))
- else:
- context['tramitacaoadministrativo__status'] = ''
-
- if self.request.GET['tramitacaoadministrativo__unidade_tramitacao_local']:
- context['tramitacaoadministrativo__unidade_tramitacao_local'] = \
- (str(UnidadeTramitacao.objects.get(
- id=self.request.GET['tramitacaoadministrativo__unidade_tramitacao_local'])))
- else:
- context['tramitacaoadministrativo__unidade_tramitacao_local'] = ''
-
- if self.request.GET['tramitacaoadministrativo__unidade_tramitacao_destino']:
- context['tramitacaoadministrativo__unidade_tramitacao_destino'] = \
- (str(UnidadeTramitacao.objects.get(
- id=self.request.GET['tramitacaoadministrativo__unidade_tramitacao_destino'])))
- else:
- context['tramitacaoadministrativo__unidade_tramitacao_destino'] = ''
-
- return context
-
-
-class RelatorioNormasPorAutorView(RelatorioMixin, FilterView):
- model = NormaJuridica
- filterset_class = RelatorioNormasPorAutorFilterSet
- template_name = 'base/RelatorioNormasPorAutor_filter.html'
- relatorio = relatorio_normas_por_autor
-
- def get_filterset_kwargs(self, filterset_class):
- super().get_filterset_kwargs(filterset_class)
- kwargs = {'data': self.request.GET or None}
- return kwargs
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
-
- context['title'] = _('Normas por Autor')
- if not self.filterset.form.is_valid():
- return context
-
- qtdes = {}
- for tipo in TipoNormaJuridica.objects.all():
- qs = context['object_list']
- qtde = len(qs.filter(tipo_id=tipo.id))
- if qtde > 0:
- qtdes[tipo] = qtde
- context['qtdes'] = qtdes
-
- qr = self.request.GET.copy()
- context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
-
- context['show_results'] = show_results_filter_set(qr)
- if self.request.GET['tipo']:
- tipo = int(self.request.GET['tipo'])
- context['tipo'] = (
- str(TipoNormaJuridica.objects.get(id=tipo)))
- else:
- context['tipo'] = ''
-
- if self.request.GET['autorianorma__autor']:
- autor = int(self.request.GET['autorianorma__autor'])
- context['autor'] = (str(Autor.objects.get(id=autor)))
- else:
- context['autor'] = ''
- context['periodo'] = (
- self.request.GET['data_0'] +
- ' - ' + self.request.GET['data_1'])
-
- return context
diff --git a/sapl/comissoes/migrations/0029_auto_20221019_2041.py b/sapl/comissoes/migrations/0029_auto_20221019_2041.py
new file mode 100644
index 000000000..abd175497
--- /dev/null
+++ b/sapl/comissoes/migrations/0029_auto_20221019_2041.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.2.28 on 2022-10-19 23:41
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('comissoes', '0028_auto_20220807_2257'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='reuniao',
+ options={'ordering': ('-data', '-nome'), 'verbose_name': 'Reunião de Comissão', 'verbose_name_plural': 'Reuniões de Comissão'},
+ ),
+ ]
diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py
index 5c6a67d8a..75381e3a2 100644
--- a/sapl/comissoes/models.py
+++ b/sapl/comissoes/models.py
@@ -258,7 +258,7 @@ class Reuniao(models.Model):
class Meta:
verbose_name = _('Reunião de Comissão')
verbose_name_plural = _('Reuniões de Comissão')
- ordering = ('numero', 'comissao')
+ ordering = ('-data', '-nome')
def __str__(self):
return self.nome
diff --git a/sapl/comissoes/views.py b/sapl/comissoes/views.py
index 0c9329f05..5d3e450c2 100644
--- a/sapl/comissoes/views.py
+++ b/sapl/comissoes/views.py
@@ -212,7 +212,6 @@ class ReuniaoCrud(MasterDetailCrud):
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['data', 'nome', 'tema', 'upload_ata']
- ordering = '-data'
class DetailView(MasterDetailCrud.DetailView):
template_name = "comissoes/reuniao_detail.html"
diff --git a/sapl/compilacao/templatetags/compilacao_filters.py b/sapl/compilacao/templatetags/compilacao_filters.py
index 201e0d62c..cc21b8eb7 100644
--- a/sapl/compilacao/templatetags/compilacao_filters.py
+++ b/sapl/compilacao/templatetags/compilacao_filters.py
@@ -82,7 +82,7 @@ def dispositivo_desativado(dispositivo, inicio_vigencia, fim_vigencia):
@register.simple_tag
def nota_automatica(dispositivo, ta_pub_list):
- if dispositivo.ta_publicado:
+ if dispositivo.ta_publicado and dispositivo.dispositivo_atualizador is not None and dispositivo.dispositivo_atualizador.dispositivo_pai is not None:
d = dispositivo.dispositivo_atualizador.dispositivo_pai
if d.auto_inserido:
diff --git a/sapl/compilacao/urls.py b/sapl/compilacao/urls.py
index 9364985aa..7ca2ad463 100644
--- a/sapl/compilacao/urls.py
+++ b/sapl/compilacao/urls.py
@@ -116,5 +116,9 @@ urlpatterns = [
include(VeiculoPublicacaoCrud.get_urls())),
url(r'^sistema/ta/config/tipo/',
include(TipoTextoArticuladoCrud.get_urls())),
+ url(r'^sistema/ta/config/tipodispositivo/',
+ include(TipoDispositivoCrud.get_urls())),
+
+
]
diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py
index c6939a5fe..9afbe3201 100644
--- a/sapl/compilacao/views.py
+++ b/sapl/compilacao/views.py
@@ -59,8 +59,35 @@ TipoNotaCrud = CrudAux.build(TipoNota, 'tipo_nota')
TipoVideCrud = CrudAux.build(TipoVide, 'tipo_vide')
TipoPublicacaoCrud = CrudAux.build(TipoPublicacao, 'tipo_publicacao')
VeiculoPublicacaoCrud = CrudAux.build(VeiculoPublicacao, 'veiculo_publicacao')
-TipoDispositivoCrud = CrudAux.build(
- TipoDispositivo, 'tipo_dispositivo')
+
+
+class TipoDispositivoCrud(CrudAux):
+ model = TipoDispositivo
+
+ class BaseMixin(CrudAux.BaseMixin):
+ list_field_names = ('nome', )
+
+ @property
+ def delete_url(self):
+ return ''
+
+ @property
+ def create_url(self):
+ return ''
+
+ class CreateView(CrudAux.CreateView):
+ def has_permission(self):
+ return False
+
+ class DeleteView(CrudAux.DeleteView):
+ def has_permission(self):
+ return False
+
+ class UpdateView(CrudAux.UpdateView):
+ layout_key = 'TipoDispositivoUpdate'
+
+ class ListView(CrudAux.ListView):
+ paginate_by = 100
def choice_models_in_extenal_views():
@@ -1365,7 +1392,7 @@ class TextEditView(CompMixin, TemplateView):
return r
def nota_alteracao(self, dispositivo, lista_ta_publicado):
- if dispositivo.ta_publicado_id:
+ if dispositivo.ta_publicado_id and dispositivo.dispositivo_atualizador:
d = dispositivo.dispositivo_atualizador.dispositivo_pai
if d.auto_inserido:
diff --git a/sapl/crispy_layout_mixin.py b/sapl/crispy_layout_mixin.py
index 7bd099748..b131623b9 100644
--- a/sapl/crispy_layout_mixin.py
+++ b/sapl/crispy_layout_mixin.py
@@ -4,6 +4,7 @@ from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Div, Fieldset, Layout, Submit
from django import template
+from django.contrib.contenttypes.models import ContentType
from django.urls import reverse, reverse_lazy
from django.utils import formats
from django.utils.encoding import force_text
@@ -329,6 +330,21 @@ class CrispyLayoutFormMixin:
return verbose_name, display
+ def widget__signs(self, obj, fieldname):
+ from sapl.base.models import Metadata
+ try:
+ md = Metadata.objects.get(
+ content_type=ContentType.objects.get_for_model(
+ obj._meta.model),
+ object_id=obj.id,)
+ autores = md.metadata['signs'][fieldname]['autores']
+ t = template.loader.get_template('base/widget__signs.html')
+ rendered = str(t.render(context={'signs': autores}))
+ except Exception as e:
+ return '', ''
+
+ return 'Assinaturas Eletrônicas', rendered
+
@property
def layout_display(self):
diff --git a/sapl/endpoint_restriction_middleware.py b/sapl/endpoint_restriction_middleware.py
new file mode 100644
index 000000000..b815b4a2e
--- /dev/null
+++ b/sapl/endpoint_restriction_middleware.py
@@ -0,0 +1,38 @@
+from django.http import HttpResponseForbidden
+import logging
+
+# lista de IPs permitidos (localhost, redes locais, etc)
+# https://en.wikipedia.org/wiki/Reserved_IP_addresses
+ALLOWED_IPS = [
+ '127.0.0.1',
+ '::1',
+ '10.0.0.0/8',
+ '172.16.0.0/12',
+ '192.168.0.0/16',
+ 'fc00::/7',
+ '::1',
+ 'fe80::/10',
+ '192.0.2.0/24',
+ '2001:db8::/32',
+ '224.0.0.0/4',
+ 'ff00::/8'
+]
+
+RESTRICTED_ENDPOINTS = ['/metrics']
+
+
+class EndpointRestrictionMiddleware:
+ logging.getLogger(__name__)
+
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ # IP do cliente
+ client_ip = request.META.get('REMOTE_ADDR')
+
+ # bloqueia acesso a endpoints restritos para IPs nao permitidos
+ if request.path in RESTRICTED_ENDPOINTS and client_ip not in ALLOWED_IPS:
+ return HttpResponseForbidden('Acesso proibido')
+
+ return self.get_response(request)
diff --git a/sapl/lexml/OAIServer.py b/sapl/lexml/OAIServer.py
index 340c03dde..e23db808e 100644
--- a/sapl/lexml/OAIServer.py
+++ b/sapl/lexml/OAIServer.py
@@ -237,7 +237,8 @@ class OAIServer:
return None
def oai_query(self, offset=0, batch_size=10, from_=None, until=None, identifier=None):
- from_ = timezone.make_aware(from_) # convert from naive to timezone aware datetime
+ if from_:
+ from_ = timezone.make_aware(from_) # convert from naive to timezone aware datetime
esfera = self.get_esfera_federacao()
offset = 0 if offset < 0 else offset
batch_size = 10 if batch_size < 0 else batch_size
diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py
index e5085491e..ee6433521 100644
--- a/sapl/materia/forms.py
+++ b/sapl/materia/forms.py
@@ -2,7 +2,7 @@ import logging
import os
from crispy_forms.bootstrap import Alert, InlineRadios
-from crispy_forms.layout import (Button, Field, Fieldset, HTML, Layout, Row)
+from crispy_forms.layout import (Button, Field, Fieldset, HTML, Layout, Row, Div)
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
@@ -1126,22 +1126,38 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
+ Div(
Fieldset(_('Pesquisa Básica'),
- row1, row2),
-
+ row1, row2,
+ HTML(autor_label),
+ HTML(autor_modal),
+ row4,
+ ),
+ Button('btn_pesquisa_avancada', 'Pesquisa Avançada >>>',
+ css_id='btn_pesquisa_avancada_id',
+ css_class='btn btn-dark',
+ onClick="pesquisaAvancada()",
+ style='margin-bottom: 2vh;font-weight: bold'
+ ),
Fieldset(_('Como listar os resultados da pesquisa'),
- row8
+ row8,
+ css_class='pesquisa_avancada',
+ style='display: none;',
),
Fieldset(_('Origem externa'),
- row10, row11
+ row10, row11,
+ css_class='pesquisa_avancada',
+ style='display: none;',
),
- Fieldset(_('Pesquisa Avançada'),
+ Fieldset(_('Mais Opções de Pesquisa...'),
row3,
- HTML(autor_label),
- HTML(autor_modal),
- row4, row6, row7, row9,
- form_actions(label=_('Pesquisar')))
- )
+ row6, row7, row9,
+ css_class='pesquisa_avancada',
+ style='display: none;'
+ ),
+ form_actions(label=_('Pesquisar')),
+ )
+ )
@property
def qs(self):
@@ -1496,15 +1512,18 @@ class TramitacaoEmLoteFilterSet(django_filters.FilterSet):
class TipoProposicaoForm(ModelForm):
- content_types_choices = [
- (
- f'{ct.app_label}/{ct.model}',
- ct
- )
- for k, ct in ContentType.objects.get_for_models(
- *models_with_gr_for_model(TipoProposicao)
- ).items()
- ]
+ try:
+ content_types_choices = [
+ (
+ f'{ct.app_label}/{ct.model}',
+ ct
+ )
+ for k, ct in ContentType.objects.get_for_models(
+ *models_with_gr_for_model(TipoProposicao)
+ ).items()
+ ]
+ except:
+ content_types_choices = []
logger = logging.getLogger(__name__)
diff --git a/sapl/materia/migrations/0082_auto_20230529_1641.py b/sapl/materia/migrations/0082_auto_20230529_1641.py
new file mode 100644
index 000000000..191ea28a2
--- /dev/null
+++ b/sapl/materia/migrations/0082_auto_20230529_1641.py
@@ -0,0 +1,33 @@
+# Generated by Django 2.2.28 on 2023-05-29 19:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('materia', '0081_auto_20220321_0934'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='materialegislativa',
+ name='ano',
+ field=models.PositiveSmallIntegerField(choices=[(2024, 2024), (2023, 2023), (2022, 2022), (2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
+ ),
+ migrations.AlterField(
+ model_name='materialegislativa',
+ name='ano_origem_externa',
+ field=models.PositiveSmallIntegerField(blank=True, choices=[(2024, 2024), (2023, 2023), (2022, 2022), (2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], null=True, verbose_name='Ano'),
+ ),
+ migrations.AlterField(
+ model_name='numeracao',
+ name='ano_materia',
+ field=models.PositiveSmallIntegerField(choices=[(2024, 2024), (2023, 2023), (2022, 2022), (2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
+ ),
+ migrations.AlterField(
+ model_name='proposicao',
+ name='ano',
+ field=models.PositiveSmallIntegerField(blank=True, choices=[(2024, 2024), (2023, 2023), (2022, 2022), (2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=None, null=True, verbose_name='Ano'),
+ ),
+ ]
diff --git a/sapl/materia/migrations/0083_auto_20230731_1845.py b/sapl/materia/migrations/0083_auto_20230731_1845.py
new file mode 100644
index 000000000..d286e0be1
--- /dev/null
+++ b/sapl/materia/migrations/0083_auto_20230731_1845.py
@@ -0,0 +1,31 @@
+# Generated by Django 2.2.28 on 2023-07-31 21:45
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('materia', '0082_auto_20230529_1641'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='proposicao',
+ name='usuario_devolucao',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='proposicoes_devolvidas', to=settings.AUTH_USER_MODEL, verbose_name='Usuário Responsável pela Devolução'),
+ ),
+ migrations.AddField(
+ model_name='proposicao',
+ name='usuario_envio',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='proposicoes_enviadas', to=settings.AUTH_USER_MODEL, verbose_name='Usuário Responsável pelo Envio'),
+ ),
+ migrations.AddField(
+ model_name='proposicao',
+ name='usuario_recebimento',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='proposicoes_recebidas', to=settings.AUTH_USER_MODEL, verbose_name='Usuário Responsável pelo Recebimento'),
+ ),
+ ]
diff --git a/sapl/materia/models.py b/sapl/materia/models.py
index c73f39db5..82035eec2 100644
--- a/sapl/materia/models.py
+++ b/sapl/materia/models.py
@@ -750,6 +750,7 @@ class Parecer(models.Model):
class Proposicao(models.Model):
+
autor = models.ForeignKey(
Autor,
null=True,
@@ -784,6 +785,33 @@ class Proposicao(models.Model):
verbose_name=_('Data de Devolução')
)
+ usuario_envio = models.ForeignKey(
+ get_settings_auth_user_model(),
+ verbose_name=_('Usuário Responsável pelo Envio'),
+ on_delete=models.PROTECT,
+ related_name='proposicoes_enviadas',
+ blank=True,
+ null=True
+ )
+
+ usuario_recebimento = models.ForeignKey(
+ get_settings_auth_user_model(),
+ verbose_name=_('Usuário Responsável pelo Recebimento'),
+ on_delete=models.PROTECT,
+ related_name='proposicoes_recebidas',
+ blank=True,
+ null=True
+ )
+
+ usuario_devolucao = models.ForeignKey(
+ get_settings_auth_user_model(),
+ verbose_name=_('Usuário Responsável pela Devolução'),
+ on_delete=models.PROTECT,
+ related_name='proposicoes_devolvidas',
+ blank=True,
+ null=True
+ )
+
descricao = models.TextField(verbose_name=_('Ementa'))
justificativa_devolucao = models.CharField(
@@ -997,6 +1025,13 @@ class Proposicao(models.Model):
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
+ # atualiza o usuario baseado no status da proposição (que esta sendo calculado pela data)
+ if self.data_envio is not None and not self.usuario_envio:
+ self.usuario_envio = self.user
+ elif self.data_recebimento is not None and not self.usuario_recebimento:
+ self.usuario_recebimento = self.user
+ elif self.data_devolucao is not None and not self.usuario_devolucao:
+ self.usuario_devolucao = self.user
if not self.pk and self.texto_original:
texto_original = self.texto_original
diff --git a/sapl/materia/views.py b/sapl/materia/views.py
index 0e7f087ed..4b72c9149 100644
--- a/sapl/materia/views.py
+++ b/sapl/materia/views.py
@@ -1260,10 +1260,13 @@ class HistoricoProposicaoView(PermissionRequiredMixin, ListView):
ordering = ['-data_hora']
paginate_by = 10
model = HistoricoProposicao
- permission_required = ('materia.detail_proposicao_enviada',
- 'materia.detail_proposicao_devolvida',
- 'materia.detail_proposicao_incorporada'
- )
+ permission_required = (
+ 'materia.list_historicoproposicao',
+ 'materia.add_historicoproposicao',
+ 'materia.change_historicoproposicao',
+ 'materia.delete_historicoproposicao',
+ 'materia.detail_historicoproposicao',
+ )
def get_queryset(self):
qs = super().get_queryset()
diff --git a/sapl/norma/migrations/0044_auto_20230529_1641.py b/sapl/norma/migrations/0044_auto_20230529_1641.py
new file mode 100644
index 000000000..a09ee4816
--- /dev/null
+++ b/sapl/norma/migrations/0044_auto_20230529_1641.py
@@ -0,0 +1,29 @@
+# Generated by Django 2.2.28 on 2023-05-29 19:41
+
+from django.db import migrations, models
+import sapl.norma.models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('norma', '0043_viewnormasestatisticas'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='anexonormajuridica',
+ name='ano',
+ field=models.PositiveSmallIntegerField(choices=[(2024, 2024), (2023, 2023), (2022, 2022), (2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
+ ),
+ migrations.AlterField(
+ model_name='normaestatisticas',
+ name='ano',
+ field=models.PositiveSmallIntegerField(choices=[(2024, 2024), (2023, 2023), (2022, 2022), (2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=sapl.norma.models.get_ano_atual, verbose_name='Ano'),
+ ),
+ migrations.AlterField(
+ model_name='normajuridica',
+ name='ano',
+ field=models.PositiveSmallIntegerField(choices=[(2024, 2024), (2023, 2023), (2022, 2022), (2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
+ ),
+ ]
diff --git a/sapl/norma/views.py b/sapl/norma/views.py
index bccd5e328..d6d474590 100644
--- a/sapl/norma/views.py
+++ b/sapl/norma/views.py
@@ -274,13 +274,14 @@ class NormaCrud(Crud):
class DetailView(Crud.DetailView):
def get(self, request, *args, **kwargs):
estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas
- if estatisticas_acesso_normas == 'S':
+ if estatisticas_acesso_normas == 'S' and \
+ NormaJuridica.objects.filter(id=kwargs['pk']).exists():
NormaEstatisticas.objects.create(usuario=str(self.request.user),
norma_id=kwargs['pk'],
ano=timezone.now().year,
horario_acesso=timezone.now())
- if not 'display' in request.GET and \
+ if 'display' not in request.GET and \
not request.user.has_perm('norma.change_normajuridica'):
ta = self.get_object().texto_articulado.first()
if ta and ta.privacidade == STATUS_TA_PUBLIC:
diff --git a/sapl/painel/views.py b/sapl/painel/views.py
index 7f36fa8be..a4c9e2da3 100644
--- a/sapl/painel/views.py
+++ b/sapl/painel/views.py
@@ -446,7 +446,7 @@ def response_nenhuma_materia(response):
return JsonResponse(response)
-def get_votos(response, materia):
+def get_votos(response, materia, mostrar_voto):
logger = logging.getLogger(__name__)
if type(materia) == OrdemDia:
if materia.tipo_votacao != 4:
@@ -492,8 +492,13 @@ def get_votos(response, materia):
for i, p in enumerate(response['presentes']):
try:
logger.info("Tentando obter votos do parlamentar (id={}).".format(p['parlamentar_id']))
- if votos_parlamentares.get(parlamentar_id=p['parlamentar_id']).voto:
- response['presentes'][i]['voto'] = 'Voto Informado'
+ voto = votos_parlamentares.get(parlamentar_id=p['parlamentar_id']).voto
+
+ if voto:
+ if mostrar_voto:
+ response['presentes'][i]['voto'] = voto
+ else:
+ response['presentes'][i]['voto'] = 'Voto Informado'
except ObjectDoesNotExist:
# logger.error("Votos do parlamentar (id={}) não encontrados. Retornado vazio."
# .format(p['parlamentar_id']))
@@ -563,7 +568,8 @@ def get_dados_painel(request, pk):
'cronometro_ordem': get_cronometro_status(request, 'ordem'),
'cronometro_consideracoes': get_cronometro_status(request, 'consideracoes'),
'status_painel': sessao.painel_aberto,
- 'brasao': brasao
+ 'brasao': brasao,
+ 'mostrar_voto': app_config.mostrar_voto
}
ordem_dia = get_materia_aberta(pk)
@@ -574,11 +580,11 @@ def get_dados_painel(request, pk):
if ordem_dia:
return JsonResponse(get_votos(
get_presentes(pk, response, ordem_dia),
- ordem_dia))
+ ordem_dia, app_config.mostrar_voto))
elif expediente:
return JsonResponse(get_votos(
get_presentes(pk, response, expediente),
- expediente))
+ expediente, app_config.mostrar_voto))
# Caso não tenha nenhuma aberta,
# a matéria a ser mostrada no Painel deve ser a última votada
@@ -614,7 +620,7 @@ def get_dados_painel(request, pk):
if ordem_expediente:
return JsonResponse(get_votos(
get_presentes(pk, response, ordem_expediente),
- ordem_expediente))
+ ordem_expediente, app_config.mostrar_voto))
# Retorna que não há nenhuma matéria já votada ou aberta
return response_nenhuma_materia(get_presentes(pk, response, None))
diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py
index 9ebd3469d..8621eb360 100755
--- a/sapl/parlamentares/forms.py
+++ b/sapl/parlamentares/forms.py
@@ -726,6 +726,8 @@ class BlocoForm(ModelForm):
)
else:
bloco.save()
+
+ bloco.partidos.set(self.cleaned_data['partidos'])
return bloco
diff --git a/sapl/parlamentares/migrations/0041_parlamentar_telefone_celular.py b/sapl/parlamentares/migrations/0041_parlamentar_telefone_celular.py
new file mode 100644
index 000000000..6d4d65631
--- /dev/null
+++ b/sapl/parlamentares/migrations/0041_parlamentar_telefone_celular.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.28 on 2023-01-12 14:32
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('parlamentares', '0040_auto_20220806_1341'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='parlamentar',
+ name='telefone_celular',
+ field=models.CharField(blank=True, max_length=50, verbose_name='Celular'),
+ ),
+ ]
diff --git a/sapl/parlamentares/migrations/0042_auto_20230529_1641.py b/sapl/parlamentares/migrations/0042_auto_20230529_1641.py
new file mode 100644
index 000000000..d941d0424
--- /dev/null
+++ b/sapl/parlamentares/migrations/0042_auto_20230529_1641.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.28 on 2023-05-29 19:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('parlamentares', '0041_parlamentar_telefone_celular'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='parlamentar',
+ name='telefone_celular',
+ field=models.CharField(blank=True, max_length=50, verbose_name='Telefone Celular'),
+ ),
+ ]
diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py
index 7595c71a9..8c6515d68 100644
--- a/sapl/parlamentares/models.py
+++ b/sapl/parlamentares/models.py
@@ -227,6 +227,8 @@ class Parlamentar(models.Model):
max_length=10, blank=True, verbose_name=_('Nº Gabinete'))
telefone = models.CharField(
max_length=50, blank=True, verbose_name=_('Telefone'))
+ telefone_celular = models.CharField(
+ max_length=50, blank=True, verbose_name=_('Telefone Celular'))
fax = models.CharField(
max_length=50, blank=True, verbose_name=_('Fax'))
endereco_residencia = models.CharField(
diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py
index 43a54c0f0..95df777bc 100644
--- a/sapl/parlamentares/views.py
+++ b/sapl/parlamentares/views.py
@@ -1388,6 +1388,7 @@ def altera_field_mesa_public_view(request):
partido_parlamentar_sessao_legislativa(sessao, parlamentar))
if parlamentar.fotografia:
try:
+ logger.warning(f"Iniciando cropping da imagem {parlamentar.fotografia}")
thumbnail_url = get_backend().get_thumbnail_url(
parlamentar.fotografia,
{
@@ -1397,6 +1398,7 @@ def altera_field_mesa_public_view(request):
'detail': True,
}
)
+ logger.warning(f"Cropping da imagem {parlamentar.fotografia} realizado com sucesso")
lista_fotos.append(thumbnail_url)
except Exception as e:
logger.error(e)
diff --git a/sapl/protocoloadm/migrations/0044_auto_20230529_1641.py b/sapl/protocoloadm/migrations/0044_auto_20230529_1641.py
new file mode 100644
index 000000000..6b7396677
--- /dev/null
+++ b/sapl/protocoloadm/migrations/0044_auto_20230529_1641.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.2.28 on 2023-05-29 19:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('protocoloadm', '0043_auto_20220919_1705'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='documentoadministrativo',
+ name='ano',
+ field=models.PositiveSmallIntegerField(choices=[(2024, 2024), (2023, 2023), (2022, 2022), (2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
+ ),
+ migrations.AlterField(
+ model_name='protocolo',
+ name='ano',
+ field=models.PositiveSmallIntegerField(choices=[(2024, 2024), (2023, 2023), (2022, 2022), (2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano do Protocolo'),
+ ),
+ ]
diff --git a/sapl/protocoloadm/urls.py b/sapl/protocoloadm/urls.py
index ab3dcefeb..05c787897 100644
--- a/sapl/protocoloadm/urls.py
+++ b/sapl/protocoloadm/urls.py
@@ -27,7 +27,8 @@ from sapl.protocoloadm.views import (AcompanhamentoDocumentoView,
TramitacaoEmLoteAdmView,
apaga_protocolos_view,
VinculoDocAdminMateriaCrud,
- VinculoDocAdminMateriaEmLoteView)
+ VinculoDocAdminMateriaEmLoteView,
+ get_pdf_docacessorios)
from .apps import AppConfig
@@ -51,6 +52,8 @@ urlpatterns_documento_administrativo = [
name='anexado_em_lote'),
url(r'^docadm/(?P\d+)/vinculo-em-lote', VinculoDocAdminMateriaEmLoteView.as_view(),
name='vinculodocadminmateria_em_lote'),
+ url(r'^docadm/documentoacessorioadministrativo/pdf/(?P\d+)$', get_pdf_docacessorios,
+ name='merge_docacessorios')
]
urlpatterns_protocolo = [
diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py
index 30daffb73..bfc697c79 100755
--- a/sapl/protocoloadm/views.py
+++ b/sapl/protocoloadm/views.py
@@ -1,9 +1,13 @@
+import os
+import time
from datetime import datetime
import logging
+from io import BytesIO
from random import choice
import re
from string import ascii_letters, digits
+from PyPDF4 import PdfFileMerger
from braces.views import FormValidMessageMixin
from django.conf import settings
from django.contrib import messages
@@ -16,7 +20,7 @@ from django.db import transaction
from django.db.models import Max, Q
from django.http import Http404, HttpResponse, JsonResponse
from django.http.response import HttpResponseRedirect
-from django.shortcuts import redirect
+from django.shortcuts import redirect, get_object_or_404
from django.shortcuts import render
from django.urls import reverse
from django.utils import timezone
@@ -43,7 +47,7 @@ from sapl.relatorios.views import relatorio_doc_administrativos
from sapl.utils import (create_barcode, get_base_url, get_client_ip,
get_mime_type_from_file_extension, lista_anexados,
show_results_filter_set, mail_service_configured, from_date_to_datetime_utc,
- google_recaptcha_configured)
+ google_recaptcha_configured, get_tempfile_dir)
from .forms import (AcompanhamentoDocumentoForm, AnexadoEmLoteFilterSet, AnexadoForm,
AnularProtocoloAdmForm, compara_tramitacoes_doc,
@@ -58,7 +62,7 @@ from .forms import (AcompanhamentoDocumentoForm, AnexadoEmLoteFilterSet, Anexado
from .models import (Anexado, AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo, StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo, TramitacaoAdministrativo)
-
+from ..settings import MEDIA_ROOT
TipoDocumentoAdministrativoCrud = CrudAux.build(
TipoDocumentoAdministrativo, '')
@@ -118,6 +122,71 @@ def doc_texto_integral(request, pk):
raise Http404
+def get_pdf_docacessorios(request, pk):
+ documento_administrativo = get_object_or_404(DocumentoAdministrativo, pk=pk)
+ logger = logging.getLogger(__name__)
+ username = 'Usuário anônimo' if request.user.is_anonymous else request.user.username
+ try:
+ external_name, data = create_pdf_docacessorios(documento_administrativo)
+ logger.info(
+ "user= {}. Gerou o pdf compilado de documento acessorios".format(username))
+ except FileNotFoundError:
+ logger.error("user= {}.Não há arquivos cadastrados".format(username))
+ msg = _('Não há arquivos cadastrados nesses documentos acessórios.')
+ messages.add_message(request, messages.ERROR, msg)
+ return redirect(reverse('sapl.materia:documentoacessorio_list',
+ kwargs={'pk': pk}))
+ except Exception as e:
+ logger.error("user= {}.Um erro inesperado ocorreu na criação do pdf de documentos acessorios: {}"
+ .format(username, str(e)))
+ msg = _('Um erro inesperado ocorreu. Entre em contato com o suporte do SAPL.')
+ messages.add_message(request, messages.ERROR, msg)
+ return redirect(reverse('sapl.materia:documentoacessorio_list',
+ kwargs={'pk': pk}))
+
+ if not data:
+ msg = _('Não há nenhum documento acessório PDF cadastrado.')
+ messages.add_message(request, messages.ERROR, msg)
+ return redirect(reverse('sapl.materia:documentoacessorio_list',
+ kwargs={'pk': pk}))
+
+ response = HttpResponse(data, content_type='application/pdf')
+ response['Content-Disposition'] = ('attachment; filename="%s"'
+ % external_name)
+ return response
+
+
+def create_pdf_docacessorios(docadministrativo):
+ """
+ Creates a unified in memory PDF file
+ """
+ logger = logging.getLogger(__name__)
+ docs = docadministrativo.documentoacessorioadministrativo_set. \
+ all().values_list('arquivo', flat=True)
+ if not docs:
+ return None, None
+
+ docs_path = [os.path.join(MEDIA_ROOT, i)
+ for i in docs if i.lower().endswith('pdf')]
+ if not docs_path:
+ raise FileNotFoundError(
+ "Não há arquivos PDF cadastrados em documentos acessorios.")
+ logger.info("Gerando compilado PDF de documentos acessorios com {} documentos"
+ .format(docs_path))
+
+ merger = PdfFileMerger()
+ for f in docs_path:
+ merger.append(fileobj=f)
+
+ data = BytesIO()
+ merger.write(data)
+ merger.close()
+
+ external_name = "docadm_{}_{}_docacessorios.pdf".format(
+ docadministrativo.numero, docadministrativo.ano)
+ return external_name, data.getvalue()
+
+
class AcompanhamentoConfirmarView(TemplateView):
logger = logging.getLogger(__name__)
diff --git a/sapl/relatorios/forms.py b/sapl/relatorios/forms.py
new file mode 100644
index 000000000..59eb2d741
--- /dev/null
+++ b/sapl/relatorios/forms.py
@@ -0,0 +1,783 @@
+import django_filters
+from crispy_forms.bootstrap import (FormActions)
+from crispy_forms.layout import (HTML, Button, Fieldset,
+ Layout, Submit)
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from sapl.audiencia.models import AudienciaPublica
+from sapl.base.models import Autor
+from sapl.comissoes.models import Reuniao
+from sapl.crispy_layout_mixin import SaplFormHelper, to_row, form_actions
+from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, MateriaEmTramitacao, UnidadeTramitacao, \
+ StatusTramitacao
+from sapl.norma.models import NormaJuridica
+from sapl.protocoloadm.models import DocumentoAdministrativo
+from sapl.sessao.models import SessaoPlenaria
+from sapl.utils import FilterOverridesMetaMixin, choice_anos_com_normas, qs_override_django_filter, \
+ choice_anos_com_materias, choice_tipos_normas, autor_label, autor_modal
+
+
+class RelatorioDocumentosAcessoriosFilterSet(django_filters.FilterSet):
+
+ @property
+ def qs(self):
+ parent = super(RelatorioDocumentosAcessoriosFilterSet, self).qs
+ return parent.distinct().order_by('-data')
+
+ class Meta(FilterOverridesMetaMixin):
+ model = DocumentoAcessorio
+ fields = ['tipo', 'materia__tipo', 'data']
+
+ def __init__(self, *args, **kwargs):
+ super(
+ RelatorioDocumentosAcessoriosFilterSet, self
+ ).__init__(*args, **kwargs)
+
+ self.filters['tipo'].label = 'Tipo de Documento'
+ self.filters['materia__tipo'].label = 'Tipo de Matéria do Documento'
+ self.filters['data'].label = 'Período (Data Inicial - Data Final)'
+
+ self.form.fields['tipo'].required = True
+
+ row0 = to_row([('tipo', 6),
+ ('materia__tipo', 6)])
+
+ row1 = to_row([('data', 12)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Pesquisa'),
+ row0, row1,
+ buttons)
+ )
+
+
+class RelatorioAtasFilterSet(django_filters.FilterSet):
+ class Meta(FilterOverridesMetaMixin):
+ model = SessaoPlenaria
+ fields = ['data_inicio']
+
+ @property
+ def qs(self):
+ parent = super(RelatorioAtasFilterSet, self).qs
+ return parent.distinct().prefetch_related('tipo').exclude(
+ upload_ata='').order_by('-data_inicio', 'tipo', 'numero')
+
+ def __init__(self, *args, **kwargs):
+ super(RelatorioAtasFilterSet, self).__init__(
+ *args, **kwargs)
+
+ self.filters['data_inicio'].label = 'Período de Abertura (Inicial - Final)'
+ self.form.fields['data_inicio'].required = False
+
+ row1 = to_row([('data_inicio', 12)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Atas das Sessões Plenárias'),
+ row1, buttons, )
+ )
+
+
+def ultimo_ano_com_norma():
+ anos_normas = choice_anos_com_normas()
+
+ if anos_normas:
+ return anos_normas[0]
+ return ''
+
+
+class RelatorioNormasMesFilterSet(django_filters.FilterSet):
+ ano = django_filters.ChoiceFilter(required=True,
+ label='Ano da Norma',
+ choices=choice_anos_com_normas,
+ initial=ultimo_ano_com_norma)
+
+ tipo = django_filters.ChoiceFilter(required=False,
+ label='Tipo Norma',
+ choices=choice_tipos_normas,
+ initial=0)
+
+ class Meta:
+ model = NormaJuridica
+ fields = ['ano']
+
+ def __init__(self, *args, **kwargs):
+ super(RelatorioNormasMesFilterSet, self).__init__(
+ *args, **kwargs)
+
+ self.filters['ano'].label = 'Ano'
+ self.form.fields['ano'].required = True
+
+ row1 = to_row([('ano', 6), ('tipo', 6)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Normas por mês do ano.'),
+ row1, buttons, )
+ )
+
+ @property
+ def qs(self):
+ parent = super(RelatorioNormasMesFilterSet, self).qs
+ return parent.distinct().order_by('data')
+
+
+class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
+ class Meta(FilterOverridesMetaMixin):
+ model = SessaoPlenaria
+ fields = ['data_inicio',
+ 'sessao_legislativa',
+ 'tipo',
+ 'legislatura']
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self.form.fields['exibir_ordem_dia'] = forms.BooleanField(
+ required=False, label='Exibir presença das Ordens do Dia')
+ self.form.initial['exibir_ordem_dia'] = True
+
+ self.form.fields['exibir_somente_titular'] = forms.BooleanField(
+ required=False, label='Exibir somente parlamentares titulares')
+ self.form.initial['exibir_somente_titular'] = False
+
+ self.form.fields['exibir_somente_ativo'] = forms.BooleanField(
+ required=False, label='Exibir somente parlamentares ativos')
+ self.form.initial['exibir_somente_ativo'] = False
+
+ self.form.fields['legislatura'].required = True
+
+ self.filters['data_inicio'].label = 'Período (Inicial - Final)'
+
+ tipo_sessao_ordinaria = self.filters['tipo'].queryset.filter(
+ nome='Ordinária')
+ if tipo_sessao_ordinaria:
+ self.form.initial['tipo'] = tipo_sessao_ordinaria.first()
+
+ row1 = to_row([('legislatura', 4),
+ ('sessao_legislativa', 4),
+ ('tipo', 4)])
+ row2 = to_row([('exibir_ordem_dia', 12),
+ ('exibir_somente_titular', 12),
+ ('exibir_somente_ativo', 12)])
+ row3 = to_row([('data_inicio', 12)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Presença dos parlamentares nas sessões plenárias'),
+ row1, row2, row3, buttons, )
+ )
+
+ @property
+ def qs(self):
+ return qs_override_django_filter(self)
+
+
+class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
+ autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
+
+ @property
+ def qs(self):
+ parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs
+ return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
+
+ class Meta(FilterOverridesMetaMixin):
+ model = MateriaLegislativa
+ fields = ['tipo', 'tramitacao__status', 'tramitacao__data_tramitacao',
+ 'tramitacao__unidade_tramitacao_local', 'tramitacao__unidade_tramitacao_destino']
+
+ def __init__(self, *args, **kwargs):
+ super(RelatorioHistoricoTramitacaoFilterSet, self).__init__(
+ *args, **kwargs)
+
+ self.filters['tipo'].label = 'Tipo de Matéria'
+ self.filters['tramitacao__status'].label = _('Status')
+ self.filters['tramitacao__unidade_tramitacao_local'].label = _(
+ 'Unidade Local (Origem)')
+ self.filters['tramitacao__unidade_tramitacao_destino'].label = _(
+ 'Unidade Destino')
+
+ row1 = to_row([('tramitacao__data_tramitacao', 12)])
+ row2 = to_row([('tramitacao__unidade_tramitacao_local', 6),
+ ('tramitacao__unidade_tramitacao_destino', 6)])
+ row3 = to_row(
+ [('tipo', 6),
+ ('tramitacao__status', 6)])
+
+ row4 = to_row([
+ ('autoria__autor', 0),
+ (Button('pesquisar',
+ 'Pesquisar Autor',
+ css_class='btn btn-primary btn-sm'), 2),
+ (Button('limpar',
+ 'Limpar Autor',
+ css_class='btn btn-primary btn-sm'), 2)
+ ])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Pesquisar'),
+ row1, row2, row3, row4,
+ HTML(autor_label),
+ HTML(autor_modal),
+ buttons, )
+ )
+
+
+class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
+ materia__ano = django_filters.ChoiceFilter(required=False,
+ label='Ano da Matéria',
+ choices=choice_anos_com_materias)
+ # materia__autores = django_filters.CharFilter(widget=forms.HiddenInput())
+ materia__autores = django_filters.ModelChoiceFilter(
+ label='Autor da Matéria',
+ queryset=Autor.objects.all(), field_name='materia__autores')
+
+ @property
+ def qs(self):
+ parent = super(RelatorioDataFimPrazoTramitacaoFilterSet, self).qs
+ return parent.distinct().prefetch_related('materia__tipo').order_by('tramitacao__data_fim_prazo', 'materia__tipo', 'materia__numero')
+
+ class Meta(FilterOverridesMetaMixin):
+ model = MateriaEmTramitacao
+ fields = ['materia__tipo',
+ 'tramitacao__unidade_tramitacao_local',
+ 'tramitacao__unidade_tramitacao_destino',
+ 'tramitacao__status',
+ 'tramitacao__data_fim_prazo',
+ 'materia__autores']
+
+ def __init__(self, *args, **kwargs):
+ super(RelatorioDataFimPrazoTramitacaoFilterSet, self).__init__(
+ *args, **kwargs)
+
+ self.filters['materia__tipo'].label = 'Tipo de Matéria'
+ self.filters[
+ 'tramitacao__unidade_tramitacao_local'].label = 'Unidade Local (Origem)'
+ self.filters['tramitacao__unidade_tramitacao_destino'].label = 'Unidade Destino'
+ self.filters['tramitacao__status'].label = 'Status de tramitação'
+ self.filters['materia__autores'].label = 'Autor da Matéria'
+
+ row1 = to_row([('materia__ano', 12)])
+ row2 = to_row([('tramitacao__data_fim_prazo', 12)])
+ row3 = to_row([('tramitacao__unidade_tramitacao_local', 6),
+ ('tramitacao__unidade_tramitacao_destino', 6)])
+ row4 = to_row(
+ [('materia__tipo', 6),
+ ('tramitacao__status', 6)])
+ row5 = to_row([('materia__autores', 12)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Tramitações'),
+ row1, row2, row3, row4, row5,
+ buttons, )
+ )
+
+
+class RelatorioReuniaoFilterSet(django_filters.FilterSet):
+
+ @property
+ def qs(self):
+ parent = super(RelatorioReuniaoFilterSet, self).qs
+ return parent.distinct().order_by('-data', 'comissao')
+
+ class Meta:
+ model = Reuniao
+ fields = ['comissao', 'data',
+ 'nome', 'tema']
+
+ def __init__(self, *args, **kwargs):
+ super(RelatorioReuniaoFilterSet, self).__init__(
+ *args, **kwargs)
+
+ row1 = to_row([('data', 12)])
+ row2 = to_row(
+ [('comissao', 4),
+ ('nome', 4),
+ ('tema', 4)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Reunião de Comissão'),
+ row1, row2,
+ buttons, )
+ )
+
+
+class RelatorioAudienciaFilterSet(django_filters.FilterSet):
+
+ @property
+ def qs(self):
+ parent = super(RelatorioAudienciaFilterSet, self).qs
+ return parent.distinct().order_by('-data', 'tipo')
+
+ class Meta:
+ model = AudienciaPublica
+ fields = ['tipo', 'data',
+ 'nome']
+
+ def __init__(self, *args, **kwargs):
+ super(RelatorioAudienciaFilterSet, self).__init__(
+ *args, **kwargs)
+
+ row1 = to_row([('data', 12)])
+ row2 = to_row(
+ [('tipo', 4),
+ ('nome', 4)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Audiência Pública'),
+ row1, row2,
+ buttons, )
+ )
+
+
+class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet):
+ materia__ano = django_filters.ChoiceFilter(required=True,
+ label='Ano da Matéria',
+ choices=choice_anos_com_materias)
+
+ tramitacao__unidade_tramitacao_destino = django_filters.ModelChoiceFilter(
+ queryset=UnidadeTramitacao.objects.all(),
+ label=_('Unidade Atual'))
+
+ tramitacao__status = django_filters.ModelChoiceFilter(
+ queryset=StatusTramitacao.objects.all(),
+ label=_('Status Atual'))
+
+ materia__autores = django_filters.ModelChoiceFilter(
+ label='Autor da Matéria',
+ queryset=Autor.objects.all())
+
+ @property
+ def qs(self):
+ parent = super(RelatorioMateriasTramitacaoFilterSet, self).qs
+ return parent.distinct().order_by(
+ '-materia__ano', 'materia__tipo', '-materia__numero'
+ )
+
+ class Meta:
+ model = MateriaEmTramitacao
+ fields = ['materia__ano', 'materia__tipo',
+ 'tramitacao__unidade_tramitacao_destino',
+ 'tramitacao__status', 'materia__autores']
+
+ def __init__(self, *args, **kwargs):
+ super(RelatorioMateriasTramitacaoFilterSet, self).__init__(
+ *args, **kwargs)
+
+ self.filters['materia__tipo'].label = 'Tipo de Matéria'
+
+ row1 = to_row([('materia__ano', 12)])
+ row2 = to_row([('materia__tipo', 12)])
+ row3 = to_row([('tramitacao__unidade_tramitacao_destino', 12)])
+ row4 = to_row([('tramitacao__status', 12)])
+ row5 = to_row([('materia__autores', 12)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Pesquisa de Matéria em Tramitação'),
+ row1, row2, row3, row4, row5,
+ buttons, )
+ )
+
+
+class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
+ ano = django_filters.ChoiceFilter(required=True,
+ label='Ano da Matéria',
+ choices=choice_anos_com_materias)
+
+ class Meta:
+ model = MateriaLegislativa
+ fields = ['ano']
+
+ def __init__(self, *args, **kwargs):
+ super(RelatorioMateriasPorAnoAutorTipoFilterSet, self).__init__(
+ *args, **kwargs)
+
+ row1 = to_row(
+ [('ano', 12)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Pesquisa de Matéria por Ano Autor Tipo'),
+ row1,
+ buttons, )
+ )
+
+
+class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
+ autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
+
+ @property
+ def qs(self):
+ parent = super().qs
+ return parent.distinct().order_by('-ano', '-numero', 'tipo', 'autoria__autor', '-autoria__primeiro_autor')
+
+ class Meta(FilterOverridesMetaMixin):
+ model = MateriaLegislativa
+ fields = ['tipo', 'data_apresentacao']
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self.filters['tipo'].label = 'Tipo de Matéria'
+
+ row1 = to_row(
+ [('tipo', 12)])
+ row2 = to_row(
+ [('data_apresentacao', 12)])
+ row3 = to_row(
+ [('autoria__autor', 0),
+ (Button('pesquisar',
+ 'Pesquisar Autor',
+ css_class='btn btn-primary btn-sm'), 2),
+ (Button('limpar',
+ 'Limpar Autor',
+ css_class='btn btn-primary btn-sm'), 10)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Pesquisa de Matéria por Autor'),
+ row1, row2,
+ HTML(autor_label),
+ HTML(autor_modal),
+ row3,
+ buttons, )
+ )
+
+
+class RelatorioHistoricoTramitacaoAdmFilterSet(django_filters.FilterSet):
+
+ @property
+ def qs(self):
+ parent = super(RelatorioHistoricoTramitacaoAdmFilterSet, self).qs
+ return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
+
+ class Meta(FilterOverridesMetaMixin):
+ model = DocumentoAdministrativo
+ fields = ['tipo', 'tramitacaoadministrativo__status',
+ 'tramitacaoadministrativo__data_tramitacao',
+ 'tramitacaoadministrativo__unidade_tramitacao_local',
+ 'tramitacaoadministrativo__unidade_tramitacao_destino']
+
+ def __init__(self, *args, **kwargs):
+ super(RelatorioHistoricoTramitacaoAdmFilterSet, self).__init__(
+ *args, **kwargs)
+
+ self.filters['tipo'].label = 'Tipo de Documento'
+ self.filters['tramitacaoadministrativo__status'].label = _('Status')
+ self.filters['tramitacaoadministrativo__unidade_tramitacao_local'].label = _(
+ 'Unidade Local (Origem)')
+ self.filters['tramitacaoadministrativo__unidade_tramitacao_destino'].label = _(
+ 'Unidade Destino')
+
+ row1 = to_row([('tramitacaoadministrativo__data_tramitacao', 12)])
+ row2 = to_row([('tramitacaoadministrativo__unidade_tramitacao_local', 6),
+ ('tramitacaoadministrativo__unidade_tramitacao_destino', 6)])
+ row3 = to_row(
+ [('tipo', 6),
+ ('tramitacaoadministrativo__status', 6)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_(''),
+ row1, row2, row3,
+ buttons, )
+ )
+
+
+class RelatorioNormasPorAutorFilterSet(django_filters.FilterSet):
+ autorianorma__autor = django_filters.CharFilter(widget=forms.HiddenInput())
+
+ @property
+ def qs(self):
+ parent = super().qs
+ return parent.distinct().filter(autorianorma__primeiro_autor=True) \
+ .order_by('autorianorma__autor', '-autorianorma__primeiro_autor', 'tipo', '-ano', '-numero')
+
+ class Meta(FilterOverridesMetaMixin):
+ model = NormaJuridica
+ fields = ['tipo', 'data']
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self.filters['tipo'].label = 'Tipo de Norma'
+
+ row1 = to_row(
+ [('tipo', 12)])
+ row2 = to_row(
+ [('data', 12)])
+ row3 = to_row(
+ [('autorianorma__autor', 0),
+ (Button('pesquisar',
+ 'Pesquisar Autor',
+ css_class='btn btn-primary btn-sm'), 2),
+ (Button('limpar',
+ 'Limpar Autor',
+ css_class='btn btn-primary btn-sm'), 10)])
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Pesquisar'),
+ row1, row2,
+ HTML(autor_label),
+ HTML(autor_modal),
+ row3,
+ form_actions(label='Pesquisar'))
+ )
+
+
+class RelatorioNormasVigenciaFilterSet(django_filters.FilterSet):
+ ano = django_filters.ChoiceFilter(required=True,
+ label='Ano da Norma',
+ choices=choice_anos_com_normas,
+ initial=ultimo_ano_com_norma)
+
+ tipo = django_filters.ChoiceFilter(required=False,
+ label='Tipo Norma',
+ choices=choice_tipos_normas,
+ initial=0)
+
+ vigencia = forms.ChoiceField(
+ label=_('Vigência'),
+ choices=[(True, "Vigente"), (False, "Não vigente")],
+ widget=forms.RadioSelect(),
+ required=True,
+ initial=True)
+
+ def __init__(self, *args, **kwargs):
+ super(RelatorioNormasVigenciaFilterSet, self).__init__(
+ *args, **kwargs)
+
+ self.filters['ano'].label = 'Ano'
+ self.form.fields['ano'].required = True
+ self.form.fields['vigencia'] = self.vigencia
+
+ row1 = to_row([('ano', 6), ('tipo', 6)])
+ row2 = to_row([('vigencia', 12)])
+
+ buttons = FormActions(
+ *[
+ HTML('''
+
+
+ Gerar relatório PDF
+
+ ''')
+ ],
+ Submit('pesquisar', _('Pesquisar'), css_class='float-right',
+ onclick='return true;'),
+ css_class='form-group row justify-content-between',
+ )
+
+ self.form.helper = SaplFormHelper()
+ self.form.helper.form_method = 'GET'
+ self.form.helper.layout = Layout(
+ Fieldset(_('Normas por vigência.'),
+ row1, row2,
+ buttons, )
+ )
+
+ @property
+ def qs(self):
+ return qs_override_django_filter(self)
diff --git a/sapl/relatorios/urls.py b/sapl/relatorios/urls.py
index 41b1de4f3..b27bfc55a 100644
--- a/sapl/relatorios/urls.py
+++ b/sapl/relatorios/urls.py
@@ -4,9 +4,15 @@ from .apps import AppConfig
from .views import (relatorio_capa_processo,
relatorio_documento_administrativo, relatorio_espelho,
relatorio_etiqueta_protocolo, relatorio_materia,
- relatorio_ordem_dia, relatorio_pauta_sessao,
- relatorio_protocolo, relatorio_sessao_plenaria,
- resumo_ata_pdf, relatorio_sessao_plenaria_pdf, etiqueta_materia_legislativa)
+ relatorio_ordem_dia, relatorio_protocolo, relatorio_sessao_plenaria,
+ resumo_ata_pdf, relatorio_sessao_plenaria_pdf, etiqueta_materia_legislativa,
+ relatorio_materia_tramitacao, RelatoriosListView, RelatorioMateriasPorAutorView,
+ RelatorioNormasPublicadasMesView, RelatorioNormasVigenciaView, RelatorioMateriasPorAnoAutorTipoView,
+ RelatorioMateriasTramitacaoView, RelatorioMateriaAnoAssuntoView, RelatorioHistoricoTramitacaoView,
+ RelatorioDataFimPrazoTramitacaoView, RelatorioPresencaSessaoView, RelatorioAtasView,
+ RelatorioReuniaoView, RelatorioAudienciaView, RelatorioHistoricoTramitacaoAdmView,
+ RelatorioDocumentosAcessoriosView, RelatorioNormasPorAutorView)
+from ..base.views import EstatisticasAcessoNormas
app_name = AppConfig.name
@@ -41,4 +47,54 @@ urlpatterns = [
relatorio_sessao_plenaria_pdf, name='relatorio_sessao_plenaria_pdf'),
url(r'^relatorios/(?P\d+)/etiqueta-materia-legislativa$',
etiqueta_materia_legislativa, name='etiqueta_materia_legislativa'),
-]
+
+ url(r'^relatorios/(?P\d+)/materia-tramitacao$',
+ relatorio_materia_tramitacao, name='relatorio_materia_tramitacao'),
+
+ # TODO mover estas telas para a app 'relatorios'
+ url(r'^sistema/relatorios/$',
+ RelatoriosListView.as_view(), name='relatorios_list'),
+ url(r'^sistema/relatorios/materia-por-autor$',
+ RelatorioMateriasPorAutorView.as_view(), name='materia_por_autor'),
+ url(r'^sistema/relatorios/relatorio-por-mes$',
+ RelatorioNormasPublicadasMesView.as_view(), name='normas_por_mes'),
+ url(r'^sistema/relatorios/relatorio-por-vigencia$',
+ RelatorioNormasVigenciaView.as_view(), name='normas_por_vigencia'),
+ url(r'^sistema/relatorios/estatisticas-acesso$',
+ EstatisticasAcessoNormas.as_view(), name='estatisticas_acesso'),
+ url(r'^sistema/relatorios/materia-por-ano-autor-tipo$',
+ RelatorioMateriasPorAnoAutorTipoView.as_view(),
+ name='materia_por_ano_autor_tipo'),
+ url(r'^sistema/relatorios/materia-por-tramitacao$',
+ RelatorioMateriasTramitacaoView.as_view(),
+ name='materia_por_tramitacao'),
+ url(r'^sistema/relatorios/materia-por-assunto$',
+ RelatorioMateriaAnoAssuntoView.as_view(),
+ name='materia_por_ano_assunto'),
+ url(r'^sistema/relatorios/historico-tramitacoes$',
+ RelatorioHistoricoTramitacaoView.as_view(),
+ name='historico_tramitacoes'),
+ url(r'^sistema/relatorios/data-fim-prazo-tramitacoes$',
+ RelatorioDataFimPrazoTramitacaoView.as_view(),
+ name='data_fim_prazo_tramitacoes'),
+ url(r'^sistema/relatorios/presenca$',
+ RelatorioPresencaSessaoView.as_view(),
+ name='presenca_sessao'),
+ url(r'^sistema/relatorios/atas$',
+ RelatorioAtasView.as_view(),
+ name='atas'),
+ url(r'^sistema/relatorios/reuniao$',
+ RelatorioReuniaoView.as_view(),
+ name='reuniao'),
+ url(r'^sistema/relatorios/audiencia$',
+ RelatorioAudienciaView.as_view(),
+ name='audiencia'),
+ url(r'^sistema/relatorios/historico-tramitacoesadm$',
+ RelatorioHistoricoTramitacaoAdmView.as_view(),
+ name='historico_tramitacoes_adm'),
+ url(r'^sistema/relatorios/documentos_acessorios$',
+ RelatorioDocumentosAcessoriosView.as_view(),
+ name='relatorio_documentos_acessorios'),
+ url(r'^sistema/relatorios/normas-por-autor$',
+ RelatorioNormasPorAutorView.as_view(), name='normas_por_autor'),
+]
\ No newline at end of file
diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py
index 2ad08eea6..98cc47442 100755
--- a/sapl/relatorios/views.py
+++ b/sapl/relatorios/views.py
@@ -1,32 +1,44 @@
-from datetime import datetime as dt
+import collections
import html
import logging
import re
-import tempfile
+from datetime import datetime as dt, datetime
+import unidecode
from django.core.exceptions import ObjectDoesNotExist
+from django.db.models import Count, Q, F
from django.http import Http404, HttpResponse
from django.template.loader import render_to_string
from django.utils import timezone
-from django.utils.html import strip_tags
from django.utils.translation import ugettext_lazy as _
-import unidecode
+from django.views.generic import TemplateView, ListView
+from django_filters.views import FilterView
from weasyprint import HTML, CSS
-from sapl.base.models import Autor, CasaLegislativa, AppConfig as SaplAppConfig
-from sapl.comissoes.models import Comissao
+from sapl.audiencia.models import TipoAudienciaPublica, AudienciaPublica
+from sapl.base.models import Autor, CasaLegislativa, AppConfig as SaplAppConfig, AppConfig
+from sapl.comissoes.models import Comissao, Reuniao
from sapl.materia.models import (Autoria, MateriaLegislativa, Numeracao,
- Tramitacao, UnidadeTramitacao, ConfigEtiquetaMateriaLegislativa)
-from sapl.parlamentares.models import CargoMesa, Filiacao, Parlamentar
+ Tramitacao, UnidadeTramitacao, ConfigEtiquetaMateriaLegislativa, MateriaAssunto,
+ TipoMateriaLegislativa, MateriaEmTramitacao, DocumentoAcessorio, TipoDocumento,
+ StatusTramitacao)
+from sapl.norma.models import TipoNormaJuridica, NormaJuridica
+from sapl.parlamentares.models import Filiacao, Parlamentar, SessaoLegislativa, Legislatura
from sapl.protocoloadm.models import (DocumentoAdministrativo, Protocolo,
- TramitacaoAdministrativo)
+ TramitacaoAdministrativo, StatusTramitacaoAdministrativo,
+ TipoDocumentoAdministrativo)
+from sapl.relatorios.forms import RelatorioNormasPorAutorFilterSet, RelatorioHistoricoTramitacaoAdmFilterSet, \
+ RelatorioNormasVigenciaFilterSet, RelatorioNormasMesFilterSet, RelatorioMateriasPorAutorFilterSet, \
+ RelatorioMateriasPorAnoAutorTipoFilterSet, RelatorioMateriasTramitacaoFilterSet, RelatorioAudienciaFilterSet, \
+ RelatorioReuniaoFilterSet, RelatorioDataFimPrazoTramitacaoFilterSet, RelatorioHistoricoTramitacaoFilterSet, \
+ RelatorioPresencaSessaoFilterSet, RelatorioAtasFilterSet, RelatorioDocumentosAcessoriosFilterSet
from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao,
IntegranteMesa, JustificativaAusencia,
Orador, OradorExpediente,
OrdemDia, PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, OcorrenciaSessao,
RegistroVotacao, VotoParlamentar, OradorOrdemDia,
- ConsideracoesFinais, TipoExpediente, ResumoOrdenacao)
+ ConsideracoesFinais, ResumoOrdenacao, TipoSessaoPlenaria)
from sapl.sessao.views import (get_identificacao_basica, get_mesa_diretora,
get_presenca_sessao, get_expedientes,
get_materias_expediente, get_oradores_expediente,
@@ -37,13 +49,14 @@ from sapl.sessao.views import (get_identificacao_basica, get_mesa_diretora,
get_correspondencias)
from sapl.settings import MEDIA_URL
from sapl.settings import STATIC_ROOT
-from sapl.utils import LISTA_DE_UFS, TrocaTag, filiacao_data, create_barcode
-
+from sapl.utils import LISTA_DE_UFS, TrocaTag, filiacao_data, create_barcode, show_results_filter_set, \
+ num_materias_por_tipo, parlamentares_ativos
from .templates import (pdf_capa_processo_gerar,
pdf_documento_administrativo_gerar, pdf_espelho_gerar,
pdf_etiqueta_protocolo_gerar, pdf_materia_gerar,
pdf_ordem_dia_gerar, pdf_pauta_sessao_gerar,
pdf_protocolo_gerar, pdf_sessao_plenaria_gerar)
+from sapl.crud.base import make_pagination
def get_kwargs_params(request, fields):
@@ -113,7 +126,7 @@ def get_materias(mats):
for materia in mats:
dic = {}
dic['titulo'] = materia.tipo.sigla + " " + materia.tipo.descricao \
- + " " + str(materia.numero) + "/" + str(materia.ano)
+ + " " + str(materia.numero) + "/" + str(materia.ano)
dic['txt_ementa'] = materia.ementa
dic['nom_autor'] = ', '.join(
@@ -533,9 +546,9 @@ def get_sessao_plenaria(sessao, casa, user):
# Lista da composicao da mesa diretora
lst_mesa = []
- for composicao in IntegranteMesa.objects.select_related('parlamentar', 'cargo')\
- .filter(sessao_plenaria=sessao)\
- .order_by('cargo_id'):
+ for composicao in IntegranteMesa.objects.select_related('parlamentar', 'cargo') \
+ .filter(sessao_plenaria=sessao) \
+ .order_by('cargo_id'):
partido_sigla = Filiacao.objects.filter(
parlamentar=composicao.parlamentar).first()
sigla = '' if not partido_sigla else partido_sigla.partido.sigla
@@ -647,7 +660,7 @@ def get_sessao_plenaria(sessao, casa, user):
materia=expediente_materia.materia).first()
if numeracao:
dic_expediente_materia["des_numeracao"] = (
- str(numeracao.numero_materia) + '/' + str(numeracao.ano_materia))
+ str(numeracao.numero_materia) + '/' + str(numeracao.ano_materia))
autoria = materia.autoria_set.all()
dic_expediente_materia['num_autores'] = 'Autores' if len(
@@ -691,8 +704,8 @@ def get_sessao_plenaria(sessao, casa, user):
# Lista dos votos nominais das matérias do Expediente
lst_expediente_materia_vot_nom = []
- materias_expediente_votacao_nominal = ExpedienteMateria.objects.filter(sessao_plenaria=sessao, tipo_votacao=2)\
- .order_by('-materia')
+ materias_expediente_votacao_nominal = ExpedienteMateria.objects.filter(sessao_plenaria=sessao, tipo_votacao=2) \
+ .order_by('-materia')
for mevn in materias_expediente_votacao_nominal:
votos_materia = []
@@ -724,8 +737,8 @@ def get_sessao_plenaria(sessao, casa, user):
# Lista presença na ordem do dia
lst_presenca_ordem_dia = []
- presenca_ordem_dia = PresencaOrdemDia.objects.filter(sessao_plenaria=sessao)\
- .order_by('parlamentar__nome_parlamentar')
+ presenca_ordem_dia = PresencaOrdemDia.objects.filter(sessao_plenaria=sessao) \
+ .order_by('parlamentar__nome_parlamentar')
for parlamentar in [p.parlamentar for p in presenca_ordem_dia]:
lst_presenca_ordem_dia.append({
"nom_parlamentar": parlamentar.nome_parlamentar,
@@ -741,17 +754,17 @@ def get_sessao_plenaria(sessao, casa, user):
"nom_resultado": '',
"num_ordem": votacao.numero_ordem,
"id_materia": (
- materia.tipo.sigla + ' ' +
- materia.tipo.descricao + ' ' +
- str(materia.numero) + '/' +
- str(materia.ano)),
+ materia.tipo.sigla + ' ' +
+ materia.tipo.descricao + ' ' +
+ str(materia.numero) + '/' +
+ str(materia.ano)),
"des_numeracao": ' '
}
numeracao = materia.numeracao_set.first()
if numeracao:
dic_votacao["des_numeracao"] = (
- str(numeracao.numero_materia) + '/' + str(numeracao.ano_materia))
+ str(numeracao.numero_materia) + '/' + str(numeracao.ano_materia))
materia_em_tramitacao = materia.materiaemtramitacao_set.first()
dic_votacao.update({
@@ -804,8 +817,8 @@ def get_sessao_plenaria(sessao, casa, user):
# Lista dos votos nominais das matérias da Ordem do Dia
lst_votacao_vot_nom = []
- materias_ordem_dia_votacao_nominal = OrdemDia.objects.filter(sessao_plenaria=sessao, tipo_votacao=2)\
- .order_by('-materia')
+ materias_ordem_dia_votacao_nominal = OrdemDia.objects.filter(sessao_plenaria=sessao, tipo_votacao=2) \
+ .order_by('-materia')
for modvn in materias_ordem_dia_votacao_nominal:
votos_materia_od = []
@@ -1015,10 +1028,10 @@ def get_protocolos(prots):
ts = timezone.localtime(protocolo.timestamp)
if protocolo.timestamp:
dic['data'] = ts.strftime("%d/%m/%Y") + ' - Horário: ' + \
- ts.strftime("%H:%m")
+ ts.strftime("%H:%m")
else:
dic['data'] = protocolo.data.strftime("%d/%m/%Y") + ' - Horário: ' \
- + protocolo.hora.strftime("%H:%m")
+ + protocolo.hora.strftime("%H:%m")
dic['txt_assunto'] = protocolo.assunto_ementa
@@ -1146,7 +1159,7 @@ def get_etiqueta_protocolos(prots):
for materia in MateriaLegislativa.objects.filter(
numero_protocolo=p.numero, ano=p.ano):
dic['num_materia'] = materia.tipo.sigla + ' ' + \
- str(materia.numero) + '/' + str(materia.ano)
+ str(materia.numero) + '/' + str(materia.ano)
dic['natureza'] = ''
if p.tipo_processo == 0:
@@ -1158,7 +1171,7 @@ def get_etiqueta_protocolos(prots):
for documento in DocumentoAdministrativo.objects.filter(
protocolo=p):
dic['num_documento'] = documento.tipo.sigla + ' ' + \
- str(documento.numero) + '/' + str(documento.ano)
+ str(documento.numero) + '/' + str(documento.ano)
dic['ident_processo'] = dic['num_materia'] or dic['num_documento']
@@ -1224,7 +1237,7 @@ def get_pauta_sessao(sessao, casa):
dic_expediente_materia = {}
dic_expediente_materia["tipo_materia"] = materia.tipo.sigla + \
- ' - ' + materia.tipo.descricao
+ ' - ' + materia.tipo.descricao
dic_expediente_materia["num_ordem"] = str(
expediente_materia.numero_ordem)
dic_expediente_materia["id_materia"] = str(
@@ -1268,7 +1281,7 @@ def get_pauta_sessao(sessao, casa):
id=votacao.materia.id).first()
dic_votacao = {}
dic_votacao["tipo_materia"] = materia.tipo.sigla + \
- ' - ' + materia.tipo.descricao
+ ' - ' + materia.tipo.descricao
dic_votacao["num_ordem"] = votacao.numero_ordem
dic_votacao["id_materia"] = str(
materia.numero) + "/" + str(materia.ano)
@@ -1696,3 +1709,1028 @@ def etiqueta_materia_legislativa(request, pk):
response.write(pdf_file)
return response
+
+
+def relatorio_materia_tramitacao(request, pk):
+ base_url = request.build_absolute_uri()
+ materia_legislativa = MateriaLegislativa.objects.get(pk=pk)
+ tramitacoes = Tramitacao.objects.filter(materia=materia_legislativa)
+ casa = CasaLegislativa.objects.first()
+ rodape = ' '.join(get_rodape(casa))
+
+ context = {}
+ context.update(
+ {'object': tramitacoes,
+ 'materia': materia_legislativa,
+ 'ano': materia_legislativa.ano,
+ 'numero': materia_legislativa.numero,
+ 'autor': materia_legislativa.autores.first(),
+ 'tipo': materia_legislativa.tipo.descricao,
+ 'rodape': rodape,
+ 'data': dt.today().strftime('%d/%m/%Y'),
+ 'rodape': rodape})
+ header_context = {"casa": casa,
+ 'logotipo': casa.logotipo, 'MEDIA_URL': MEDIA_URL}
+
+ html_template = render_to_string(
+ 'relatorios/relatorio_materia_tramitacao.html', context)
+ html_header = render_to_string(
+ 'relatorios/header_ata.html', header_context)
+
+ pdf_file = make_pdf(
+ base_url=base_url, main_template=html_template, header_template=html_header)
+
+ response = HttpResponse(content_type='application/pdf;')
+ response['Content-Disposition'] = 'inline; filename=relatorio.pdf'
+ response['Content-Transfer-Encoding'] = 'binary'
+ response.write(pdf_file)
+
+ return response
+
+
+class RelatoriosListView(TemplateView):
+ template_name = 'relatorios/relatorios_list.html'
+
+ def get_context_data(self, **kwargs):
+ context = super(TemplateView, self).get_context_data(**kwargs)
+ estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas
+ context['estatisticas_acesso_normas'] = True if estatisticas_acesso_normas == 'S' else False
+
+ return context
+
+
+class RelatorioMixin:
+ # TODO: verificar se todos os relatorios de sistema/relatorios extendem esse Mixin
+ def get(self, request, *args, **kwargs):
+ super(RelatorioMixin, self).get(request)
+
+ # TODO: import as global
+ from sapl.utils import is_report_allowed
+ if not is_report_allowed(request):
+ raise Http404()
+
+ is_relatorio = request.GET.get('relatorio')
+ context = self.get_context_data(filter=self.filterset)
+
+ if is_relatorio:
+ return self.relatorio(request, context)
+ else:
+ return self.render_to_response(context)
+
+
+class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView):
+ model = DocumentoAcessorio
+ filterset_class = RelatorioDocumentosAcessoriosFilterSet
+ template_name = 'relatorios/RelatorioDocumentosAcessorios_filter.html'
+ relatorio = relatorio_documento_acessorio
+
+ def get_context_data(self, **kwargs):
+ context = super(
+ RelatorioDocumentosAcessoriosView, self
+ ).get_context_data(**kwargs)
+
+ context['title'] = _('Documentos Acessórios das Matérias Legislativas')
+
+ if not self.filterset.form.is_valid():
+ return context
+
+ query_dict = self.request.GET.copy()
+ context['show_results'] = show_results_filter_set(query_dict)
+
+ context['tipo_documento'] = str(
+ TipoDocumento.objects.get(pk=self.request.GET['tipo'])
+ )
+
+ tipo_materia = self.request.GET['materia__tipo']
+ if tipo_materia:
+ context['tipo_materia'] = str(
+ TipoMateriaLegislativa.objects.get(pk=tipo_materia)
+ )
+ else:
+ context['tipo_materia'] = "Não selecionado"
+
+ data_inicial = self.request.GET['data_0']
+ data_final = self.request.GET['data_1']
+ if not data_inicial:
+ data_inicial = "Data Inicial não definida"
+ if not data_final:
+ data_final = "Data Final não definida"
+ context['periodo'] = (
+ data_inicial + ' - ' + data_final
+ )
+
+ return context
+
+
+class RelatorioAtasView(RelatorioMixin, FilterView):
+ model = SessaoPlenaria
+ filterset_class = RelatorioAtasFilterSet
+ template_name = 'relatorios/RelatorioAtas_filter.html'
+ relatorio = relatorio_atas
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['title'] = _('Atas das Sessões Plenárias')
+
+ # Verifica se os campos foram preenchidos
+ if not self.filterset.form.is_valid():
+ return context
+
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+ context['periodo'] = (
+ self.request.GET['data_inicio_0'] +
+ ' - ' + self.request.GET['data_inicio_1'])
+
+ return context
+
+
+class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
+ logger = logging.getLogger(__name__)
+ model = SessaoPlenaria
+ filterset_class = RelatorioPresencaSessaoFilterSet
+ template_name = 'relatorios/RelatorioPresencaSessao_filter.html'
+ relatorio = relatorio_presenca_sessao
+
+ def get_context_data(self, **kwargs):
+
+ context = super().get_context_data(**kwargs)
+ context['title'] = _('Presença dos parlamentares nas sessões')
+
+ # Verifica se os campos foram preenchidos
+ if not self.filterset.form.is_valid():
+ return context
+
+ cd = self.filterset.form.cleaned_data
+ if not cd['data_inicio'] and not cd['sessao_legislativa'] \
+ and not cd['legislatura']:
+ msg = _(
+ "Formulário inválido! Preencha pelo menos algum dos campos Período, Legislatura ou Sessão Legislativa.")
+ messages.error(self.request, msg)
+ return context
+
+ # Caso a data tenha sido preenchida, verifica se foi preenchida
+ # corretamente
+ if self.request.GET.get('data_inicio_0') and not self.request.GET.get('data_inicio_1'):
+ msg = _("Formulário inválido! Preencha a data do Período Final.")
+ messages.error(self.request, msg)
+ return context
+
+ if not self.request.GET.get('data_inicio_0') and self.request.GET.get('data_inicio_1'):
+ msg = _("Formulário inválido! Preencha a data do Período Inicial.")
+ messages.error(self.request, msg)
+ return context
+
+ param0 = {}
+
+ legislatura_pk = self.request.GET.get('legislatura')
+ if legislatura_pk:
+ param0['sessao_plenaria__legislatura_id'] = legislatura_pk
+ legislatura = Legislatura.objects.get(id=legislatura_pk)
+ context['legislatura'] = legislatura
+
+ sessao_legislativa_pk = self.request.GET.get('sessao_legislativa')
+ if sessao_legislativa_pk:
+ param0['sessao_plenaria__sessao_legislativa_id'] = sessao_legislativa_pk
+ sessao_legislativa = SessaoLegislativa.objects.get(
+ id=sessao_legislativa_pk)
+ context['sessao_legislativa'] = sessao_legislativa
+
+ tipo_sessao_plenaria_pk = self.request.GET.get('tipo')
+ context['tipo'] = ''
+ if tipo_sessao_plenaria_pk:
+ param0['sessao_plenaria__tipo_id'] = tipo_sessao_plenaria_pk
+ context['tipo'] = TipoSessaoPlenaria.objects.get(
+ id=tipo_sessao_plenaria_pk)
+
+ _range = []
+
+ if ('data_inicio_0' in self.request.GET) and self.request.GET['data_inicio_0'] and \
+ ('data_inicio_1' in self.request.GET) and self.request.GET['data_inicio_1']:
+ where = context['object_list'].query.where
+ _range = where.children[0].rhs
+
+ elif legislatura_pk and not sessao_legislativa_pk:
+ _range = [legislatura.data_inicio, legislatura.data_fim]
+
+ elif sessao_legislativa_pk:
+ _range = [sessao_legislativa.data_inicio,
+ sessao_legislativa.data_fim]
+
+ param0.update({'sessao_plenaria__data_inicio__range': _range})
+
+ # Parlamentares com Mandato no intervalo de tempo (Ativos)
+ parlamentares_qs = parlamentares_ativos(
+ _range[0], _range[1]).order_by('nome_parlamentar')
+ parlamentares_id = parlamentares_qs.values_list('id', flat=True)
+
+ # Presenças de cada Parlamentar em Sessões
+ presenca_sessao = SessaoPlenariaPresenca.objects.filter(
+ **param0).values_list('parlamentar_id').annotate(sessao_count=Count('id'))
+
+ # Presenças de cada Ordem do Dia
+ presenca_ordem = PresencaOrdemDia.objects.filter(
+ **param0).values_list('parlamentar_id').annotate(sessao_count=Count('id'))
+
+ # Ausencias justificadas
+ ausencia_justificadas = JustificativaAusencia.objects.filter(
+ **param0, ausencia=2).values_list('parlamentar_id')\
+ .annotate(sessao_count=Count('id'))
+
+ total_ordemdia = PresencaOrdemDia.objects.filter(
+ **param0).distinct('sessao_plenaria__id').order_by('sessao_plenaria__id').count()
+
+ total_sessao = context['object_list'].count()
+
+ username = self.request.user.username
+
+ context['exibir_somente_titular'] = self.request.GET.get(
+ 'exibir_somente_titular') == 'on'
+ context['exibir_somente_ativo'] = self.request.GET.get(
+ 'exibir_somente_ativo') == 'on'
+
+ # Completa o dicionario as informacoes parlamentar/sessao/ordem
+ parlamentares_presencas = []
+ for p in parlamentares_qs:
+ parlamentar = {}
+ m = p.mandato_set.filter(Q(data_inicio_mandato__lte=_range[0], data_fim_mandato__gte=_range[1]) |
+ Q(data_inicio_mandato__lte=_range[0], data_fim_mandato__isnull=True) |
+ Q(data_inicio_mandato__gte=_range[0], data_fim_mandato__lte=_range[1]) |
+ # mandato suplente
+ Q(data_inicio_mandato__gte=_range[0], data_fim_mandato__lte=_range[1]))
+
+ m = m.last()
+
+ if not context['exibir_somente_titular'] and not context['exibir_somente_ativo']:
+ parlamentar = {
+ 'parlamentar': p,
+ 'titular': m.titular if m else False,
+ 'sessao_porc': 0,
+ 'ordemdia_porc': 0
+ }
+ elif context['exibir_somente_titular'] and not context['exibir_somente_ativo']:
+ if m and m.titular:
+ parlamentar = {
+ 'parlamentar': p,
+ 'titular': m.titular if m else False,
+ 'sessao_porc': 0,
+ 'ordemdia_porc': 0
+ }
+ else:
+ continue
+ elif not context['exibir_somente_titular'] and context['exibir_somente_ativo']:
+ if p.ativo:
+ parlamentar = {
+ 'parlamentar': p,
+ 'titular': m.titular if m else False,
+ 'sessao_porc': 0,
+ 'ordemdia_porc': 0
+ }
+ else:
+ continue
+ elif context['exibir_somente_titular'] and context['exibir_somente_ativo']:
+ if m and m.titular and p.ativo:
+ parlamentar = {
+ 'parlamentar': p,
+ 'titular': m.titular if m else False,
+ 'sessao_porc': 0,
+ 'ordemdia_porc': 0
+ }
+ else:
+ continue
+ else:
+ continue
+
+ try:
+ self.logger.debug(
+ F'user={username}. Tentando obter presença do parlamentar (pk={p.id}).')
+ sessao_count = presenca_sessao.get(parlamentar_id=p.id)[1]
+ except ObjectDoesNotExist as e:
+ self.logger.error(
+ F'user={username}. Erro ao obter presença do parlamentar (pk={p.id}). Definido como 0. {str(e)}')
+ sessao_count = 0
+ try:
+ # Presenças de cada Ordem do Dia
+ self.logger.info(
+ F'user={username}. Tentando obter PresencaOrdemDia para o parlamentar pk={p.id}.')
+ ordemdia_count = presenca_ordem.get(parlamentar_id=p.id)[1]
+ except ObjectDoesNotExist:
+ self.logger.error(
+ F'user={username}. Erro ao obter PresencaOrdemDia para o parlamentar pk={p.id}. Definido como 0.')
+ ordemdia_count = 0
+ try:
+ self.logger.debug(
+ F'user={username}. Tentando obter ausência justificada do parlamentar (pk={p.id}).')
+ ausencia_justificadas_count = ausencia_justificadas.get(parlamentar_id=p.id)[1]
+ except ObjectDoesNotExist as e:
+ self.logger.error(
+ F'user={username}. Erro ao obter ausência do parlamentar (pk={p.id}). Definido como 0. {str(e)}')
+ ausencia_justificadas_count = 0
+
+ ausencia_count = total_sessao - sessao_count if total_sessao else 0
+ ausencia_porc = round(100 * (1 - sessao_count / total_sessao), 2) if total_sessao else 0
+ # # porcentagem do total de ausencias
+ # ausencia_justificadas_porc = round(100 * ausencias_justificadas_count / ausencia_count, 2)\
+ # if ausencia_count else 0
+
+ # porcentagem do total de sessoes
+ ausencia_justificadas_porc = round(100 * ausencia_justificadas_count / total_sessao, 2) \
+ if total_sessao else 0
+
+ parlamentar.update({
+ 'sessao_count': sessao_count,
+ 'ordemdia_count': ordemdia_count,
+ 'ausencia_count': ausencia_count,
+ 'ausencia_porc': ausencia_porc,
+ 'ausencia_justificada_count': ausencia_justificadas_count,
+ 'ausencia_justificadas_porc': ausencia_justificadas_porc,
+ })
+
+ if total_sessao != 0:
+ parlamentar.update({'sessao_porc': round(
+ sessao_count * 100 / total_sessao, 2)})
+ if total_ordemdia != 0:
+ parlamentar.update({'ordemdia_porc': round(
+ ordemdia_count * 100 / total_ordemdia, 2)})
+
+ parlamentares_presencas.append(parlamentar)
+
+ context['date_range'] = _range
+ context['total_ordemdia'] = total_ordemdia
+ context['total_sessao'] = context['object_list'].count()
+ context['parlamentares'] = parlamentares_presencas
+ context['periodo'] = f"{self.request.GET['data_inicio_0']} - {self.request.GET['data_inicio_1']}"
+ context['sessao_legislativa'] = ''
+ context['legislatura'] = ''
+ context['exibir_ordem'] = self.request.GET.get(
+ 'exibir_ordem_dia') == 'on'
+
+ if sessao_legislativa_pk:
+ context['sessao_legislativa'] = SessaoLegislativa.objects.get(
+ id=sessao_legislativa_pk)
+ if legislatura_pk:
+ context['legislatura'] = Legislatura.objects.get(id=legislatura_pk)
+ # =====================================================================
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+
+ return context
+
+
+class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView):
+ model = MateriaLegislativa
+ filterset_class = RelatorioHistoricoTramitacaoFilterSet
+ template_name = 'relatorios/RelatorioHistoricoTramitacao_filter.html'
+ relatorio = relatorio_historico_tramitacao
+
+ def get_context_data(self, **kwargs):
+ context = super(RelatorioHistoricoTramitacaoView,
+ self).get_context_data(**kwargs)
+ context['title'] = _(
+ 'Histórico de Tramitações de Matérias Legislativas')
+ if not self.filterset.form.is_valid():
+ return context
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+ context['data_tramitacao'] = (self.request.GET['tramitacao__data_tramitacao_0'] + ' - ' +
+ self.request.GET['tramitacao__data_tramitacao_1'])
+ if self.request.GET['tipo']:
+ tipo = self.request.GET['tipo']
+ context['tipo'] = (
+ str(TipoMateriaLegislativa.objects.get(id=tipo)))
+ else:
+ context['tipo'] = ''
+
+ if self.request.GET['tramitacao__status']:
+ tramitacao_status = self.request.GET['tramitacao__status']
+ context['tramitacao__status'] = (
+ str(StatusTramitacao.objects.get(id=tramitacao_status)))
+ else:
+ context['tramitacao__status'] = ''
+
+ if self.request.GET['tramitacao__unidade_tramitacao_local']:
+ context['tramitacao__unidade_tramitacao_local'] = \
+ (str(UnidadeTramitacao.objects.get(
+ id=self.request.GET['tramitacao__unidade_tramitacao_local'])))
+ else:
+ context['tramitacao__unidade_tramitacao_local'] = ''
+
+ if self.request.GET['tramitacao__unidade_tramitacao_destino']:
+ context['tramitacao__unidade_tramitacao_destino'] = \
+ (str(UnidadeTramitacao.objects.get(
+ id=self.request.GET['tramitacao__unidade_tramitacao_destino'])))
+ else:
+ context['tramitacao__unidade_tramitacao_destino'] = ''
+
+ if self.request.GET['autoria__autor']:
+ context['autoria__autor'] = \
+ (str(Autor.objects.get(
+ id=self.request.GET['autoria__autor'])))
+ else:
+ context['autoria__autor'] = ''
+
+ return context
+
+
+class RelatorioDataFimPrazoTramitacaoView(RelatorioMixin, FilterView):
+ model = MateriaEmTramitacao
+ filterset_class = RelatorioDataFimPrazoTramitacaoFilterSet
+ template_name = 'relatorios/RelatorioDataFimPrazoTramitacao_filter.html'
+ relatorio = relatorio_fim_prazo_tramitacao
+
+ def get_context_data(self, **kwargs):
+ context = super(RelatorioDataFimPrazoTramitacaoView,
+ self).get_context_data(**kwargs)
+ context['title'] = _(
+ 'Relatório de tramitações em intervalo de data de fim de prazo.')
+ if not self.filterset.form.is_valid():
+ return context
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+
+ context['data_fim_prazo'] = (self.request.GET['tramitacao__data_fim_prazo_0'] + ' - ' +
+ self.request.GET['tramitacao__data_fim_prazo_1'])
+
+ if self.request.GET['materia__ano']:
+ context['ano'] = self.request.GET['materia__ano']
+ else:
+ context['ano'] = ''
+
+ if self.request.GET['materia__tipo']:
+ tipo = self.request.GET['materia__tipo']
+ context['tipo'] = (
+ str(TipoMateriaLegislativa.objects.get(id=tipo)))
+ else:
+ context['tipo'] = ''
+
+ if self.request.GET['materia__autores']:
+ autor = int(self.request.GET['materia__autores'])
+ context['materia__autor'] = (str(Autor.objects.get(id=autor)))
+ else:
+ context['materia__autor'] = ''
+
+ if self.request.GET['tramitacao__status']:
+ tramitacao_status = self.request.GET['tramitacao__status']
+ context['tramitacao__status'] = (
+ str(StatusTramitacao.objects.get(id=tramitacao_status)))
+ else:
+ context['tramitacao__status'] = ''
+
+ if self.request.GET['tramitacao__unidade_tramitacao_local']:
+ context['tramitacao__unidade_tramitacao_local'] = \
+ (str(UnidadeTramitacao.objects.get(
+ id=self.request.GET['tramitacao__unidade_tramitacao_local'])))
+ else:
+ context['tramitacao__unidade_tramitacao_local'] = ''
+
+ if self.request.GET['tramitacao__unidade_tramitacao_destino']:
+ context['tramitacao__unidade_tramitacao_destino'] = \
+ (str(UnidadeTramitacao.objects.get(
+ id=self.request.GET['tramitacao__unidade_tramitacao_destino'])))
+ else:
+ context['tramitacao__unidade_tramitacao_destino'] = ''
+
+ return context
+
+
+class RelatorioReuniaoView(RelatorioMixin, FilterView):
+ model = Reuniao
+ filterset_class = RelatorioReuniaoFilterSet
+ template_name = 'relatorios/RelatorioReuniao_filter.html'
+ relatorio = relatorio_reuniao
+
+ def get_filterset_kwargs(self, filterset_class):
+ super(RelatorioReuniaoView,
+ self).get_filterset_kwargs(filterset_class)
+
+ kwargs = {'data': self.request.GET or None}
+ return kwargs
+
+ def get_context_data(self, **kwargs):
+ context = super(RelatorioReuniaoView,
+ self).get_context_data(**kwargs)
+ context['title'] = _('Reunião de Comissão')
+ if not self.filterset.form.is_valid():
+ return context
+ qr = self.request.GET.copy()
+
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+
+ if self.request.GET['comissao']:
+ comissao = self.request.GET['comissao']
+ context['comissao'] = (str(Comissao.objects.get(id=comissao)))
+ else:
+ context['comissao'] = ''
+
+ return context
+
+
+class RelatorioAudienciaView(RelatorioMixin, FilterView):
+ model = AudienciaPublica
+ filterset_class = RelatorioAudienciaFilterSet
+ template_name = 'relatorios/RelatorioAudiencia_filter.html'
+ relatorio = relatorio_audiencia
+
+ def get_filterset_kwargs(self, filterset_class):
+ super(RelatorioAudienciaView,
+ self).get_filterset_kwargs(filterset_class)
+
+ kwargs = {'data': self.request.GET or None}
+ return kwargs
+
+ def get_context_data(self, **kwargs):
+ context = super(RelatorioAudienciaView,
+ self).get_context_data(**kwargs)
+ context['title'] = _('Audiência Pública')
+ if not self.filterset.form.is_valid():
+ return context
+
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+
+ if self.request.GET['tipo']:
+ tipo = self.request.GET['tipo']
+ context['tipo'] = (str(TipoAudienciaPublica.objects.get(id=tipo)))
+ else:
+ context['tipo'] = ''
+
+ return context
+
+
+class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
+ model = MateriaEmTramitacao
+ filterset_class = RelatorioMateriasTramitacaoFilterSet
+ template_name = 'relatorios/RelatorioMateriasPorTramitacao_filter.html'
+ relatorio = relatorio_materia_em_tramitacao
+
+ paginate_by = 100
+
+ total_resultados_tipos = {}
+
+ def get_filterset_kwargs(self, filterset_class):
+ data = super().get_filterset_kwargs(filterset_class)
+
+ if data['data']:
+ qs = data['queryset']
+
+ ano_materia = data['data']['materia__ano']
+ tipo_materia = data['data']['materia__tipo']
+ unidade_tramitacao_destino = data['data']['tramitacao__unidade_tramitacao_destino']
+ status_tramitacao = data['data']['tramitacao__status']
+ autor = data['data']['materia__autores']
+
+ kwargs = {}
+ if ano_materia:
+ kwargs['materia__ano'] = ano_materia
+ if tipo_materia:
+ kwargs['materia__tipo'] = tipo_materia
+ if unidade_tramitacao_destino:
+ kwargs['tramitacao__unidade_tramitacao_destino'] = unidade_tramitacao_destino
+ if status_tramitacao:
+ kwargs['tramitacao__status'] = status_tramitacao
+ if autor:
+ kwargs['materia__autores'] = autor
+
+ qs = qs.filter(**kwargs)
+ data['queryset'] = qs
+
+ self.total_resultados_tipos = num_materias_por_tipo(
+ qs, "materia__tipo")
+
+ return data
+
+ def get_queryset(self):
+ qs = super().get_queryset()
+ qs = qs.select_related('materia__tipo').filter(
+ materia__em_tramitacao=True
+ ).exclude(
+ tramitacao__status__indicador='F'
+ ).order_by('-materia__ano', '-materia__numero')
+ return qs
+
+ def get_context_data(self, **kwargs):
+ context = super(
+ RelatorioMateriasTramitacaoView, self
+ ).get_context_data(**kwargs)
+
+ context['title'] = _('Matérias em Tramitação')
+
+ if not self.filterset.form.is_valid():
+ return context
+
+ qr = self.request.GET.copy()
+
+ context['qtdes'] = self.total_resultados_tipos
+ context['ano'] = (self.request.GET['materia__ano'])
+
+ if self.request.GET['materia__tipo']:
+ tipo = self.request.GET['materia__tipo']
+ context['tipo'] = (
+ str(TipoMateriaLegislativa.objects.get(id=tipo))
+ )
+ else:
+ context['tipo'] = ''
+
+ if self.request.GET['tramitacao__status']:
+ tramitacao_status = self.request.GET['tramitacao__status']
+ context['tramitacao__status'] = (
+ str(StatusTramitacao.objects.get(id=tramitacao_status))
+ )
+ else:
+ context['tramitacao__status'] = ''
+
+ if self.request.GET['tramitacao__unidade_tramitacao_destino']:
+ context['tramitacao__unidade_tramitacao_destino'] = (
+ str(UnidadeTramitacao.objects.get(
+ id=self.request.GET['tramitacao__unidade_tramitacao_destino']
+ ))
+ )
+ else:
+ context['tramitacao__unidade_tramitacao_destino'] = ''
+
+ if self.request.GET['materia__autores']:
+ autor = self.request.GET['materia__autores']
+ context['materia__autor'] = (
+ str(Autor.objects.get(id=autor))
+ )
+ else:
+ context['materia__autor'] = ''
+ if 'page' in qr:
+ del qr['page']
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+ context['show_results'] = show_results_filter_set(qr)
+
+ paginator = context['paginator']
+ page_obj = context['page_obj']
+
+ context['page_range'] = make_pagination(
+ page_obj.number, paginator.num_pages
+ )
+ context['NO_ENTRIES_MSG'] = 'Nenhum encontrado.'
+
+ return context
+
+
+class RelatorioMateriasPorAnoAutorTipoView(RelatorioMixin, FilterView):
+ model = MateriaLegislativa
+ filterset_class = RelatorioMateriasPorAnoAutorTipoFilterSet
+ template_name = 'relatorios/RelatorioMateriasPorAnoAutorTipo_filter.html'
+ relatorio = relatorio_materia_por_ano_autor
+
+ def get_materias_autor_ano(self, ano, primeiro_autor):
+
+ autorias = Autoria.objects.filter(materia__ano=ano, primeiro_autor=primeiro_autor).values(
+ 'autor',
+ 'materia__tipo__sigla',
+ 'materia__tipo__descricao').annotate(
+ total=Count('materia__tipo')).order_by(
+ 'autor',
+ 'materia__tipo')
+
+ autores_ids = set([i['autor'] for i in autorias])
+
+ autores = dict((a.id, a) for a in Autor.objects.filter(
+ id__in=autores_ids))
+
+ relatorio = []
+ visitados = set()
+ curr = None
+
+ for a in autorias:
+ # se mudou autor, salva atual, caso existente, e reinicia `curr`
+ if a['autor'] not in visitados:
+ if curr:
+ relatorio.append(curr)
+
+ curr = {}
+ curr['autor'] = autores[a['autor']]
+ curr['materia'] = []
+ curr['total'] = 0
+
+ visitados.add(a['autor'])
+
+ # atualiza valores
+ curr['materia'].append((a['materia__tipo__descricao'], a['total']))
+ curr['total'] += a['total']
+ # adiciona o ultimo
+ relatorio.append(curr)
+
+ return relatorio
+
+ def get_filterset_kwargs(self, filterset_class):
+ super(RelatorioMateriasPorAnoAutorTipoView,
+ self).get_filterset_kwargs(filterset_class)
+
+ kwargs = {'data': self.request.GET or None}
+ return kwargs
+
+ def get_context_data(self, **kwargs):
+ context = super(RelatorioMateriasPorAnoAutorTipoView,
+ self).get_context_data(**kwargs)
+
+ context['title'] = _('Matérias por Ano, Autor e Tipo')
+ if not self.filterset.form.is_valid():
+ return context
+ qs = context['object_list']
+ context['qtdes'] = num_materias_por_tipo(qs)
+
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+ context['ano'] = self.request.GET['ano']
+
+ if 'ano' in self.request.GET and self.request.GET['ano']:
+ ano = int(self.request.GET['ano'])
+ context['relatorio'] = self.get_materias_autor_ano(ano, True)
+ context['corelatorio'] = self.get_materias_autor_ano(ano, False)
+ else:
+ context['relatorio'] = []
+
+ return context
+
+
+class RelatorioMateriasPorAutorView(RelatorioMixin, FilterView):
+ model = MateriaLegislativa
+ filterset_class = RelatorioMateriasPorAutorFilterSet
+ template_name = 'relatorios/RelatorioMateriasPorAutor_filter.html'
+ relatorio = relatorio_materia_por_autor
+
+ def get_filterset_kwargs(self, filterset_class):
+ super().get_filterset_kwargs(filterset_class)
+ kwargs = {'data': self.request.GET or None}
+ return kwargs
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+
+ context['title'] = _('Matérias por Autor')
+ if not self.filterset.form.is_valid():
+ return context
+
+ qs = context['object_list']
+ context['materias_resultado'] = list(collections.OrderedDict.fromkeys(qs))
+ context['qtdes'] = num_materias_por_tipo(qs)
+
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+ if self.request.GET['tipo']:
+ tipo = int(self.request.GET['tipo'])
+ context['tipo'] = (
+ str(TipoMateriaLegislativa.objects.get(id=tipo)))
+ else:
+ context['tipo'] = ''
+ if self.request.GET['autoria__autor']:
+ autor = int(self.request.GET['autoria__autor'])
+ context['autor'] = (str(Autor.objects.get(id=autor)))
+ else:
+ context['autor'] = ''
+ context['periodo'] = (
+ self.request.GET['data_apresentacao_0'] +
+ ' - ' + self.request.GET['data_apresentacao_1'])
+
+ return context
+
+
+class RelatorioMateriaAnoAssuntoView(ListView):
+ template_name = 'relatorios/RelatorioMateriasAnoAssunto.html'
+
+ def get_queryset(self):
+ return MateriaAssunto.objects.all().values(
+ 'assunto_id',
+ assunto_materia=F('assunto__assunto'),
+ ano=F('materia__ano')).annotate(
+ total=Count('assunto_id')).order_by('-materia__ano', 'assunto_id')
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['title'] = _('Matérias por Ano e Assunto')
+
+ # In[10]: MateriaAssunto.objects.all().values(
+ # ...: 'materia__ano').annotate(
+ # ...: total = Count('materia__ano')).order_by('-materia__ano')
+
+ mat = MateriaLegislativa.objects.filter(
+ materiaassunto__isnull=True).values(
+ 'ano').annotate(
+ total=Count('ano')).order_by('-ano')
+
+ context.update({"materias_sem_assunto": mat})
+ return context
+
+
+class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView):
+ model = NormaJuridica
+ filterset_class = RelatorioNormasMesFilterSet
+ template_name = 'relatorios/RelatorioNormaMes_filter.html'
+ relatorio = relatorio_normas_mes
+
+ def get_context_data(self, **kwargs):
+ context = super(RelatorioNormasPublicadasMesView,
+ self).get_context_data(**kwargs)
+ context['title'] = _('Normas')
+
+ # Verifica se os campos foram preenchidos
+ if not self.filterset.form.is_valid():
+ return context
+
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+ context['ano'] = self.request.GET['ano']
+
+ normas_mes = collections.OrderedDict()
+ meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho',
+ 7: 'Julho', 8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'}
+ for norma in context['object_list']:
+ if not meses[norma.data.month] in normas_mes:
+ normas_mes[meses[norma.data.month]] = []
+ normas_mes[meses[norma.data.month]].append(norma)
+
+ context['normas_mes'] = normas_mes
+
+ quant_normas_mes = {}
+ for key in normas_mes.keys():
+ quant_normas_mes[key] = len(normas_mes[key])
+
+ context['quant_normas_mes'] = quant_normas_mes
+
+ return context
+
+
+class RelatorioNormasVigenciaView(RelatorioMixin, FilterView):
+ model = NormaJuridica
+ filterset_class = RelatorioNormasVigenciaFilterSet
+ template_name = 'relatorios/RelatorioNormasVigencia_filter.html'
+ relatorio = relatorio_normas_vigencia
+
+ def get_filterset_kwargs(self, filterset_class):
+ super(RelatorioNormasVigenciaView,
+ self).get_filterset_kwargs(filterset_class)
+
+ kwargs = {'data': self.request.GET or None}
+ qs = self.get_queryset().order_by('data').distinct()
+ if kwargs['data']:
+ ano = kwargs['data']['ano']
+ vigencia = kwargs['data']['vigencia']
+ if ano:
+ qs = qs.filter(ano=ano)
+
+ if vigencia == 'True':
+ qs_dt_not_null = qs.filter(data_vigencia__isnull=True)
+ qs = (qs_dt_not_null | qs.filter(
+ data_vigencia__gte=datetime.now().date())).distinct()
+ else:
+ qs = qs.filter(
+ data_vigencia__lt=datetime.now().date())
+
+ kwargs.update({
+ 'queryset': qs
+ })
+ return kwargs
+
+ def get_context_data(self, **kwargs):
+ context = super(RelatorioNormasVigenciaView,
+ self).get_context_data(**kwargs)
+ context['title'] = _('Normas por vigência')
+
+ # Verifica se os campos foram preenchidos
+ if not self.filterset.form.is_valid():
+ return context
+
+ normas_totais = NormaJuridica.objects.filter(
+ ano=self.request.GET['ano'])
+
+ context['quant_total'] = len(normas_totais)
+ if self.request.GET['vigencia'] == 'True':
+ context['vigencia'] = 'Vigente'
+ context['quant_vigente'] = len(context['object_list'])
+ context['quant_nao_vigente'] = context['quant_total'] - \
+ context['quant_vigente']
+ else:
+ context['vigencia'] = 'Não vigente'
+ context['quant_nao_vigente'] = len(context['object_list'])
+ context['quant_vigente'] = context['quant_total'] - \
+ context['quant_nao_vigente']
+
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+ context['ano'] = self.request.GET['ano']
+
+ return context
+
+
+class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView):
+ model = DocumentoAdministrativo
+ filterset_class = RelatorioHistoricoTramitacaoAdmFilterSet
+ template_name = 'relatorios/RelatorioHistoricoTramitacaoAdm_filter.html'
+ relatorio = relatorio_historico_tramitacao_adm
+
+ def get_context_data(self, **kwargs):
+ context = super(RelatorioHistoricoTramitacaoAdmView,
+ self).get_context_data(**kwargs)
+ context['title'] = _(
+ 'Histórico de Tramitações de Documento Administrativo')
+ if not self.filterset.form.is_valid():
+ return context
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+ context['data_tramitacao'] = (self.request.GET['tramitacaoadministrativo__data_tramitacao_0'] + ' - ' +
+ self.request.GET['tramitacaoadministrativo__data_tramitacao_1'])
+ if self.request.GET['tipo']:
+ tipo = self.request.GET['tipo']
+ context['tipo'] = (
+ str(TipoDocumentoAdministrativo.objects.get(id=tipo)))
+ else:
+ context['tipo'] = ''
+
+ if self.request.GET['tramitacaoadministrativo__status']:
+ tramitacao_status = self.request.GET['tramitacaoadministrativo__status']
+ context['tramitacaoadministrativo__status'] = (
+ str(StatusTramitacaoAdministrativo.objects.get(id=tramitacao_status)))
+ else:
+ context['tramitacaoadministrativo__status'] = ''
+
+ if self.request.GET['tramitacaoadministrativo__unidade_tramitacao_local']:
+ context['tramitacaoadministrativo__unidade_tramitacao_local'] = \
+ (str(UnidadeTramitacao.objects.get(
+ id=self.request.GET['tramitacaoadministrativo__unidade_tramitacao_local'])))
+ else:
+ context['tramitacaoadministrativo__unidade_tramitacao_local'] = ''
+
+ if self.request.GET['tramitacaoadministrativo__unidade_tramitacao_destino']:
+ context['tramitacaoadministrativo__unidade_tramitacao_destino'] = \
+ (str(UnidadeTramitacao.objects.get(
+ id=self.request.GET['tramitacaoadministrativo__unidade_tramitacao_destino'])))
+ else:
+ context['tramitacaoadministrativo__unidade_tramitacao_destino'] = ''
+
+ return context
+
+
+class RelatorioNormasPorAutorView(RelatorioMixin, FilterView):
+ model = NormaJuridica
+ filterset_class = RelatorioNormasPorAutorFilterSet
+ template_name = 'relatorios/RelatorioNormasPorAutor_filter.html'
+ relatorio = relatorio_normas_por_autor
+
+ def get_filterset_kwargs(self, filterset_class):
+ super().get_filterset_kwargs(filterset_class)
+ kwargs = {'data': self.request.GET or None}
+ return kwargs
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+
+ context['title'] = _('Normas por Autor')
+ if not self.filterset.form.is_valid():
+ return context
+
+ qtdes = {}
+ for tipo in TipoNormaJuridica.objects.all():
+ qs = context['object_list']
+ qtde = len(qs.filter(tipo_id=tipo.id))
+ if qtde > 0:
+ qtdes[tipo] = qtde
+ context['qtdes'] = qtdes
+
+ qr = self.request.GET.copy()
+ context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
+
+ context['show_results'] = show_results_filter_set(qr)
+ if self.request.GET['tipo']:
+ tipo = int(self.request.GET['tipo'])
+ context['tipo'] = (
+ str(TipoNormaJuridica.objects.get(id=tipo)))
+ else:
+ context['tipo'] = ''
+
+ if self.request.GET['autorianorma__autor']:
+ autor = int(self.request.GET['autorianorma__autor'])
+ context['autor'] = (str(Autor.objects.get(id=autor)))
+ else:
+ context['autor'] = ''
+ context['periodo'] = (
+ self.request.GET['data_0'] +
+ ' - ' + self.request.GET['data_1'])
+
+ return context
diff --git a/sapl/rules/group_geral.py b/sapl/rules/group_geral.py
index 5e9545d7b..d12b94152 100644
--- a/sapl/rules/group_geral.py
+++ b/sapl/rules/group_geral.py
@@ -110,7 +110,7 @@ rules_group_geral = {
__base__ + ['lock_unlock_textoarticulado'], set()),
# estes tres models são complexos e a principio apenas o admin tem perm
- (compilacao.TipoDispositivo, [], set()),
+ (compilacao.TipoDispositivo, __listdetailchange__, __perms_publicas__),
(compilacao.TipoDispositivoRelationship, [], set()),
(compilacao.PerfilEstruturalTextoArticulado, [], set()),
diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py
index 27ea9675b..73ac911f0 100644
--- a/sapl/sessao/forms.py
+++ b/sapl/sessao/forms.py
@@ -71,9 +71,9 @@ class SessaoPlenariaForm(FileFieldCheckMixin, ModelForm):
encerramento = self.cleaned_data['data_fim']
error = ValidationError(
- "Número de Sessão Plenária já existente "
- "para a Legislatura, Sessão Legislativa e Tipo informados. "
- "Favor escolher um número distinto.")
+ "Número de Sessão Plenária '" + str(num) + "' já existente "
+ "para o Tipo de Sessão " + str(tipo) + " da " + str(sl) +
+ " Sessão Legislativa da " + str(leg) + " Legislatura. Por Favor, escolha um número distinto.")
qs = tipo.build_predicados_queryset(leg, sl, abertura)
qs &= Q(numero=num)
diff --git a/sapl/sessao/urls.py b/sapl/sessao/urls.py
index 8464c2d78..05e19a844 100644
--- a/sapl/sessao/urls.py
+++ b/sapl/sessao/urls.py
@@ -31,6 +31,7 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente,
sessao_legislativa_legislatura_ajax,
VotacaoEmBlocoOrdemDia, VotacaoEmBlocoExpediente,
VotacaoEmBlocoSimbolicaView, VotacaoEmBlocoNominalView,
+ LeituraEmBlocoExpediente, LeituraEmBlocoOrdemDia,
recuperar_nome_tipo_sessao,
ExpedienteLeituraView,
OrdemDiaLeituraView,
@@ -158,6 +159,12 @@ urlpatterns = [
url(r'^sessao/(?P\d+)/votacao_bloco_expediente$',
VotacaoEmBlocoExpediente.as_view(),
name='votacao_bloco_expediente'),
+ url(r'^sessao/(?P\d+)/leitura_bloco_expediente$',
+ LeituraEmBlocoExpediente.as_view(),
+ name='leitura_bloco_expediente'),
+ url(r'^sessao/(?P\d+)/leitura_bloco_ordem_dia$',
+ LeituraEmBlocoOrdemDia.as_view(),
+ name='leitura_bloco_ordem_dia'),
url(r'^sessao/(?P\d+)/resumo$',
ResumoView.as_view(), name='resumo'),
url(r'^sessao/(?P\d+)/resumo_ata$',
diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py
index 97ec4be36..7657489d1 100755
--- a/sapl/sessao/views.py
+++ b/sapl/sessao/views.py
@@ -38,10 +38,10 @@ from sapl.materia.models import (Autoria, TipoMateriaLegislativa,
from sapl.materia.views import MateriaLegislativaPesquisaView
from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato,
Parlamentar, SessaoLegislativa)
-from sapl.protocoloadm.models import TipoDocumentoAdministrativo,\
+from sapl.protocoloadm.models import TipoDocumentoAdministrativo, \
DocumentoAdministrativo
from sapl.sessao.apps import AppConfig
-from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm, OrdemExpedienteLeituraForm,\
+from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm, OrdemExpedienteLeituraForm, \
CorrespondenciaForm, CorrespondenciaEmLoteFilterSet
from sapl.sessao.models import Correspondencia
from sapl.settings import TIME_ZONE
@@ -62,7 +62,6 @@ from .models import (Bancada, CargoBancada, CargoMesa,
RetiradaPauta, TipoJustificativa, JustificativaAusencia, OradorOrdemDia,
ORDENACAO_RESUMO, RegistroLeitura)
-
TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria')
TipoJustificativaCrud = CrudAux.build(TipoJustificativa, 'tipo_justificativa')
CargoBancadaCrud = CrudAux.build(CargoBancada, '')
@@ -167,7 +166,8 @@ def verifica_sessao_iniciada(request, spk, is_leitura=False):
username = request.user.username
aux_text = 'leitura' if is_leitura else 'votação'
logger.info('user=' + username + '. Não é possível abrir matérias para {}. '
- 'Esta SessaoPlenaria (id={}) não foi iniciada ou está finalizada.'.format(aux_text, spk))
+ 'Esta SessaoPlenaria (id={}) não foi iniciada ou está finalizada.'.format(
+ aux_text, spk))
msg = _('Não é possível abrir matérias para {}. '
'Esta Sessão Plenária não foi iniciada ou está finalizada.'
' Vá em "Abertura"->"Dados Básicos" e altere os valores dos campos necessários.'.format(aux_text))
@@ -199,7 +199,7 @@ def abrir_votacao(request, pk, spk):
materia_votacao = model.objects.get(id=pk)
is_leitura = materia_votacao.tipo_votacao == 4
if (verifica_presenca(request, presenca_model, spk, is_leitura) and
- verifica_votacoes_abertas(request) and
+ verifica_votacoes_abertas(request) and
verifica_sessao_iniciada(request, spk, is_leitura)):
materia_votacao.votacao_aberta = True
sessao = SessaoPlenaria.objects.get(id=spk)
@@ -235,25 +235,33 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
num_protocolo = materia.numero_protocolo if materia.numero_protocolo else "-"
sessao_plenaria = SessaoPlenaria.objects.get(id=pk)
data_sessao = sessao_plenaria.data_fim if sessao_plenaria.data_fim else sessao_plenaria.data_inicio
- tramitacao = Tramitacao.objects\
- .select_related('materia', 'status', 'materia__tipo')\
- .filter(materia=materia, turno__isnull=False, data_tramitacao__lte=data_sessao)\
- .exclude(turno__exact='')\
- .order_by('-data_tramitacao', '-id')\
- .first()
+ tramitacao = Tramitacao.objects \
+ .select_related('materia', 'status', 'materia__tipo') \
+ .filter(materia=materia, turno__isnull=False, data_tramitacao__lte=data_sessao) \
+ .exclude(turno__exact='') \
+ .order_by('-data_tramitacao', '-id') \
+ .first()
turno = '-'
if tramitacao:
for t in Tramitacao.TURNO_CHOICES:
if t[0] == tramitacao.turno:
turno = t[1]
break
- materia_em_tramitacao = MateriaEmTramitacao.objects\
- .select_related("materia", "tramitacao")\
- .filter(materia=materia)\
- .first()
+ materia_em_tramitacao = MateriaEmTramitacao.objects \
+ .select_related("materia", "tramitacao") \
+ .filter(materia=materia) \
+ .first()
# idUnica para cada materia
idAutor = "autor" + str(i)
idAutores = "autores" + str(i)
+ link_texto_original = ''
+ if materia.texto_original:
+ link_texto_original = f"""
+
+
+ Texto original
+
+ """
title_materia = f"""
{row[1][0]}
Processo: {numeracao}
@@ -261,6 +269,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
Autor: {todos_autores}
Protocolo: {num_protocolo}
Turno: {turno}
+ {link_texto_original}
"""
# Na linha abaixo, o segundo argumento é None para não colocar
@@ -274,8 +283,8 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
exist_leitura = obj.registroleitura_set.filter(
materia=obj.materia).exists()
- if (obj.tipo_votacao != 4 and not exist_resultado and not exist_retirada) or\
- (obj.tipo_votacao == 4 and not exist_leitura):
+ if (obj.tipo_votacao != LEITURA and not exist_resultado and not exist_retirada) or \
+ (obj.tipo_votacao == LEITURA and not exist_leitura):
if obj.votacao_aberta:
url = ''
if is_expediente:
@@ -472,11 +481,11 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
'mid': obj.materia_id})
resultado = (
- '%s %s ' % (
- url,
- context.get('page', 1),
- resultado_descricao,
- resultado_observacao))
+ '%s %s ' % (
+ url,
+ context.get('page', 1),
+ resultado_descricao,
+ resultado_observacao))
else:
if obj.tipo_votacao == NOMINAL:
@@ -487,7 +496,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
'pk': obj.sessao_plenaria_id,
'oid': obj.pk,
'mid': obj.materia_id}) + \
- '?&materia=expediente'
+ '?&materia=expediente'
else:
url = reverse(
'sapl.sessao:votacao_nominal_transparencia',
@@ -495,7 +504,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
'pk': obj.sessao_plenaria_id,
'oid': obj.pk,
'mid': obj.materia_id}) + \
- '?&materia=ordem'
+ '?&materia=ordem'
resultado = ('%s %s ' %
(url,
@@ -510,7 +519,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
'pk': obj.sessao_plenaria_id,
'oid': obj.pk,
'mid': obj.materia_id}) + \
- '?&materia=expediente'
+ '?&materia=expediente'
else:
url = reverse(
'sapl.sessao:votacao_simbolica_transparencia',
@@ -518,7 +527,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
'pk': obj.sessao_plenaria_id,
'oid': obj.pk,
'mid': obj.materia_id}) + \
- '?&materia=ordem'
+ '?&materia=ordem'
resultado = ('%s %s ' %
(url,
@@ -673,8 +682,8 @@ class TransferenciaMateriasSessaoAbstract(PermissionRequiredMixin, ListView):
exp = ExpedienteMateria.objects.filter(sessao_plenaria=sessao)
numero_ordem = exp.last().numero_ordem if exp.exists() else 0
for num_ordem, expediente in enumerate(
- ExpedienteMateria.objects.filter(id__in=marcadas),
- numero_ordem + 1
+ ExpedienteMateria.objects.filter(id__in=marcadas),
+ numero_ordem + 1
):
lista_expediente.append(
ExpedienteMateria(
@@ -694,7 +703,7 @@ class TransferenciaMateriasSessaoAbstract(PermissionRequiredMixin, ListView):
o = OrdemDia.objects.filter(sessao_plenaria=sessao)
numero_ordem = o.last().numero_ordem if o.exists() else 0
for num_ordem, ordemdia in enumerate(
- OrdemDia.objects.filter(id__in=marcadas), numero_ordem + 1
+ OrdemDia.objects.filter(id__in=marcadas), numero_ordem + 1
):
lista_ordemdia.append(
OrdemDia(
@@ -780,9 +789,9 @@ class MateriaOrdemDiaCrud(MasterDetailCrud):
pk=self.kwargs['pk']).data_inicio.strftime('%d/%m/%Y')
max_numero_ordem = OrdemDia.objects.filter(
sessao_plenaria=self.kwargs['pk']).aggregate(
- Max('numero_ordem'))['numero_ordem__max']
+ Max('numero_ordem'))['numero_ordem__max']
self.initial['numero_ordem'] = (
- max_numero_ordem if max_numero_ordem else 0) + 1
+ max_numero_ordem if max_numero_ordem else 0) + 1
return self.initial
def get_success_url(self):
@@ -923,9 +932,9 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
pk=self.kwargs['pk']).data_inicio.strftime('%d/%m/%Y')
max_numero_ordem = ExpedienteMateria.objects.filter(
sessao_plenaria=self.kwargs['pk']).aggregate(
- Max('numero_ordem'))['numero_ordem__max']
+ Max('numero_ordem'))['numero_ordem__max']
initial['numero_ordem'] = (
- max_numero_ordem if max_numero_ordem else 0) + 1
+ max_numero_ordem if max_numero_ordem else 0) + 1
return initial
def get_success_url(self):
@@ -1312,7 +1321,7 @@ class SessaoCrud(Crud):
username = self.request.user.username
self.logger.error('user=' + username + '. Cadastre alguma legislatura antes de adicionar '
- 'uma sessão plenária!')
+ 'uma sessão plenária!')
messages.add_message(self.request, messages.ERROR, msg)
return {}
@@ -1405,7 +1414,7 @@ class PresencaView(FormMixin, PresencaMixin, DetailView):
# Id dos parlamentares presentes
marcados = request.POST.getlist('presenca_ativos') \
- + request.POST.getlist('presenca_inativos')
+ + request.POST.getlist('presenca_inativos')
# Deletar os que foram desmarcados
deletar = set(presentes_banco) - set(marcados)
@@ -1461,7 +1470,7 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
username = self.request.user.username
self.logger.error('user=' + username + '. Você precisa primeiro configurar os cronômetros'
- ' nas Configurações da Aplicação')
+ ' nas Configurações da Aplicação')
msg = _(
'Você precisa primeiro configurar os cronômetros \
nas Configurações da Aplicação')
@@ -1520,7 +1529,7 @@ class PresencaOrdemDiaView(FormMixin, PresencaMixin, DetailView):
# Id dos parlamentares presentes
marcados = request.POST.getlist('presenca_ativos') \
- + request.POST.getlist('presenca_inativos')
+ + request.POST.getlist('presenca_inativos')
# Deletar os que foram desmarcados
deletar = set(presentes_banco) - set(marcados)
@@ -1782,7 +1791,7 @@ def insere_parlamentar_composicao(request):
username = request.user.username
if request.user.has_perm(
'%s.add_%s' % (
- AppConfig.label, IntegranteMesa._meta.model_name)):
+ AppConfig.label, IntegranteMesa._meta.model_name)):
composicao = IntegranteMesa()
@@ -1815,8 +1824,9 @@ def insere_parlamentar_composicao(request):
cargo_id=composicao.cargo.id).exists()
if parlamentar_ja_inserido:
- logger.debug("user=" + username + ". Parlamentar (id={}) já inserido na sessao_plenaria(id={}) e cargo(ìd={})."
- .format(request.POST['parlamentar'], composicao.sessao_plenaria.id, composicao.cargo.id))
+ logger.debug(
+ "user=" + username + ". Parlamentar (id={}) já inserido na sessao_plenaria(id={}) e cargo(ìd={})."
+ .format(request.POST['parlamentar'], composicao.sessao_plenaria.id, composicao.cargo.id))
return JsonResponse({'msg': ('Parlamentar já inserido!', 0)})
composicao.save()
@@ -1843,8 +1853,8 @@ def remove_parlamentar_composicao(request):
logger = logging.getLogger(__name__)
username = request.user.username
if request.POST and request.user.has_perm(
- '%s.delete_%s' % (
- AppConfig.label, IntegranteMesa._meta.model_name)):
+ '%s.delete_%s' % (
+ AppConfig.label, IntegranteMesa._meta.model_name)):
if 'composicao_mesa' in request.POST:
try:
@@ -1968,7 +1978,6 @@ def get_mesa_diretora(sessao_plenaria):
def get_presenca_sessao(sessao_plenaria):
-
parlamentares_sessao = [p.parlamentar for p in SessaoPlenariaPresenca.objects.filter(
sessao_plenaria_id=sessao_plenaria.id
).order_by('parlamentar__nome_parlamentar').distinct()]
@@ -2029,7 +2038,8 @@ def get_materias_expediente(sessao_plenaria):
for m in ExpedienteMateria.objects.select_related("materia").filter(sessao_plenaria_id=sessao_plenaria.id):
tramitacao = ''
data_sessao = sessao_plenaria.data_fim if sessao_plenaria.data_fim else sessao_plenaria.data_inicio
- for aux_tramitacao in Tramitacao.objects.filter(materia=m.materia, data_tramitacao__lte=data_sessao).order_by('-data_tramitacao', '-id'):
+ for aux_tramitacao in Tramitacao.objects.filter(materia=m.materia, data_tramitacao__lte=data_sessao).order_by(
+ '-data_tramitacao', '-id'):
if aux_tramitacao.turno:
tramitacao = aux_tramitacao
break
@@ -2175,7 +2185,8 @@ def get_materias_ordem_do_dia(sessao_plenaria):
for o in OrdemDia.objects.filter(sessao_plenaria_id=sessao_plenaria.id):
tramitacao = ''
data_sessao = sessao_plenaria.data_fim if sessao_plenaria.data_fim else sessao_plenaria.data_inicio
- for aux_tramitacao in Tramitacao.objects.filter(materia=o.materia, data_tramitacao__lte=data_sessao).order_by('-data_tramitacao', '-id'):
+ for aux_tramitacao in Tramitacao.objects.filter(materia=o.materia, data_tramitacao__lte=data_sessao).order_by(
+ '-data_tramitacao', '-id'):
if aux_tramitacao.turno:
tramitacao = aux_tramitacao
break
@@ -2316,8 +2327,8 @@ class ResumoView(DetailView):
# Votos de Votação Nominal de Matérias Expediente
votacoes = []
- for mevn in ExpedienteMateria.objects.filter(sessao_plenaria_id=self.object.id, tipo_votacao=2)\
- .order_by('-materia'):
+ for mevn in ExpedienteMateria.objects.filter(sessao_plenaria_id=self.object.id, tipo_votacao=2) \
+ .order_by('-materia'):
votos_materia = []
titulo_materia = mevn.materia
registro = RegistroVotacao.objects.filter(expediente=mevn)
@@ -2437,7 +2448,7 @@ class ResumoView(DetailView):
})
except KeyError as e:
self.logger.error("KeyError: " + str(e) + ". Erro ao tentar utilizar "
- "configuração de ordenação. Utilizando ordenação padrão.")
+ "configuração de ordenação. Utilizando ordenação padrão.")
context.update({
'primeiro_ordenacao': 'identificacao_basica.html',
'segundo_ordenacao': 'conteudo_multimidia.html',
@@ -2510,7 +2521,6 @@ class ExpedienteView(FormMixin, DetailView):
list_conteudo = request.POST.getlist('conteudo')
for tipo, conteudo in zip(list_tipo, list_conteudo):
-
ExpedienteSessao.objects.filter(
sessao_plenaria_id=self.object.id,
tipo_id=tipo).delete()
@@ -2523,8 +2533,9 @@ class ExpedienteView(FormMixin, DetailView):
msg = _('Registro salvo com sucesso')
messages.add_message(self.request, messages.SUCCESS, msg)
- self.logger.info('user=' + username + '. ExpedienteSessao(sessao_plenaria_id={} e tipo_id={}) salvo com sucesso.'
- .format(self.object.id, tipo))
+ self.logger.info(
+ 'user=' + username + '. ExpedienteSessao(sessao_plenaria_id={} e tipo_id={}) salvo com sucesso.'
+ .format(self.object.id, tipo))
return self.form_valid(form)
else:
@@ -2609,7 +2620,8 @@ class OcorrenciaSessaoView(FormMixin, DetailView):
username = self.request.user.username
self.logger.info(
- 'user=' + username + '. OcorrenciaSessao de sessao_plenaria_id={} atualizada com sucesso.'.format(self.object.id))
+ 'user=' + username + '. OcorrenciaSessao de sessao_plenaria_id={} atualizada com sucesso.'.format(
+ self.object.id))
@method_decorator(permission_required('sessao.add_ocorrenciasessao'))
def post(self, request, *args, **kwargs):
@@ -2677,7 +2689,8 @@ class ConsideracoesFinaisView(FormMixin, DetailView):
username = self.request.user.username
self.logger.info(
- 'user=' + username + '. consideracoesFinais de sessao_plenaria_id={} atualizada com sucesso.'.format(self.object.id))
+ 'user=' + username + '. consideracoesFinais de sessao_plenaria_id={} atualizada com sucesso.'.format(
+ self.object.id))
@method_decorator(permission_required('sessao.add_consideracoesfinais'))
def post(self, request, *args, **kwargs):
@@ -2701,7 +2714,6 @@ class ConsideracoesFinaisView(FormMixin, DetailView):
class VotacaoEditView(SessaoPermissionMixin):
-
'''
Votação Simbólica e Secreta
'''
@@ -2716,7 +2728,7 @@ class VotacaoEditView(SessaoPermissionMixin):
materia_id = kwargs['mid']
ordem_id = kwargs['oid']
- if(int(request.POST['anular_votacao']) == 1):
+ if (int(request.POST['anular_votacao']) == 1):
RegistroVotacao.objects.filter(ordem_id=ordem_id).delete()
ordem = OrdemDia.objects.get(id=ordem_id)
@@ -2752,7 +2764,7 @@ class VotacaoEditView(SessaoPermissionMixin):
' ', ' ', strip_tags(votacao.observacao)),
'resultado': votacao.tipo_resultado_votacao.nome,
'tipo_resultado':
- votacao.tipo_resultado_votacao_id}
+ votacao.tipo_resultado_votacao_id}
context.update({'votacao_titulo': titulo,
'votacao': votacao_existente,
'tipos': self.get_tipos_votacao()})
@@ -2774,7 +2786,6 @@ class VotacaoEditView(SessaoPermissionMixin):
class VotacaoView(SessaoPermissionMixin):
-
"""
Votação Simbólica e Secreta
"""
@@ -2895,7 +2906,8 @@ class VotacaoView(SessaoPermissionMixin):
except Exception as e:
username = request.user.username
self.logger.error('user=' + username + '. Problemas ao salvar RegistroVotacao da materia de id={} '
- 'e da ordem de id={}. '.format(materia_id, ordem_id) + str(e))
+ 'e da ordem de id={}. '.format(materia_id, ordem_id) + str(
+ e))
return self.form_invalid(form)
else:
ordem = OrdemDia.objects.get(id=ordem_id)
@@ -3108,7 +3120,7 @@ class VotacaoNominalAbstract(SessaoPermissionMixin):
# Caso todas as opções sejam 'Não votou', fecha a votação
if nao_votou == len(request.POST.getlist('voto_parlamentar')):
self.logger.error('user=' + username + '. Não é possível finalizar a votação sem '
- 'nenhum voto')
+ 'nenhum voto')
form.add_error(None, 'Não é possível finalizar a votação sem '
'nenhum voto')
return self.form_invalid(form)
@@ -3266,7 +3278,8 @@ class VotacaoNominalEditAbstract(SessaoPermissionMixin):
if not ordem or not votacao:
self.logger.error(
- 'user=' + username + '. Objeto OrdemDia com id={} ou RegistroVotacao de OrdemDia não existe.'.format(ordem_id))
+ 'user=' + username + '. Objeto OrdemDia com id={} ou RegistroVotacao de OrdemDia não existe.'.format(
+ ordem_id))
raise Http404()
materia = ordem.materia
@@ -3354,7 +3367,7 @@ class VotacaoNominalEditAbstract(SessaoPermissionMixin):
'user=' + username + '. Objeto ExpedienteMateria com id={} não existe.'.format(expediente_id))
raise Http404()
- if(int(request.POST['anular_votacao']) == 1):
+ if (int(request.POST['anular_votacao']) == 1):
fechar_votacao_materia(materia_votacao)
return self.form_valid(form)
@@ -3430,7 +3443,7 @@ class VotacaoNominalTransparenciaDetailView(TemplateView):
' ', ' ', strip_tags(votacao.observacao)),
'resultado': votacao.tipo_resultado_votacao.nome,
'tipo_resultado':
- votacao.tipo_resultado_votacao_id}
+ votacao.tipo_resultado_votacao_id}
context.update({'resultado_votacao': votacao_existente,
'tipos': self.get_tipos_votacao()})
@@ -3470,7 +3483,7 @@ class VotacaoNominalExpedienteDetailView(DetailView):
' ', ' ', strip_tags(votacao.observacao)),
'resultado': votacao.tipo_resultado_votacao.nome,
'tipo_resultado':
- votacao.tipo_resultado_votacao_id}
+ votacao.tipo_resultado_votacao_id}
context.update({'votacao': votacao_existente,
'tipos': self.get_tipos_votacao()})
@@ -3518,7 +3531,7 @@ class VotacaoSimbolicaTransparenciaDetailView(TemplateView):
' ', ' ', strip_tags(votacao.observacao)),
'resultado': votacao.tipo_resultado_votacao.nome,
'tipo_resultado':
- votacao.tipo_resultado_votacao_id}
+ votacao.tipo_resultado_votacao_id}
context.update({'resultado_votacao': votacao_existente,
'tipos': self.get_tipos_votacao()})
@@ -3530,7 +3543,6 @@ class VotacaoSimbolicaTransparenciaDetailView(TemplateView):
class VotacaoExpedienteView(SessaoPermissionMixin):
-
"""
Votação Simbólica e Secreta
"""
@@ -3683,7 +3695,6 @@ class VotacaoExpedienteView(SessaoPermissionMixin):
class VotacaoExpedienteEditView(SessaoPermissionMixin):
-
"""
Votação Simbólica e Secreta
"""
@@ -3735,7 +3746,7 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin):
' ', ' ', strip_tags(votacao.observacao)),
'resultado': votacao.tipo_resultado_votacao.nome,
'tipo_resultado':
- votacao.tipo_resultado_votacao_id}
+ votacao.tipo_resultado_votacao_id}
context.update({'votacao_titulo': titulo,
'votacao': votacao_existente})
@@ -3887,8 +3898,8 @@ class PautaSessaoDetailView(DetailView):
# =====================================================================
# Expedientes
expedientes = []
- for e in ExpedienteSessao.objects.select_related("tipo").filter(sessao_plenaria_id=self.object.id)\
- .order_by('tipo__ordenacao'):
+ for e in ExpedienteSessao.objects.select_related("tipo").filter(sessao_plenaria_id=self.object.id) \
+ .order_by('tipo__ordenacao'):
conteudo = e.conteudo
from sapl.relatorios.views import is_empty
if not is_empty(conteudo):
@@ -4070,7 +4081,8 @@ def verifica_materia_sessao_plenaria_ajax(request):
materia=id_materia_selecionada
).exists()
- return JsonResponse({'is_materia_presente': is_materia_presente, 'is_materia_presente_any_sessao': is_materia_presente_any_sessao})
+ return JsonResponse(
+ {'is_materia_presente': is_materia_presente, 'is_materia_presente_any_sessao': is_materia_presente_any_sessao})
class AdicionarVariasMateriasExpediente(PermissionRequiredForAppCrudMixin,
@@ -4217,7 +4229,7 @@ class AdicionarVariasMateriasOrdemDia(AdicionarVariasMateriasExpediente):
MateriaLegislativa.objects.get(id=m))
messages.add_message(request, messages.ERROR, msg)
self.logger.error('user=' + username + '. Formulário Inválido. Você esqueceu de selecionar '
- 'o tipo de votação de MateriaLegislativa com id={}'.format(m))
+ 'o tipo de votação de MateriaLegislativa com id={}'.format(m))
return self.get(request, self.kwargs)
@@ -4316,7 +4328,6 @@ class JustificativaAusenciaCrud(MasterDetailCrud):
@property
def layout_display(self):
-
layout = super().layout_display
if self.object.ausencia == 2:
@@ -4335,7 +4346,6 @@ class JustificativaAusenciaCrud(MasterDetailCrud):
layout_key = None
def get_context_data_old(self, **kwargs):
-
context = super().get_context_data(**kwargs)
presencas = SessaoPlenariaPresenca.objects.filter(
@@ -4371,7 +4381,6 @@ class JustificativaAusenciaCrud(MasterDetailCrud):
kwargs={'pk': self.kwargs['pk']})
class UpdateView(MasterDetailCrud.UpdateView):
-
form_class = JustificativaAusenciaForm
layout_key = None
@@ -4384,6 +4393,97 @@ class JustificativaAusenciaCrud(MasterDetailCrud):
pass
+class LeituraEmBloco(PermissionRequiredForAppCrudMixin, ListView):
+ template_name = 'sessao/leitura/leitura_bloco.html'
+ app_label = AppConfig.label
+ expediente = True
+ paginate_by = 100
+
+ def get_queryset(self):
+ return ExpedienteMateria.objects.filter(sessao_plenaria_id=self.kwargs['pk'],
+ retiradapauta=None, tipo_votacao=LEITURA, registroleitura__materia=None)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['pk'] = self.kwargs['pk']
+ context['root_pk'] = self.kwargs['pk']
+ if not verifica_sessao_iniciada(self.request, self.kwargs['pk']):
+ context['sessao_iniciada'] = False
+ return context
+ context['sessao_iniciada'] = True
+ context['turno_choices'] = Tramitacao.TURNO_CHOICES
+ context['title'] = SessaoPlenaria.objects.get(id=self.kwargs['pk'])
+ if self.expediente:
+ context['expediente'] = True
+ else:
+ context['expediente'] = False
+ return context
+
+ def post(self, request, *args, **kwargs):
+ if 'marcadas_4' in request.POST:
+ models = None
+ selectedlist = request.POST.getlist('marcadas_4')
+ if request.POST['origem'] == 'ordem':
+ models = OrdemDia.objects.filter(id__in=selectedlist)
+ elif request.POST['origem'] == 'expediente':
+ models = ExpedienteMateria.objects.filter(id__in=selectedlist)
+
+ if not models:
+ messages.add_message(self.request, messages.ERROR,
+ _('Impossível localizar as matérias selecionadas'))
+ return self.get(request, self.kwargs)
+
+ materias = [m.materia for m in models]
+ RegistroLeitura.objects.filter(materia__in=materias).delete()
+ leituras = []
+ for m in models:
+ obj = None
+ if isinstance(m, ExpedienteMateria):
+ obj = RegistroLeitura(expediente=m, materia=m.materia,
+ observacao=request.POST['observacao'],
+ user=self.request.user,
+ ip=get_client_ip(self.request))
+ elif isinstance(m, OrdemDia):
+ obj = RegistroLeitura(ordem=m, materia=m.materia,
+ observacao=request.POST['observacao'],
+ user=self.request.user,
+ ip=get_client_ip(self.request))
+ leituras.append(obj)
+
+ RegistroLeitura.objects.bulk_create(leituras)
+ else:
+ messages.add_message(self.request, messages.ERROR, _('Nenhuma matéria selecionada para leitura em Bloco'))
+ return self.get(request, self.kwargs)
+
+ return HttpResponseRedirect(self.get_success_url())
+
+ def get_success_url(self):
+ if self.request.POST['origem'] == 'ordem':
+ return reverse('sapl.sessao:ordemdia_list',
+ kwargs={'pk': self.kwargs['pk']})
+ else:
+ return reverse('sapl.sessao:expedientemateria_list',
+ kwargs={'pk': self.kwargs['pk']})
+
+
+class LeituraEmBlocoExpediente(LeituraEmBloco):
+ expediente = True
+ paginate_by = 100
+
+ def get_queryset(self):
+ return ExpedienteMateria.objects.filter(sessao_plenaria_id=self.kwargs['pk'],
+ retiradapauta=None, tipo_votacao=LEITURA, registroleitura__materia=None)
+
+
+class LeituraEmBlocoOrdemDia(LeituraEmBloco):
+ expediente = False
+ paginate_by = 100
+
+ def get_queryset(self):
+ return OrdemDia.objects.filter(sessao_plenaria_id=self.kwargs['pk'],
+ retiradapauta=None, tipo_votacao=LEITURA, registroleitura__materia=None)
+
+
class VotacaoEmBlocoExpediente(PermissionRequiredForAppCrudMixin, ListView):
template_name = 'sessao/votacao/votacao_bloco.html'
app_label = AppConfig.label
@@ -4423,7 +4523,6 @@ class VotacaoEmBlocoOrdemDia(VotacaoEmBlocoExpediente):
class VotacaoEmBlocoSimbolicaView(PermissionRequiredForAppCrudMixin, TemplateView):
-
"""
Votação Simbólica
"""
@@ -4519,8 +4618,8 @@ class VotacaoEmBlocoSimbolicaView(PermissionRequiredForAppCrudMixin, TemplateVie
except Exception as e:
username = request.user.username
self.logger.error('user=' + username + '. Problemas ao salvar '
- 'RegistroVotacao da materia de id={} '
- 'e da ordem de id={}. '
+ 'RegistroVotacao da materia de id={} '
+ 'e da ordem de id={}. '
.format(ordem.materia.id, ordem.id) + str(e))
return self.form_invalid(form, context)
else:
@@ -4551,8 +4650,10 @@ class VotacaoEmBlocoSimbolicaView(PermissionRequiredForAppCrudMixin, TemplateVie
votacao.save()
except Exception as e:
username = request.user.username
- self.logger.error('user=' + username + '. Problemas ao salvar RegistroVotacao da materia de id={} '
- 'e da ordem de id={}. '.format(expediente.materia.id, expediente.id) + str(e))
+ self.logger.error(
+ 'user=' + username + '. Problemas ao salvar RegistroVotacao da materia de id={} '
+ 'e da ordem de id={}. '.format(expediente.materia.id,
+ expediente.id) + str(e))
return self.form_invalid(form, context)
else:
expediente.resultado = resultado.nome
@@ -4716,7 +4817,7 @@ class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView)
if form.is_valid():
if form.cleaned_data['resultado_votacao'] == None:
form.add_error(None, 'Não é possível finalizar a votação sem '
- 'nenhum resultado da votação.')
+ 'nenhum resultado da votação.')
return self.form_invalid(form, context)
qtde_votos = (int(request.POST['votos_sim']) +
@@ -4727,9 +4828,9 @@ class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView)
# Caso todas as opções sejam 'Não votou', fecha a votação
if int(request.POST['nao_votou']) == qtde_votos:
self.logger.error('user=' + username + '. Não é possível finalizar a votação sem '
- 'nenhum voto.')
+ 'nenhum voto.')
form.add_error(None, 'Não é possível finalizar a votação sem '
- 'nenhum voto.')
+ 'nenhum voto.')
return self.form_invalid(form, context)
if request.POST['origem'] == 'ordem':
@@ -5099,7 +5200,7 @@ class CorrespondenciaCrud(MasterDetailCrud):
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['numero_ordem',
- ('documento__data', 'documento__interessado'), 'documento']
+ ('documento__data', 'documento__interessado'), 'documento', 'documento__assunto']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -5155,9 +5256,9 @@ class CorrespondenciaCrud(MasterDetailCrud):
initial = super().get_initial()
max_numero_ordem = Correspondencia.objects.filter(
sessao_plenaria=self.kwargs['pk']).aggregate(
- Max('numero_ordem'))['numero_ordem__max']
+ Max('numero_ordem'))['numero_ordem__max']
initial['numero_ordem'] = (
- max_numero_ordem if max_numero_ordem else 0) + 1
+ max_numero_ordem if max_numero_ordem else 0) + 1
return initial
@@ -5258,8 +5359,8 @@ class CorrespondenciaEmLoteView(PermissionRequiredMixin, FilterView):
sessao_plenaria = SessaoPlenaria.objects.get(
pk=self.kwargs['pk'])
not_list = [self.kwargs['pk']] + \
- [m for m in sessao_plenaria.correspondencias.values_list(
- 'id', flat=True)]
+ [m for m in sessao_plenaria.correspondencias.values_list(
+ 'id', flat=True)]
context['object_list'] = context['object_list'].exclude(
pk__in=not_list)
diff --git a/sapl/settings.py b/sapl/settings.py
index 1d9061618..df4d98283 100644
--- a/sapl/settings.py
+++ b/sapl/settings.py
@@ -41,7 +41,7 @@ ALLOWED_HOSTS = ['*']
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/?next='
-SAPL_VERSION = '3.1.163-RC7'
+SAPL_VERSION = '3.1.163-RC16'
if DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
@@ -93,6 +93,8 @@ INSTALLED_APPS = (
'webpack_loader',
+ 'django_prometheus',
+
) + SAPL_APPS
# FTS = Full Text Search
@@ -123,15 +125,18 @@ HAYSTACK_CONNECTIONS = {
}
MIDDLEWARE = [
+ 'django_prometheus.middleware.PrometheusBeforeMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
+ 'sapl.endpoint_restriction_middleware.EndpointRestrictionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
+ 'django_prometheus.middleware.PrometheusAfterMiddleware',
]
if DEBUG:
INSTALLED_APPS += ('debug_toolbar',)
diff --git a/sapl/static/sapl/css/relatorio.css b/sapl/static/sapl/css/relatorio.css
index c3489dfe2..0dcbd8061 100644
--- a/sapl/static/sapl/css/relatorio.css
+++ b/sapl/static/sapl/css/relatorio.css
@@ -58,6 +58,7 @@ fieldset {
table {
+ table-layout: fixed;
max-width: 520px;
}
table.grayTable {
@@ -69,6 +70,10 @@ table.grayTable {
table.grayTable td, table.grayTable th {
border: 1px solid #000000;
padding: 5px;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ text-align: justify;
+ vertical-align: top;
}
table.grayTable tbody td {
font-size: 10px;
diff --git a/sapl/static/sapl/frontend/js/compilacao.1c9473f1.js.gz b/sapl/static/sapl/frontend/js/compilacao.1c9473f1.js.gz
deleted file mode 100644
index 09be795cb..000000000
Binary files a/sapl/static/sapl/frontend/js/compilacao.1c9473f1.js.gz and /dev/null differ
diff --git a/sapl/static/sapl/frontend/js/compilacao.1c9473f1.js b/sapl/static/sapl/frontend/js/compilacao.d68d2b28.js
similarity index 99%
rename from sapl/static/sapl/frontend/js/compilacao.1c9473f1.js
rename to sapl/static/sapl/frontend/js/compilacao.d68d2b28.js
index b6fc1085c..707fb379d 100644
--- a/sapl/static/sapl/frontend/js/compilacao.1c9473f1.js
+++ b/sapl/static/sapl/frontend/js/compilacao.d68d2b28.js
@@ -1 +1 @@
-(()=>{var r,c,t,i={4248:(t,e,i)=>{var o=i(9755),l=(i(2564),i(7327),i(1539),i(9826),i(1038),i(8783),i(1249),i(4916),i(3123),window.$);window.DispositivoEdit=function(){var d,n="textarea";if(!(this instanceof window.DispositivoEdit))return d=d||new window.DispositivoEdit;d=this,window.DispositivoEdit=function(){return d},d.bindActionsEditorType=function(t){n=this.getAttribute("editortype"),window.SetCookie("editortype",n,30);var e=l(this).closest(".dpt").attr("pk");d.clearEditSelected(),d.triggerBtnDptEdit(e),t.preventDefault()},d.bindActionsClick=function(t){var e=this.getAttribute("pk"),i={action:this.getAttribute("action"),tipo_pk:this.getAttribute("tipo_pk"),perfil_pk:this.getAttribute("perfil_pk"),variacao:this.getAttribute("variacao"),pk_bloco:this.getAttribute("pk_bloco")},e=e+"/refresh";d.waitShow(),l.get(e,i).done(function(t){d.clearEditSelected(),null!=t.pk&&d.message(t)}).fail(d.waitHide).always(d.waitHide)},d.clearEditSelected=function(){l(".dpt-selected > .dpt-form").html(""),l(".dpt-actions, .dpt-actions-bottom").html(""),window.tinymce.remove(),l(".dpt-selected").removeClass("dpt-selected")},d.editDispositivo=function(t){var e,t=t.target.classList.contains("dpt-link")?t.target:t.target.parentElement.classList.contains("dpt-link")?t.target.parentElement:null;t&&t.getAttribute("href")&&0 .dpt-form").html("")},500)},d.get_form_base=function(){var t=l(this);t.addClass("dpt-selected"),t.children().filter(".dpt-form").find("form").submit(d.onSubmitEditFormBase),d.scrollTo(t),t.off("get_form_base");t.find(".btn-fechar").on("click",function(t){d.clearEditSelected(),t.preventDefault()});var e=t.find(".btns-excluir");t.find(".dpt-actions-bottom").first().append(e),e.find(".btn-outline-danger").on("click",d.bindActionsClick)},d.get_form_alteracao=function(){var e=l(this),t=(e.off("get_form_alteracao"),l(".dpt-actions, .dpt-actions-bottom").html(""),e.children().filter(".dpt-form").children().first()),i=t[0].id_dispositivo_search_form.value;window.DispositivoSearch({url_form:i,text_button:"Selecionar",autostart:!0}),d.scrollTo(e),t.submit(d.onSubmitFormRegistraAlteracao),e.find(".btn-fechar").on("click",function(t){d.clearEditSelected(),d.triggerBtnDptEdit(e.attr("pk")),t.preventDefault()})},d.get_form_inclusao=function(){var e=l(this),t=(e.off("get_form_inclusao"),l(".dpt-actions, .dpt-actions-bottom").html(""),e.children().filter(".dpt-form").children().first()),i=t[0].id_dispositivo_search_form.value;window.DispositivoSearch({url_form:i,text_button:"Selecionar",post_selected:d.allowed_inserts_registro_inclusao,params_post_selected:{pk_bloco:e.attr("pk")},autostart:!0}),d.scrollTo(e),t.submit(d.onSubmitFormRegistraInclusao),e.find(".btn-fechar").on("click",function(t){d.clearEditSelected(),d.triggerBtnDptEdit(e.attr("pk")),t.preventDefault()})},d.get_form_revogacao=function(){var t=l(this),e=(t.off("get_form_revogacao"),l(".dpt-actions, .dpt-actions-bottom").html(""),t.children().filter(".dpt-form").children().first()),i=e[0].id_dispositivo_search_form.value;window.DispositivoSearch({url_form:i,text_button:"Selecionar",autostart:!0}),d.scrollTo(t),e.submit(d.onSubmitFormRegistraRevogacao),t.find(".btn-fechar").on("click",function(){d.clearEditSelected(),d.triggerBtnDptEdit(t.attr("pk"))})},d.allowed_inserts_registro_inclusao=function(t){var e=l("#id"+t.pk_bloco+" input[name='dispositivo_base_para_inclusao']");0!==e.length&&(e=e[0].value,t={action:"get_actions_allowed_inserts_registro_inclusao",pk_bloco:t.pk_bloco},e=e+"/refresh",d.waitShow(),l.get(e,t).done(function(t){l(".allowed_inserts").html(t),l(".allowed_inserts").find(".btn-action").on("click",d.bindActionsClick)}).fail(d.waitHide).always(d.waitHide))},d.loadActionsEdit=function(e){var t=e.attr("pk");l.get(t+"/refresh?action=get_actions").done(function(t){e.find(".dpt-actions").first().html(t),e.find(".btn-action").on("click",d.bindActionsClick),e.find(".btn-compila").on("click",d.loadFormsCompilacao),e.find(".btn-editor-type").on("click",d.bindActionsEditorType),"construct"===n&&(e.find(".btn-group-inserts").first().addClass("open show"),e.find(".btn-group-inserts ul").first().addClass("show")),e.find(".btn-group-inserts button").mouseenter(function(t){e.find(".btn-group-inserts ul").removeClass("show"),e.find(".btn-group-inserts").removeClass("open show"),l(this.parentElement).addClass("open show"),l(this.parentElement).find("ul").addClass("show")}),e.find(".btn-group-inserts").mouseleave(function(t){e.find(".btn-group-inserts ul").removeClass("show"),e.find(".btn-group-inserts").removeClass("open show")}),d.gc()})},d.loadForm=function(e,i){var t=e.attr("pk"),o=e.children().filter(".dpt-form");1===o.length&&l.get(t+"/refresh?action="+i).done(function(t){"construct"!==n&&(o.html(t),"tinymce"===n&&window.initTextRichEditor(null,!1,!0)),e.trigger(i)}).always(function(){d.waitHide()})},d.loadFormsCompilacao=function(t){var e=l(this).closest(".dpt"),i=this.getAttribute("action");e.on(i,d[i]),d.loadForm(e,i)},d.modalMessage=function(t,e,i){return null!==t&&""!==t&&(l("#modal-message #message").html(t),l("#modal-message").modal("show"),l("#modal-message, #modal-message .alert button").off(),l("#modal-message .alert").removeClass("alert-success alert-info alert-warning alert-danger alert-danger"),l("#modal-message .alert").addClass(e),null!=i&&l("#modal-message").on("hidden.bs.modal",i),l("#modal-message .alert button").on("click",function(){l("#modal-message").modal("hide")}),!0)},d.message=function(t){var e,i;void 0!==t.message?t.message.modal?d.modalMessage(t.message.value,"alert-"+t.message.type,function(){d.waitShow(),d.refreshScreenFocusPk(t)}):(d.refreshScreenFocusPk(t),"message"in t&&((e=l(".cp-notify")).removeClass("hide"),(i=e.find(".message")).html(t.message.value),i.removeClass("bg-primary bg-success bg-info bg-warning bg-danger").addClass("bg-"+t.message.type),setTimeout(function(){e.addClass("hide")},t.message.time||3e3))):d.refreshScreenFocusPk(t)},d.offClicks=function(){l(".btn-dpt-edit").off()},d.onClicks=function(t){t=null==t?l(".btn-dpt-edit"):l(t).find(".btn-dpt-edit");t.on("click",d.editDispositivo)},d.onSubmitFormRegistraAlteracao=function(t){var e,i;void 0===this.dispositivo_alterado?d.modalMessage("Nenhum dispositivo selecionado","alert-info"):(e=void 0===this.dispositivo_alterado.length?[this.dispositivo_alterado]:Array.from(this.dispositivo_alterado),e={csrfmiddlewaretoken:this.csrfmiddlewaretoken.value,dispositivo_alterado:e.filter(function(t,e,i){return t.checked}).map(function(t){return t.value}),formtype:"get_form_alteracao"},i=l(this).closest(".dpt").attr("pk")+"/refresh",d.waitShow(),l.post(i,e).done(function(t){d.clearEditSelected(),null!=t.pk?d.message(t):alert("Erro na resposta!")}).always(function(){d.waitHide()})),null!=t&&t.preventDefault()},d.onSubmitFormRegistraInclusao=function(t){var e={csrfmiddlewaretoken:this.csrfmiddlewaretoken.value,dispositivo_base_para_inclusao:this.dispositivo_base_para_inclusao.value,formtype:"get_form_inclusao"},i=l(this).closest(".dpt").attr("pk")+"/refresh";d.waitShow(),l.post(i,e).done(function(t){d.clearEditSelected(),null!=t.pk?d.message(t):alert("Erro na resposta!")}).always(function(){d.waitHide()}),null!=t&&t.preventDefault()},d.onSubmitFormRegistraRevogacao=function(t){var e,i;void 0===this.dispositivo_revogado?d.modalMessage("Nenhum dispositivo selecionado","alert-info"):(e=void 0===this.dispositivo_revogado.length?[this.dispositivo_revogado]:Array.from(this.dispositivo_revogado),e={csrfmiddlewaretoken:this.csrfmiddlewaretoken.value,dispositivo_revogado:e.filter(function(t,e,i){return t.checked}).map(function(t){return t.value}),revogacao_em_bloco:this.revogacao_em_bloco.value,formtype:"get_form_revogacao"},i=l(this).closest(".dpt").attr("pk")+"/refresh",d.waitShow(),l.post(i,e).done(function(t){d.clearEditSelected(),null!=t.pk?d.message(t):alert("Erro na resposta!")}).always(function(t){d.waitHide()})),null!=t&&t.preventDefault()},d.onSubmitEditFormBase=function(t){var i=this,e="",o="",n="",a=window.tinymce.get("id_texto"),s=window.tinymce.get("id_texto_atualizador"),e=null!=a?a.getContent():this.id_texto.value,a=(null!=s?o=s.getContent():"id_texto_atualizador"in this&&(o=this.id_texto_atualizador.value),"visibilidade"in this&&(n=this.visibilidade.value),{csrfmiddlewaretoken:this.csrfmiddlewaretoken.value,texto:e,texto_atualizador:o,visibilidade:n,formtype:"get_form_base"}),s=l(this).closest(".dpt").attr("pk")+"/refresh";d.waitShow(),l.post(s,a).done(function(t){var e;"string"==typeof t?(e=l(i).closest(".dpt"),e=l("#"+e.replaceWith(t).attr("id")),d.onClicks(e),d.waitHide()):(d.clearEditSelected(),null!=t.pk?d.message(t):alert("Erro na resposta!"))}).always(function(){d.waitHide()}),null!=t&&t.preventDefault()},d.refreshContent=function(i,o){var n;0===i.length?d.waitHide():(n=i.shift(),l.get(n+"/refresh").done(function(t){var e=l("#id"+n).closest(".dpt"),e=l("#"+e.replaceWith(t).attr("id"));d.onClicks(e),d.reloadFunctionsDraggables(),0 .dpt-text.btn-dpt-edit");(e=0===e.length?l("#id"+t+" > .dpt-actions-fixed > .btn-dpt-edit"):e).first().trigger("click")},d.waitHide=function(){l("#wait_message").addClass("displaynone")},d.waitShow=function(){l("#wait_message").removeClass("displaynone")},d.init=function(){l(".dpt-actions-fixed").first().css("opacity","1"),null!==(n=window.ReadCookie("editortype"))&&""!==n||(n="textarea",window.SetCookie("editortype",n,30)),d.offClicks(),d.onClicks(),d.reloadFunctionsDraggables();var t=location.href.split("#");2===t.length&&""!==t[1]&&d.triggerBtnDptEdit(t[1]),l("main").click(function(t){t.target!==this&&t.target!==this.firstElementChild||d.clearEditSelected()}),d.waitHide()},d.init()},l(document).ready(function(){0{"use strict";i(6992),i(8674),i(9601),i(7727),i(9554),i(1539),i(4747),i(3710),i(2130),i(9550),i(2772),i(8221),i(9826),i(7327),i(8862),i(2564),i(4916),i(3123),i(1058);var o=i(9755),f=window.$;function m(t){f(t).append('
')}var n={SetCookie:function(t,e,i){var o=new Date,n=new Date;null!==i&&0!==i||(i=1),n.setTime(o.getTime()+864e5*i),document.cookie=t+"="+escape(e)+";expires="+n.toGMTString()},ReadCookie:function(t){var e,i=" "+document.cookie,o=i.indexOf(" "+t+"=");return-1===(o=-1===o?i.indexOf(";"+t+"="):o)||""===t?"":(-1===(e=i.indexOf(";",o+1))&&(e=i.length),unescape(i.substring(o+t.length+2,e)))},insertWaitAjax:m,InitViewTAs:function(){setTimeout(function(){var t=location.href.split("#");if(2===t.length)try{o("html, body").animate({scrollTop:o("#dptt"+t[1]).offset().top-window.innerHeight/9},0)}catch(t){}},100),o("#btn_font_menos").click(function(){o(".dpt").css("font-size","-=1")}),o("#btn_font_mais").click(function(){o(".dpt").css("font-size","+=1")}),o(".dpt.bloco_alteracao .dpt").each(function(){var t=parseInt(o(this).attr("nivel"));o(this).css("z-index",15-t)}),o(".cp-linha-vigencias > li:not(:first-child):not(:last-child) > a").click(function(t){o(".cp-linha-vigencias > li").removeClass("active"),o(this).closest("li").addClass("active"),t.preventDefault()}),o("main").click(function(t){t.target!==this&&t.target!==this.firstElementChild||o(".cp-linha-vigencias > li").removeClass("active")})},DispositivoSearch:function(d){f(function(){var u={},s=f("body").children("#container_ds");0 '),f("body").prepend(s),f('[data-sapl-ta="DispositivoSearch"]').each(function(){function e(t){var e;"checkbox"===r?((e=a.find('input[name="ta_select_all"]')).off(),e.on("change",function(t){f(this).closest("ul").find('input[name="'+c+'"]').prop("checked",this.checked)})):((e=a.find("input")).off(),e.attr("type","hidden"),f('').insertBefore(e).append(f('× ')).on("click",function(){(2===f(this).closest("ul").find("li").length?f(this).closest("ul"):f(this).closest("li")).remove()}))}function o(t){var e=f('select[name="tipo_ta"]').val(),i=f('select[name="tipo_model"]').val(),o=f('input[name="num_ta"]').val(),n=f('input[name="ano_ta"]').val(),a=f('input[name="tipo_resultado"]:checked').val(),s=f('input[name="rotulo_dispositivo"]').val(),d=f('input[name="texto_dispositivo"]').val(),l=f('select[name="max_results"]').val();0 '),a.prepend(t),f("").text(d.text_button).attr("type","button").attr("class","btn btn-sm btn-success btn-modal-open"));t.append(i),i.on("click",function(){f.get(d.url_form,function(t){s.html(t);var i=f("#modal-ds");i.find('select[name="tipo_ta"]').change(function(t){var e="/ta/search_fragment_form?action=get_tipos&tipo_ta="+this.value,o=(i.find('label[for="id_tipo_model"]').html("Tipos de "+this.children[this.selectedIndex].innerHTML),i.find('select[name="tipo_model"]'));o.empty(),f('Carregando... ').appendTo(o),f.get(e).done(function(t){for(var e in o.empty(),t)for(var i in t[e])o.append(f("").attr("value",i).text(t[e][i]));setTimeout(function(){f('select[name="tipo_model"]').val(u.tipo_model)},200)})}),i.find('input[name="texto_dispositivo"], input[name="rotulo_dispositivo"]').on("keyup",n),i.find(".btn-busca").click(o),i.find("#btn-modal-select").click(function(){var t=a.find("ul"),t=("radio"===r&&t.remove(),i.find('[name="'+c+'"]:checked'));t.closest("ul").find("input:not(:checked)").filter('[name!="ta_select_all"]').closest("li").remove(),t.closest("ul").each(function(){var t=a.find("#"+this.id);0===t.length?a.append(this):f(this).find("input").each(function(){0 .dn").addClass("displaynone"),s(".desativado > .dpt-img").addClass("displaynone"),s(".nota-alteracao").removeClass("displaynone"),s(".dptt.revogado").removeClass("displaynone"),e||s(".nota-alteracao").addClass("displaynone"),i)try{s("html, body").animate({scrollTop:s(i).parent().offset().top-60},0)}catch(t){}}var c={isElementInViewport:l,textoMultiVigente:function(t,e){for(var i=null,o=s(".dptt"),n=0;n .dn").removeClass("displaynone"),s(".desativado > .dpt-img").removeClass("displaynone"),s(".dptt.revogado").removeClass("displaynone"),s(".dtxt").removeClass("displaynone"),s(".dtxt.diff").remove(),s(".nota-alteracao").removeClass("displaynone"),e&&s('.dtxt[id^="da"').each(function(){var t,e,i,o,n;0/g)||(t=s(this).attr("pk"),e=s(this).attr("pks"),o=s("#d"+e).contents().filter(function(){return this.nodeType===Node.TEXT_NODE}),i=s("#da"+t).contents().filter(function(){return this.nodeType===Node.TEXT_NODE}),0<(o=d.diffWordsWithSpace(s(o).text(),s(i).text())).length&&(s("#d"+e).closest(".desativado").addClass("displaynone"),n=s("#da"+t).clone(),s("#da"+t).after(n),s("#da"+t).addClass("displaynone"),s(n).addClass("diff").html(""),o.forEach(function(t){var e=document.createElement("span"),i=t.value;t.removed?(s(e).addClass("desativado"),i+=" "):t.added&&s(e).addClass("added"),e.appendChild(document.createTextNode(i)),s(n).append(e)})))}),i)try{s("html, body").animate({scrollTop:s(i).parent().offset().top-60},0)}catch(t){}},textoVigente:r,textoVigenteSemRevogados:function(t,e){r(t,e),s(".dptt.revogado").addClass("displaynone")}},p=window.$;function u(i,t){p("html, body").animate({scrollTop:p("#dne"+i).offset().top-window.innerHeight/5},300),window.refreshDatePicker(),p("#dne"+i+" #button-id-submit-form").click(h),p("#dne"+i+" .btn-close-container").click(function(){p(this).closest(".dne-nota").removeClass("dne-nota"),p(this).closest(".dne-form").html("")}),"nota"===t?p("#dne"+i+' select[name="tipo"]').change(function(t){var e="text/"+i+"/nota/create?action=modelo_nota&id_tipo="+this.value;p.get(e).done(function(t){p("#dne"+i+' textarea[name="texto"]').val(t)})}):"vide"===t&&window.DispositivoSearch({url_form:"/ta/search_form",text_button:"Definir Dispositivo"})}function h(t){var e,i="",o="nota",n=p("#id_dispositivo").val();void 0===n&&(n=p("#id_dispositivo_base").val(),o="vide"),e=p("#id_pk").val(),i="text/"+n+"/"+o+"/",p.post(i+=null===e||""===e?"create":e+"/edit",p("#dne"+n+" form").serialize(),function(t){if("string"==typeof t)if(0<=t.indexOf("
@@ -264,18 +264,10 @@
{% block extra_js %}{% endblock extra_js %}
-
- {% if not DEBUG %}
-
-
- {% endif %}
-
+
+
+ {% if not DEBUG %}
+
+
+
+ {% if "google_analytics_id_metrica"|get_config_attr %}
+
+
+
+ {% endif %}
+ {% endif %}
+
+
{% endblock foot_js %}