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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' -
- - -
- ''') - ], - 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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(''' +
+ + +
+ ''') + ], + 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): 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("