Browse Source

Merge branch '3.1.x' into doc_restrito

pull/3613/head
cristian-longhi 1 year ago
committed by GitHub
parent
commit
379d2ef879
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 50
      CHANGES.md
  2. 3
      README.rst
  3. 4
      docker/docker-compose.yaml
  4. 3
      docker/start.sh
  5. 57
      drfautoapi/drfautoapi.py
  6. 2
      frontend/src/__apps/compilacao/js/old/compilacao_edit.js
  7. 8
      frontend/src/__apps/compilacao/scss/compilacao.scss
  8. 3
      frontend/src/__global/js/tinymce/index.js
  9. 180
      frontend/webpack-stats.json
  10. 76
      release.sh
  11. 1
      requirements/dev-requirements.txt
  12. 10
      requirements/requirements.txt
  13. 4
      sapl/api/serializers.py
  14. 2
      sapl/api/views_materia.py
  15. 31
      sapl/audiencia/forms.py
  16. 27
      sapl/audiencia/migrations/0017_audienciapublica_ano.py
  17. 17
      sapl/audiencia/migrations/0018_auto_20230529_1641.py
  18. 8
      sapl/audiencia/models.py
  19. 12
      sapl/audiencia/views.py
  20. 860
      sapl/base/forms.py
  21. 38
      sapl/base/management/commands/backfill_auditlog.py
  22. 18
      sapl/base/migrations/0055_appconfig_mostrar_voto.py
  23. 23
      sapl/base/migrations/0056_auto_20221118_1330.py
  24. 18
      sapl/base/migrations/0057_appconfig_google_analytics_id_metrica.py
  25. 16
      sapl/base/models.py
  26. 298
      sapl/base/receivers.py
  27. 43
      sapl/base/templatetags/common_tags.py
  28. 68
      sapl/base/urls.py
  29. 1103
      sapl/base/views.py
  30. 17
      sapl/comissoes/migrations/0029_auto_20221019_2041.py
  31. 2
      sapl/comissoes/models.py
  32. 1
      sapl/comissoes/views.py
  33. 2
      sapl/compilacao/templatetags/compilacao_filters.py
  34. 4
      sapl/compilacao/urls.py
  35. 33
      sapl/compilacao/views.py
  36. 16
      sapl/crispy_layout_mixin.py
  37. 38
      sapl/endpoint_restriction_middleware.py
  38. 1
      sapl/lexml/OAIServer.py
  39. 39
      sapl/materia/forms.py
  40. 33
      sapl/materia/migrations/0082_auto_20230529_1641.py
  41. 31
      sapl/materia/migrations/0083_auto_20230731_1845.py
  42. 35
      sapl/materia/models.py
  43. 9
      sapl/materia/views.py
  44. 29
      sapl/norma/migrations/0044_auto_20230529_1641.py
  45. 5
      sapl/norma/views.py
  46. 18
      sapl/painel/views.py
  47. 2
      sapl/parlamentares/forms.py
  48. 18
      sapl/parlamentares/migrations/0041_parlamentar_telefone_celular.py
  49. 18
      sapl/parlamentares/migrations/0042_auto_20230529_1641.py
  50. 2
      sapl/parlamentares/models.py
  51. 2
      sapl/parlamentares/views.py
  52. 23
      sapl/protocoloadm/migrations/0044_auto_20230529_1641.py
  53. 5
      sapl/protocoloadm/urls.py
  54. 75
      sapl/protocoloadm/views.py
  55. 783
      sapl/relatorios/forms.py
  56. 62
      sapl/relatorios/urls.py
  57. 1072
      sapl/relatorios/views.py
  58. 2
      sapl/rules/group_geral.py
  59. 6
      sapl/sessao/forms.py
  60. 7
      sapl/sessao/urls.py
  61. 181
      sapl/sessao/views.py
  62. 7
      sapl/settings.py
  63. 5
      sapl/static/sapl/css/relatorio.css
  64. BIN
      sapl/static/sapl/frontend/js/compilacao.1c9473f1.js.gz
  65. 2
      sapl/static/sapl/frontend/js/compilacao.d68d2b28.js
  66. BIN
      sapl/static/sapl/frontend/js/compilacao.d68d2b28.js.gz
  67. BIN
      sapl/static/sapl/frontend/js/global.e8c9c610.js.gz
  68. 4
      sapl/static/sapl/frontend/js/global.f01dd32a.js
  69. 0
      sapl/static/sapl/frontend/js/global.f01dd32a.js.LICENSE.txt
  70. BIN
      sapl/static/sapl/frontend/js/global.f01dd32a.js.gz
  71. 4
      sapl/templates/audiencia/layouts.yaml
  72. 33
      sapl/templates/base.html
  73. 13
      sapl/templates/base/EstatisticasAcessoNormas_filter.html
  74. 49
      sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html
  75. 88
      sapl/templates/base/auditlog_filter.html
  76. 3
      sapl/templates/base/layouts.yaml
  77. 84
      sapl/templates/base/relatorios_list.html
  78. 23
      sapl/templates/base/widget__signs.html
  79. 23
      sapl/templates/compilacao/layouts.yaml
  80. 1
      sapl/templates/materia/layouts.yaml
  81. 17
      sapl/templates/materia/materialegislativa_filter.html
  82. 9
      sapl/templates/materia/proposicao_detail.html
  83. 11
      sapl/templates/materia/recibo_proposicao.html
  84. 14
      sapl/templates/materia/tramitacao_list.html
  85. 3
      sapl/templates/menu_tabelas_auxiliares.yaml
  86. 5
      sapl/templates/navbar.yaml
  87. 3
      sapl/templates/painel/index.html
  88. 6
      sapl/templates/parlamentares/layouts.yaml
  89. 6
      sapl/templates/parlamentares/parlamentar_perfil_publico.html
  90. 9
      sapl/templates/protocoloadm/documentoacessorioadministrativo_list.html
  91. 2
      sapl/templates/relatorios/RelatorioAtas_filter.html
  92. 2
      sapl/templates/relatorios/RelatorioAudiencia_filter.html
  93. 67
      sapl/templates/relatorios/RelatorioDataFimPrazoTramitacao_filter.html
  94. 2
      sapl/templates/relatorios/RelatorioDocumentosAcessorios_filter.html
  95. 2
      sapl/templates/relatorios/RelatorioHistoricoTramitacaoAdm_filter.html
  96. 2
      sapl/templates/relatorios/RelatorioHistoricoTramitacao_filter.html
  97. 0
      sapl/templates/relatorios/RelatorioMateriasAnoAssunto.html
  98. 2
      sapl/templates/relatorios/RelatorioMateriasPorAnoAutorTipo_filter.html
  99. 2
      sapl/templates/relatorios/RelatorioMateriasPorAutor_filter.html
  100. 6
      sapl/templates/relatorios/RelatorioMateriasPorTramitacao_filter.html

50
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

3
README.rst

@ -71,6 +71,9 @@ Orientações gerais sobre o GitHub
===================================
`Instruções para GitHub <https://github.com/interlegis/sapl/blob/3.1.x/docs/howtogit.rst>`_
Suporte ao utilizadores
===================================
`Sala do Discord "Somos Interlegis" sobre SAPL <https://discord.gg/fzXSbhZbcy>`_
Perguntas Frequentes

4
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

3
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 "| ██╔════╝██╔══██╗██╔══██╗██║ |"

57
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 []

2
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()
}

8
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;
}
}
}

3
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'

180
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/"

76
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 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 "================================================================================"
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."
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
;;

1
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

10
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

4
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' %

2
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)

31
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%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']:

27
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),
]

17
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'},
),
]

8
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

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

860
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']
@ -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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check col-auto">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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)
@ -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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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
@ -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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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):

38
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")

18
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?'),
),
]

23
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'),
),
]

18
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'),
),
]

16
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,

298
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')

43
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)

68
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<uidb64>[0-9A-Za-z_\-]+)/'
'(?P<token>[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<path>.*)$', RedirectView.as_view(
url=os.path.join(MEDIA_URL, 'sapl/public/XSLT/HTML/%(path)s'),

1103
sapl/base/views.py

File diff suppressed because it is too large

17
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'},
),
]

2
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

1
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"

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

4
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())),
]

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

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

38
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)

1
sapl/lexml/OAIServer.py

@ -237,6 +237,7 @@ class OAIServer:
return None
def oai_query(self, offset=0, batch_size=10, from_=None, until=None, identifier=None):
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

39
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,21 +1126,37 @@ 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
@ -1496,6 +1512,7 @@ class TramitacaoEmLoteFilterSet(django_filters.FilterSet):
class TipoProposicaoForm(ModelForm):
try:
content_types_choices = [
(
f'{ct.app_label}/{ct.model}',
@ -1505,6 +1522,8 @@ class TipoProposicaoForm(ModelForm):
*models_with_gr_for_model(TipoProposicao)
).items()
]
except:
content_types_choices = []
logger = logging.getLogger(__name__)

33
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'),
),
]

31
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'),
),
]

35
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

9
sapl/materia/views.py

@ -1260,9 +1260,12 @@ 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):

29
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'),
),
]

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

18
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,7 +492,12 @@ 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:
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."
@ -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))

2
sapl/parlamentares/forms.py

@ -726,6 +726,8 @@ class BlocoForm(ModelForm):
)
else:
bloco.save()
bloco.partidos.set(self.cleaned_data['partidos'])
return bloco

18
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'),
),
]

18
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'),
),
]

2
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(

2
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)

23
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'),
),
]

5
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<pk>\d+)/vinculo-em-lote', VinculoDocAdminMateriaEmLoteView.as_view(),
name='vinculodocadminmateria_em_lote'),
url(r'^docadm/documentoacessorioadministrativo/pdf/(?P<pk>\d+)$', get_pdf_docacessorios,
name='merge_docacessorios')
]
urlpatterns_protocolo = [

75
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__)

783
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check col-auto">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
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)

62
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<pk>\d+)/etiqueta-materia-legislativa$',
etiqueta_materia_legislativa, name='etiqueta_materia_legislativa'),
url(r'^relatorios/(?P<pk>\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'),
]

1072
sapl/relatorios/views.py

File diff suppressed because it is too large

2
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()),

6
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)

7
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<pk>\d+)/votacao_bloco_expediente$',
VotacaoEmBlocoExpediente.as_view(),
name='votacao_bloco_expediente'),
url(r'^sessao/(?P<pk>\d+)/leitura_bloco_expediente$',
LeituraEmBlocoExpediente.as_view(),
name='leitura_bloco_expediente'),
url(r'^sessao/(?P<pk>\d+)/leitura_bloco_ordem_dia$',
LeituraEmBlocoOrdemDia.as_view(),
name='leitura_bloco_ordem_dia'),
url(r'^sessao/(?P<pk>\d+)/resumo$',
ResumoView.as_view(), name='resumo'),
url(r'^sessao/(?P<pk>\d+)/resumo_ata$',

181
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))
@ -235,11 +235,11 @@ 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')\
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:
@ -247,13 +247,21 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
if t[0] == tramitacao.turno:
turno = t[1]
break
materia_em_tramitacao = MateriaEmTramitacao.objects\
.select_related("materia", "tramitacao")\
.filter(materia=materia)\
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"""
<strong>
<a href="{materia.texto_original.url}" target="_blank">
Texto original
</a>
</strong>"""
title_materia = f"""<div onmouseover = "mostra_autores({idAutor}, {idAutores})" onmouseleave = "autor_unico({idAutor}, {idAutores})">
<a id={obj.materia.id} href={url_materia}>{row[1][0]}</a></br>
<b>Processo:</b> {numeracao}</br>
@ -261,6 +269,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
<span id='{idAutores}' style="display: none"><b>Autor:</b> {todos_autores}</br></span>
<b>Protocolo:</b> {num_protocolo}</br>
<b>Turno:</b> {turno}</br>
{link_texto_original}
</div>
"""
# 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:
@ -1815,7 +1824,8 @@ 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={})."
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)})
@ -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,7 +2327,7 @@ 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)\
for mevn in ExpedienteMateria.objects.filter(sessao_plenaria_id=self.object.id, tipo_votacao=2) \
.order_by('-materia'):
votos_materia = []
titulo_materia = mevn.materia
@ -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,7 +2533,8 @@ 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.'
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)
@ -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)
@ -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)
@ -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)
@ -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
"""
@ -3887,7 +3898,7 @@ class PautaSessaoDetailView(DetailView):
# =====================================================================
# Expedientes
expedientes = []
for e in ExpedienteSessao.objects.select_related("tipo").filter(sessao_plenaria_id=self.object.id)\
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
@ -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,
@ -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
"""
@ -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
@ -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)

7
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',)

5
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;

BIN
sapl/static/sapl/frontend/js/compilacao.1c9473f1.js.gz

Binary file not shown.

2
sapl/static/sapl/frontend/js/compilacao.1c9473f1.js → sapl/static/sapl/frontend/js/compilacao.d68d2b28.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/compilacao.d68d2b28.js.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/js/global.e8c9c610.js.gz

Binary file not shown.

4
sapl/static/sapl/frontend/js/global.e8c9c610.js → sapl/static/sapl/frontend/js/global.f01dd32a.js

File diff suppressed because one or more lines are too long

0
sapl/static/sapl/frontend/js/global.e8c9c610.js.LICENSE.txt → sapl/static/sapl/frontend/js/global.f01dd32a.js.LICENSE.txt

BIN
sapl/static/sapl/frontend/js/global.f01dd32a.js.gz

Binary file not shown.

4
sapl/templates/audiencia/layouts.yaml

@ -1,7 +1,7 @@
{% load i18n %}
AudienciaPublica:
{% trans 'Audiência Pública' %}:
- nome:10 numero
- nome:8 numero ano
- tema
{% trans 'Dados' %}:
- tipo_materia numero_materia ano_materia
@ -14,7 +14,7 @@ AudienciaPublica:
AudienciaPublicaDetail:
{% trans 'Audiência Pública' %}:
- nome:10 numero
- nome:10 numero ano
- tema
{% trans 'Dados' %}:
- materia tipo

33
sapl/templates/base.html

@ -194,7 +194,7 @@
<small>
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small>
<span>Release: 3.1.163-RC7</span>
<span>Release: 3.1.163-RC16</span>
</p>
</div>
@ -264,18 +264,10 @@
{% block extra_js %}{% endblock extra_js %}
{% if not DEBUG %}
<script src="https://vlibras.gov.br/app/vlibras-plugin.js"></script>
<script>
new window.VLibras.Widget('https://vlibras.gov.br/app');
</script>
{% endif %}
<script type="text/javascript" >
function inIframe () {
try {
return window.self !== window.top;
return window.location !== window.parent.location
} catch (e) {
return true;
}
@ -297,6 +289,27 @@
});
</script>
{% if not DEBUG %}
<script src="https://vlibras.gov.br/app/vlibras-plugin.js"></script>
<script>
new window.VLibras.Widget('https://vlibras.gov.br/app');
</script>
{% if "google_analytics_id_metrica"|get_config_attr %}
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{"google_analytics_id_metrica"|get_config_attr}}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{"google_analytics_id_metrica"|get_config_attr}}');
</script>
{% endif %}
{% endif %}
{% endblock foot_js %}
</body>

13
sapl/templates/base/EstatisticasAcessoNormas_filter.html

@ -1,6 +1,6 @@
{% extends "crud/list.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load crispy_forms_tags common_tags %}
{% block base_content %}
{% if not ano %}
@ -22,7 +22,12 @@
<table class="table table-bordered table-hover" style="width:100%; margin-bottom: 30px;">
<thead class="thead-default" >
<tr>
<th colspan=3><h3 style="text-align:center;">Mês: {{ mes }}</h3></th>
<th colspan=3>
<h3 style="text-align:center;">Mês: {{ mes }}</h3>
<center>
<h5>Um total de acessos {{normas_count_mes|lookup:mes}} nas {{normas|length}} mais acessadas.</h5>
</center>
</th>
</tr>
<tr class="active">
<th>Posição</th>
@ -70,9 +75,13 @@
$('#id_mais_acessadas').val('5')
$('#id_mais_acessadas')[0].options[2].setAttribute('disabled', true)
$('#id_mais_acessadas')[0].options[3].setAttribute('disabled', true)
$('#id_mais_acessadas')[0].options[4].setAttribute('disabled', true)
$('#id_mais_acessadas')[0].options[5].setAttribute('disabled', true)
} else {
$('#id_mais_acessadas')[0].options[2].removeAttribute('disabled')
$('#id_mais_acessadas')[0].options[3].removeAttribute('disabled')
$('#id_mais_acessadas')[0].options[4].removeAttribute('disabled')
$('#id_mais_acessadas')[0].options[5].removeAttribute('disabled')
}
//$('#id_mais_acessadas').prop('disabled', event.currentTarget.selectedOptions[0].value === '')

49
sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html

@ -1,49 +0,0 @@
{% extends "crud/list.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block base_content %}
{% if not show_results %}
<br>
{% crispy filter.form %}
{% else %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.base:data_fim_prazo_tramitacoes' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b>
&emsp;Ano: {{ ano }} <br />
&emsp;Período: {{ data_tramitacao }} <br />
&emsp;Tipo de matéria: {{ tipo }}<br />
&emsp;Status de tramitação: {{ tramitacao__status }}<br />
&emsp;Local de origem: {{ tramitacao__unidade_tramitacao_local }}<br />
&emsp;Local de destino: {{ tramitacao__unidade_tramitacao_destino }}<br /><br /><br />
{% if object_list %}
{% if object_list|length == 1 %}
<tr><td><h3 style="text-align: left;">Foi encontrada 1 matéria com esses parâmetros.</h3></td></tr><br><br>
{% else %}
<tr><td><h3 style="text-align: left;">Foram encontradas {{object_list|length}} matérias com esses parâmetros.</h3></td></tr><br><br>
{% endif %}
<table class="table table-bordered table-hover">
<thead class="thead-default" >
<tr class="active">
<th>Matéria</th>
<th>Ementa</th>
</tr>
</thead>
<tbody>
{% for materia in object_list %}
<tr>
<td><a href="{% url 'sapl.materia:tramitacao_list' materia.pk %}">
{{materia.tipo.descricao}} - {{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}}
</a></td>
<td>{{materia.ementa}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<tr><td><h3 style="text-align: left;">Nenhuma matéria encontrada com esses parâmetros.</h3></td></tr><br><br>
{% endif %}
{% endif %}
{% endblock base_content %}

88
sapl/templates/base/auditlog_filter.html

@ -0,0 +1,88 @@
{% extends "crud/list.html" %}
{% load i18n common_tags %}
{% load tz %}
{% load crispy_forms_tags staticfiles %}
{% block head_extra_css %}
created {
background-color: green;
color: #FFF;
}
deleted {
background-color: red;
color: #FFF;
}
{% endblock head_extra_css %}
{% block base_content %}
{% crispy filter.form %}
<br>
{% if numero_res > 0 %}
{% if numero_res == 1 %}
<h3>Foi encontrado {{ numero_res }} resultado</h3>
{% else %}
<h3>Foram encontrados {{ numero_res }} resultados</h3>
{% endif %}
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Data/Hora</th>
<th>Usuário</th>
<th>Operação</th>
<th>Registro</th>
<th>Id</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for obj in page_obj %}
<tr class="background:{%if obj.operation == 'D' %}red{%else%}lightgray{%endif%}">
<td>{{ obj.timestamp|localtime|date:"d/m/Y, H:i:s" }}</td>
<td>{{ obj.username|default:"Não informado" }}</td>
<td>{{ obj.operation|desc_operation }}</td>
<td>{{ obj.model_name }}</td>
<td>{{obj.data.pk}}</td>
<td>
<strong>Atributos ({{obj.data.fields|length}})</strong><br/>
<hr/>
<ul>
{% for key, value in obj.data.fields.items %}
{% if forloop.counter == 11 %}
<div id="{{obj.id}}" style="display:none;">
{%endif%}
<li>
{{key}}: {{ value|default_if_none:""|obfuscate_value:key }}<br/>
</li>
{% if forloop.last and forloop.counter > 10 %}
</div>
<input class="btn btn-primary btn-sm" type="button" value="Expandir/Colapsar" onclick="toggleDetails({{obj.id}})"/>
{% endif %}
{% endfor %}
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<font size="4"><p align="center">{{ NO_ENTRIES_MSG }}</p></font>
{% endif %}
<br/>
{% include 'paginacao.html'%}
<br /><br /><br />
{% endblock base_content %}
{% block extra_js %}
<script language="Javascript">
function toggleDetails(id) {
let curr = document.getElementById(id);
if (curr.style.display == "none") {
document.getElementById(id).style.display = "block";
}
else {
document.getElementById(id).style.display = "none";
}
}
</script>
{% endblock extra_js %}

3
sapl/templates/base/layouts.yaml

@ -48,10 +48,11 @@ AppConfig:
{% trans 'Módulo Painel' %}:
- cronometro_discurso cronometro_aparte
- cronometro_ordem cronometro_consideracoes
- mostrar_brasao_painel
- mostrar_brasao_painel mostrar_voto
{% trans 'Estatísticas de acesso' %}:
- estatisticas_acesso_normas
- google_analytics_id_metrica
{% trans 'Segurança' %}:
- google_recaptcha_site_key google_recaptcha_secret_key

84
sapl/templates/base/relatorios_list.html

@ -1,84 +0,0 @@
{% extends "base.html" %}
{% load i18n crispy_forms_tags %}
{% block base_content %}
<fieldset>
<legend>Relatórios Administrativos</legend>
<table class="table">
<thead>
<tr>
<th>Título</th>
<th>Descrição</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="{% url 'sapl.base:materia_por_tramitacao' %}">Matérias em tramitação</a></td>
<td> Matérias Legislativas por Ano, Tipo, Local atual e Status da Tramitação informados. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:materia_por_autor' %}">Matérias por Autor</a></td>
<td> Listagem e totalização de matérias por autor, com filtros para tipo e período. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:materia_por_ano_autor_tipo' %}">Matérias por Ano, Autor e Tipo</a></td>
<td> Totalização anual de matérias agrupadas por autor e tipo. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:materia_por_ano_assunto' %}">Matérias por Ano, Assunto</a></td>
<td> Totalização de matérias agrupadas por ano e assunto. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:presenca_sessao' %}">Presença nas sessões</a></td>
<td>Presença dos parlamentares nas sessões plenárias.</td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:atas' %}">Atas</a></td>
<td> Atas de Sessão Plenária. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:historico_tramitacoes' %}">Histórico de tramitações de Matérias</a></td>
<td> Histórico de tramitações por período e local informados. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:data_fim_prazo_tramitacoes' %}">Tramitações de Matérias</a></td>
<td> Tramitações de matéria com status informado no intervalo. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:reuniao' %}">Reunião de Comissão</a></td>
<td> Reunião de Comissão por data. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:audiencia' %}">Audiência Pública</a></td>
<td> Audiência Pública com o tipo. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:normas_por_mes' %}">Normas por mês</a></td>
<td> Normas publicadas por mês. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:normas_por_vigencia' %}">Normas por vigência</a></td>
<td> Normas vigentes ou não vigentes. </td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:historico_tramitacoes_adm' %}">Histórico de tramitações de Documentos</a></td>
<td> Histórico de tramitações de Documentos por período e local informados. </td>
</tr>
{% if estatisticas_acesso_normas %}
<tr>
<td><a href="{% url 'sapl.base:estatisticas_acesso' %}">Estatísticas de acesso de Normas</a></td>
<td> Normas por acesso. </td>
</tr>
{% endif %}
<tr>
<td><a href="{% url 'sapl.base:relatorio_documentos_acessorios' %}"> Documentos Acessórios de Matérias Legislativas</a></td>
<td> Documentos Acessórios por tipo, período e tipo da Matéria Legislativa associada.</td>
</tr>
<tr>
<td><a href="{% url 'sapl.base:normas_por_autor' %}"> Normas Por Autor</a></td>
<td> Listagem e totalização de normas por autor, com filtros para tipo e período.</td>
</tr>
</tbody>
</table>
</fieldset>
{% endblock base_content %}

23
sapl/templates/base/widget__signs.html

@ -0,0 +1,23 @@
{% load common_tags%}
{% for nome, data in signs %}
{% if forloop.first %}
<div class="col d-flex align-items-center">
<div class="box-assinatura-eletronica">
<ul class="sigs px-0">
{% endif %}
<li class="sig">
<span class="sig-nome">
{{nome}}
</span>
<small class="sig-data">
(Assinado em: {{data.0|parse_datetime|date:"DATETIME_FORMAT" }} -
<small>{{data.1}}</small>)
</small>
</li>
{% if forloop.last %}
</ul>
</div>
</div>
{% endif %}
{% endfor %}

23
sapl/templates/compilacao/layouts.yaml

@ -21,13 +21,12 @@ PerfilEstruturalTextoArticulado:
- sigla:2 nome
TipoDispositivo:
{% trans 'Dados Básicos' %}:
- nome:8 class_css
{% trans 'Configurações para Edição do Rótulo' %}:
- rotulo_prefixo_texto rotulo_sufixo_texto rotulo_ordinal contagem_continua
{% trans 'Dados Básicos Fixos' %}:
- nome class_css:2 dispositivo_de_articulacao:4 dispositivo_de_alteracao
- contagem_continua:2 rotulo_prefixo_texto:3 rotulo_ordinal:4 rotulo_sufixo_texto
{% trans 'Configurações para Renderização de Rótulo e Texto' %}:
- rotulo_prefixo_html rotulo_sufixo_html
- texto_prefixo_html dispositivo_de_articulacao dispositivo_de_alteracao texto_sufixo_html
- texto_prefixo_html texto_sufixo_html
{% trans 'Configurações para Nota Automática' %}:
- nota_automatica_prefixo_html nota_automatica_sufixo_html
{% trans 'Configurações para Variações Numéricas' %}:
@ -38,6 +37,20 @@ TipoDispositivo:
- rotulo_separador_variacao34:5 formato_variacao4
- rotulo_separador_variacao45:5 formato_variacao5
TipoDispositivoUpdate:
{% trans 'Configurações para Variações Numéricas' %}:
- rotulo_ordinal formato_variacao0
- rotulo_separador_variacao01:5 formato_variacao1
- rotulo_separador_variacao12:5 formato_variacao2
- rotulo_separador_variacao23:5 formato_variacao3
- rotulo_separador_variacao34:5 formato_variacao4
- rotulo_separador_variacao45:5 formato_variacao5
{% trans 'Configurações para Renderização de Rótulo e Texto' %}:
- rotulo_prefixo_html rotulo_sufixo_html
- texto_prefixo_html texto_sufixo_html
{% trans 'Configurações para Nota Automática' %}:
- nota_automatica_prefixo_html nota_automatica_sufixo_html
TipoTextoArticulado:
{% trans 'Identificação Básica' %}:

1
sapl/templates/materia/layouts.yaml

@ -146,6 +146,7 @@ MateriaLegislativaDetail:
- tipo ano numero
- data_apresentacao numero_protocolo tipo_apresentacao
- texto_original
- texto_original|widget__signs
- numeracao_set
- materia_anexada_set__materia_principal|m2m_urlize_for_detail
- materia_principal_set__materia_anexada|m2m_urlize_for_detail

17
sapl/templates/materia/materialegislativa_filter.html

@ -200,3 +200,20 @@
{% endblock detail_content %}
{% block table_content %}
{% endblock table_content %}
{% block extra_js %}
<script type="text/javascript" >
function pesquisaAvancada(){
$('.pesquisa_avancada').toggle();
var id_btn = "#btn_pesquisa_avancada_id";
if ($(id_btn).val().endsWith('>>>')){
$(id_btn).val($(id_btn).val().replace('>>>', '<<<'))
}else{
$(id_btn).val($(id_btn).val().replace('<<<', '>>>'))
}
};
</script>
{% endblock extra_js %}

9
sapl/templates/materia/proposicao_detail.html

@ -114,7 +114,14 @@
<div id="div_id_obseracao" class="form-group">
<p class="control-label">Enviada por</p>
<div class="controls">
<div class="form-control-static">{{ proposicao.user|format_user }}</div>
<div class="form-control-static">
{# TODO remover condicional apos migrar as acoes para os devidos campos#}
{% if proposicao.usuario_envio %}
{{ proposicao.usuario_envio|format_user }}
{% else %}
{{ proposicao.user|format_user }}
{% endif %}
</div>
</div>
</div>
</div>

11
sapl/templates/materia/recibo_proposicao.html

@ -50,8 +50,15 @@
<td>Tipo de Proposição: <b>{{proposicao.tipo.descricao}}</b></td>
</tr>
<tr>
<td>Autor: <b>{{proposicao.autor}}</b></td>
<td>Enviada por: <b>{{ proposicao.user|format_user }}</b></td>
<td>Autor: <b>{{ proposicao.autor }}</b></td>
<td>Enviada por:
{# TODO remover condicional apos migrar as acoes para os devidos campos#}
{% if proposicao.usuario_envio %}
<b>{{ proposicao.usuario_envio|format_user }}</b>
{% else %}
<b>{{ proposicao.user }}</b>
{% endif %}
</td>
</tr>
<tr>
<td>Descrição: <b>{{proposicao.descricao}}</b></td>

14
sapl/templates/materia/tramitacao_list.html

@ -0,0 +1,14 @@
{% extends "crud/list.html" %}
{% load i18n %}
{% load common_tags %}
{% block more_buttons %}
{% if perms|get_add_perm:view %}
<a href="{% url 'sapl.relatorios:relatorio_materia_tramitacao' root_pk %}" class="btn btn-outline-primary">
{% trans "Imprimir" %}
</a>
{% endif %}
{% endblock more_buttons %}

3
sapl/templates/menu_tabelas_auxiliares.yaml

@ -208,6 +208,9 @@
- title: {% trans 'Tipos de Vides' %}
url: sapl.compilacao:tipovide_list
css_class: btn btn-link
- title: {% trans 'Tipos de Dispositivos' %}
url: sapl.compilacao:tipodispositivo_list
css_class: btn btn-link
- title: {% trans 'Módulo LexML' %}
css_class: head_title
children:

5
sapl/templates/navbar.yaml

@ -57,7 +57,7 @@
url: sapl.materia:proposicao_list
check_permission: materia.add_proposicao
- title: {% trans 'Relatórios' %}
url: sapl.base:relatorios_list
url: sapl.relatorios:relatorios_list
- title: {% trans 'Sessões Plenárias' %}
url: sapl.sessao:pesquisar_sessao
- title: {% trans 'Tramitação em Lote' %}
@ -97,6 +97,9 @@
- title: {% trans 'Inconsistências de Dados' %}
url: {% url 'sapl.base:lista_inconsistencias' %}
check_permission: user.is_superuser
- title: {% trans 'Logs de Auditoria' %}
url: {% url 'sapl.base:pesquisar_auditlog' %}
check_permission: user.is_superuser
{% comment %}
<li class="nav__sub-item"><a class="nav__sub-link" href="#">Provedor LexML</a></li>

3
sapl/templates/painel/index.html

@ -281,11 +281,12 @@
var presentes_list = data["presentes"];
if (data["status_painel"] == true) {
mostrar_voto = data["mostrar_voto"];
presentes.append('<table id="parlamentares_list">');
$.each(presentes_list, function (index, parlamentar) {
if (parlamentar.voto == 'Voto Informado'){
if (parlamentar.voto == 'Voto Informado' && mostrar_voto == false){
$('#parlamentares_list').append('<tr><td style="padding-right:20px; color:yellow" >' +
parlamentar.nome +
'</td> <td style="padding-right:20px; color:yellow">' +

6
sapl/templates/parlamentares/layouts.yaml

@ -39,7 +39,7 @@ Parlamentar:
- situacao_militar profissao
- endereco_web
- email
- numero_gab_parlamentar telefone
- numero_gab_parlamentar telefone telefone_celular
- endereco_residencia cep_residencia
- municipio_residencia uf_residencia
- telefone_residencia
@ -56,7 +56,7 @@ ParlamentarUpdate:
- situacao_militar profissao
- endereco_web
- email
- numero_gab_parlamentar telefone
- numero_gab_parlamentar telefone telefone_celular
- endereco_residencia cep_residencia
- municipio_residencia uf_residencia
- telefone_residencia
@ -73,7 +73,7 @@ ParlamentarCreate:
- situacao_militar profissao
- endereco_web
- email
- numero_gab_parlamentar telefone
- numero_gab_parlamentar telefone telefone_celular
- endereco_residencia cep_residencia
- municipio_residencia
- telefone_residencia

6
sapl/templates/parlamentares/parlamentar_perfil_publico.html

@ -35,12 +35,6 @@
</div>
</div>
<div class="col-sm-8">
<div id="div_data_nascimento" class="form-group">
<p><b>Data de Nascimento: </b> &nbsp {{object.data_nascimento|default_if_none:"Não informado"}}</p>
</div>
</div>
<div class="col-sm-8">
<div id="div_data_nascimento" class="form-group">
<p><b>Telefone: </b> &nbsp {{object.telefone|default_if_none:"Não informado"}}</p>

9
sapl/templates/protocoloadm/documentoacessorioadministrativo_list.html

@ -61,3 +61,12 @@
</div>
{% endif %}
{% endblock container_table_list %}
{% block base_content %}
{{ block.super }}
<div style="display:flex;padding-left: 600px;padding-top: 10px;">
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.protocoloadm:merge_docacessorios' root_pk %}" class="btn btn-outline-primary">{% trans 'Baixar documentos como PDF único' %}</a>
</div>
</div>
{% endblock base_content%}

2
sapl/templates/base/RelatorioAtas_filter.html → sapl/templates/relatorios/RelatorioAtas_filter.html

@ -10,7 +10,7 @@
{% if filter_url %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.base:atas' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
<a href="{% url 'sapl.relatorios:atas' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br/><br/><br/>
<b>PERÍODO: {{ periodo }}<br /></b><br /><br/>

2
sapl/templates/base/RelatorioAudiencia_filter.html → sapl/templates/relatorios/RelatorioAudiencia_filter.html

@ -9,7 +9,7 @@
{% if show_results %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.base:audiencia' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
<a href="{% url 'sapl.relatorios:audiencia' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
{% if object_list|length > 0 %}

67
sapl/templates/relatorios/RelatorioDataFimPrazoTramitacao_filter.html

@ -0,0 +1,67 @@
{% extends "crud/list.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block base_content %}
{% if not show_results %}
<br>
{% crispy filter.form %}
{% else %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.relatorios:data_fim_prazo_tramitacoes' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b>
&emsp;Ano: {{ ano }} <br />
&emsp;Período: {{ data_fim_prazo }} <br />
&emsp;Tipo de matéria: {{ tipo }}<br />
&emsp;Status de tramitação: {{ tramitacao__status }}<br />
&emsp;Local de origem: {{ tramitacao__unidade_tramitacao_local }}<br />
&emsp;Local de destino: {{ tramitacao__unidade_tramitacao_destino }}<br />
&emsp;Autor: {{ materia__autor }}<br /><br /><br />
{% if object_list %}
{% if object_list|length == 1 %}
<tr><td><h3 style="text-align: left;">Foi encontrada 1 matéria com esses parâmetros.</h3></td></tr><br><br>
{% else %}
<tr><td><h3 style="text-align: left;">Foram encontradas {{object_list|length}} matérias com esses parâmetros.</h3></td></tr><br><br>
{% endif %}
<table class="table table-bordered table-hover">
<thead class="thead-default" >
<tr class="active">
<th>Matéria</th>
<th>Ementa</th>
</tr>
</thead>
<tbody>
{% for materia_em_tramitacao in object_list %}
<tr>
<td width="35%"><a href="{% url 'sapl.materia:tramitacao_list' materia_em_tramitacao.materia.pk %}">
{{materia_em_tramitacao.materia.tipo.descricao}} - {{materia_em_tramitacao.materia.tipo.sigla}} {{materia_em_tramitacao.materia.numero}}/{{materia_em_tramitacao.materia.ano}}
</a><br>
<small>
<strong>Data de Fim de Prazo:</strong> {{materia_em_tramitacao.tramitacao.data_fim_prazo}}
</small>
</td>
<td>
{{materia_em_tramitacao.materia.ementa}}
{% if not tramitacao__status or not tramitacao__unidade_tramitacao_destino %}
<small>
<br/>
<strong>Local Atual: </strong>{{ materia_em_tramitacao.materia.tramitacao_set.first.unidade_tramitacao_destino }}
<br/>
<strong>Status: </strong>{{ materia_em_tramitacao.materia.tramitacao_set.first.status }}
<br/>
<strong>Texto da Ação: </strong>{{ materia_em_tramitacao.materia.tramitacao_set.first.texto }}
</small>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<tr><td><h3 style="text-align: left;">Nenhuma matéria encontrada com esses parâmetros.</h3></td></tr><br><br>
{% endif %}
{% endif %}
{% endblock base_content %}

2
sapl/templates/base/RelatorioDocumentosAcessorios_filter.html → sapl/templates/relatorios/RelatorioDocumentosAcessorios_filter.html

@ -7,7 +7,7 @@
{% crispy filter.form %}
{% else %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.base:relatorio_documentos_acessorios' %}" class="btn btn-outline-primary">{% trans 'Fazer uma nova pesquisa' %}</a>
<a href="{% url 'sapl.relatorios:relatorio_documentos_acessorios' %}" class="btn btn-outline-primary">{% trans 'Fazer uma nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA<br /></b>

2
sapl/templates/base/RelatorioHistoricoTramitacaoAdm_filter.html → sapl/templates/relatorios/RelatorioHistoricoTramitacaoAdm_filter.html

@ -8,7 +8,7 @@
{% crispy filter.form %}
{% else %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.base:historico_tramitacoes_adm' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
<a href="{% url 'sapl.relatorios:historico_tramitacoes_adm' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b>

2
sapl/templates/base/RelatorioHistoricoTramitacao_filter.html → sapl/templates/relatorios/RelatorioHistoricoTramitacao_filter.html

@ -8,7 +8,7 @@
{% crispy filter.form %}
{% else %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.base:historico_tramitacoes' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
<a href="{% url 'sapl.relatorios:historico_tramitacoes' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b>

0
sapl/templates/base/RelatorioMateriasAnoAssunto.html → sapl/templates/relatorios/RelatorioMateriasAnoAssunto.html

2
sapl/templates/base/RelatorioMateriasPorAnoAutorTipo_filter.html → sapl/templates/relatorios/RelatorioMateriasPorAnoAutorTipo_filter.html

@ -9,7 +9,7 @@
{% if show_results %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.base:materia_por_ano_autor_tipo' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
<a href="{% url 'sapl.relatorios:materia_por_ano_autor_tipo' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b>

2
sapl/templates/base/RelatorioMateriasPorAutor_filter.html → sapl/templates/relatorios/RelatorioMateriasPorAutor_filter.html

@ -9,7 +9,7 @@
{% if show_results %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.base:materia_por_autor' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
<a href="{% url 'sapl.relatorios:materia_por_autor' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b>

6
sapl/templates/base/RelatorioMateriasPorTramitacao_filter.html → sapl/templates/relatorios/RelatorioMateriasPorTramitacao_filter.html

@ -9,7 +9,7 @@
{% if filter_url %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.base:materia_por_tramitacao' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
<a href="{% url 'sapl.relatorios:materia_por_tramitacao' %}" class="btn btn-outline-primary">{% trans 'Fazer nova pesquisa' %}</a>
</div>
<br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b>
@ -72,6 +72,8 @@
{% endfor %}
</table>
{% endif %}
{% include 'paginacao.html' %}
{% if page.object_list %}
{% include "paginacao.html" %}
{% endif %}
<br/>
{% endblock base_content %}

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

Loading…
Cancel
Save