Browse Source

Merge branch '3.1.x' into doc_restrito

pull/3613/head
cristian-longhi 1 month ago
committed by GitHub
parent
commit
090d18e26c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 90
      CHANGES.md
  2. 2
      docker/Dockerfile
  3. 4
      docker/docker-compose.yaml
  4. 17
      docker/start.sh
  5. 45
      docs/feature-flags.md
  6. 24
      frontend/src/__apps/compilacao/js/old/compilacao_edit.js
  7. 6
      frontend/src/__global/js/tinymce/index.js
  8. 754
      frontend/webpack-stats.json
  9. 14525
      package-lock.json
  10. 4
      package.json
  11. 14
      release.sh
  12. 4
      requirements/dev-requirements.txt
  13. 9
      requirements/requirements.txt
  14. 17
      sapl/api/views_comissoes.py
  15. 18
      sapl/audiencia/migrations/0019_auto_20240711_1400.py
  16. 33
      sapl/base/forms.py
  17. 17
      sapl/base/migrations/0059_remove_appconfig_sapl_as_sapn.py
  18. 18
      sapl/base/migrations/0060_auto_20240812_1628.py
  19. 6
      sapl/base/models.py
  20. 12
      sapl/base/receivers.py
  21. 8
      sapl/base/templatetags/common_tags.py
  22. 10
      sapl/base/templatetags/menus.py
  23. 25
      sapl/base/views.py
  24. 19
      sapl/compilacao/models.py
  25. 2
      sapl/compilacao/urls.py
  26. 27
      sapl/context_processors.py
  27. 10
      sapl/crispy_layout_mixin.py
  28. 78
      sapl/crud/base.py
  29. 9
      sapl/materia/forms.py
  30. 19
      sapl/materia/migrations/0085_auto_20231028_1838.py
  31. 33
      sapl/materia/migrations/0086_auto_20240711_1400.py
  32. 26
      sapl/materia/migrations/0087_update_viewdb_materiaemtramitacao.py
  33. 27
      sapl/materia/models.py
  34. 39
      sapl/materia/views.py
  35. 21
      sapl/middleware.py
  36. 22
      sapl/norma/forms.py
  37. 46
      sapl/norma/migrations/0045_auto_20240711_1405.py
  38. 31
      sapl/norma/models.py
  39. 22
      sapl/norma/views.py
  40. 5
      sapl/parlamentares/forms.py
  41. 27
      sapl/parlamentares/migrations/0008_adiciona_cargos_mesa.py
  42. 41
      sapl/parlamentares/migrations/0044_adiciona_cargos_mesa.py
  43. 26
      sapl/parlamentares/urls.py
  44. 12
      sapl/parlamentares/views.py
  45. 23
      sapl/protocoloadm/migrations/0045_auto_20240711_1405.py
  46. 14
      sapl/protocoloadm/views.py
  47. 2
      sapl/rules/group_geral.py
  48. 121
      sapl/sessao/views.py
  49. 34
      sapl/settings.py
  50. 13
      sapl/static/sapl/frontend/css/chunk-vendors.045ec640.css
  51. BIN
      sapl/static/sapl/frontend/css/chunk-vendors.045ec640.css.gz
  52. 13
      sapl/static/sapl/frontend/css/chunk-vendors.299c587b.css
  53. BIN
      sapl/static/sapl/frontend/css/chunk-vendors.299c587b.css.gz
  54. BIN
      sapl/static/sapl/frontend/css/compilacao.0baf3580.css.gz
  55. BIN
      sapl/static/sapl/frontend/css/global.45591136.css.gz
  56. BIN
      sapl/static/sapl/frontend/css/painel.e2b9504e.css.gz
  57. BIN
      sapl/static/sapl/frontend/fonts/fa-brands-400.5d18d427.ttf
  58. BIN
      sapl/static/sapl/frontend/fonts/fa-brands-400.5d18d427.ttf.gz
  59. BIN
      sapl/static/sapl/frontend/fonts/fa-brands-400.87587a68.woff2
  60. BIN
      sapl/static/sapl/frontend/fonts/fa-brands-400.9a905705.ttf
  61. BIN
      sapl/static/sapl/frontend/fonts/fa-brands-400.9a905705.ttf.gz
  62. BIN
      sapl/static/sapl/frontend/fonts/fa-brands-400.b6033b54.woff2
  63. BIN
      sapl/static/sapl/frontend/fonts/fa-regular-400.3580b4a9.woff2
  64. BIN
      sapl/static/sapl/frontend/fonts/fa-regular-400.3ccdbd3d.woff2
  65. BIN
      sapl/static/sapl/frontend/fonts/fa-regular-400.67a0fb74.ttf
  66. BIN
      sapl/static/sapl/frontend/fonts/fa-regular-400.67a0fb74.ttf.gz
  67. BIN
      sapl/static/sapl/frontend/fonts/fa-regular-400.81482cd4.ttf
  68. BIN
      sapl/static/sapl/frontend/fonts/fa-regular-400.81482cd4.ttf.gz
  69. BIN
      sapl/static/sapl/frontend/fonts/fa-solid-900.0b0cc8a6.woff2
  70. BIN
      sapl/static/sapl/frontend/fonts/fa-solid-900.69d3141a.ttf
  71. BIN
      sapl/static/sapl/frontend/fonts/fa-solid-900.69d3141a.ttf.gz
  72. BIN
      sapl/static/sapl/frontend/fonts/fa-solid-900.6a8db53d.ttf
  73. BIN
      sapl/static/sapl/frontend/fonts/fa-solid-900.6a8db53d.ttf.gz
  74. BIN
      sapl/static/sapl/frontend/fonts/fa-solid-900.fd0b155c.woff2
  75. BIN
      sapl/static/sapl/frontend/fonts/fa-v4compatibility.2c070fd2.ttf
  76. BIN
      sapl/static/sapl/frontend/fonts/fa-v4compatibility.2c070fd2.ttf.gz
  77. BIN
      sapl/static/sapl/frontend/fonts/fa-v4compatibility.e4efb16c.ttf
  78. BIN
      sapl/static/sapl/frontend/fonts/fa-v4compatibility.e4efb16c.ttf.gz
  79. BIN
      sapl/static/sapl/frontend/img/down_arrow_select.jpg.gz
  80. 19
      sapl/static/sapl/frontend/js/chunk-vendors.5e41c7a6.js
  81. BIN
      sapl/static/sapl/frontend/js/chunk-vendors.5e41c7a6.js.LICENSE.txt.gz
  82. BIN
      sapl/static/sapl/frontend/js/chunk-vendors.5e41c7a6.js.gz
  83. 19
      sapl/static/sapl/frontend/js/chunk-vendors.e8ab4373.js
  84. 244
      sapl/static/sapl/frontend/js/chunk-vendors.e8ab4373.js.LICENSE.txt
  85. BIN
      sapl/static/sapl/frontend/js/chunk-vendors.e8ab4373.js.LICENSE.txt.gz
  86. BIN
      sapl/static/sapl/frontend/js/chunk-vendors.e8ab4373.js.gz
  87. 1
      sapl/static/sapl/frontend/js/compilacao.57574b17.js
  88. BIN
      sapl/static/sapl/frontend/js/compilacao.57574b17.js.gz
  89. 1
      sapl/static/sapl/frontend/js/compilacao.d3adb1c1.js
  90. BIN
      sapl/static/sapl/frontend/js/compilacao.d3adb1c1.js.gz
  91. 2
      sapl/static/sapl/frontend/js/global.2f5aff92.js
  92. BIN
      sapl/static/sapl/frontend/js/global.2f5aff92.js.gz
  93. 2
      sapl/static/sapl/frontend/js/global.8ef6e932.js
  94. 0
      sapl/static/sapl/frontend/js/global.8ef6e932.js.LICENSE.txt
  95. BIN
      sapl/static/sapl/frontend/js/global.8ef6e932.js.gz
  96. 2
      sapl/static/sapl/frontend/js/painel.73a4fb17.js
  97. BIN
      sapl/static/sapl/frontend/js/painel.73a4fb17.js.gz
  98. BIN
      sapl/static/sapl/frontend/js/painel.aa1df64a.js.gz
  99. 1
      sapl/static/sapl/frontend/js/parlamentar.f0710dca.js
  100. BIN
      sapl/static/sapl/frontend/js/parlamentar.f0710dca.js.gz

90
CHANGES.md

@ -1,4 +1,94 @@
3.1.164-RC0 / 2025-07-09
========================
* Fix: nome casa duplicado
* feat: #3771 (#3772)
* Adiciona metadados para Google crawler
* Remove version e monta mais um volume
* Fix: log de aplicação
* feat: #3769 (#3770)
* fix: #3767 (#3768)
* Release: 3.1.163
3.1.163 / 2025-05-21
====================
* Fix fancycompleter incompatibility when calling shell_plus
3.1.163-RC24 / 2025-05-19
=========================
* Implementa relatórios CSV, XLSX e JSON para Pauta de Sessão (#3744)
* feat: adiciona pydevd-pycharm para facilitar debug usando pycharm ide (#3717)
* Ajusta link do texto integral da Norma Jurídica nos formatos CSV, XLSX e JSON. (#3743)
* Força mudança de senha para senhas fracas
* Fix Chamado #643964 - Multiple definitions in dictionary at byte 0xc854c for key /Info
* Ajusta link do texto original da Matéria nos formatos CSV, XLSX e JSON. (#3742)
* fix: aplica um lazy loading no sortable do jquery ui pelo evento mouseenter. (#3737)
* fix: corrige condição para mostrar btn para TAs.
* fix: add migrate resultante do commit anterior
* fix: torna cep opcional no model CasaLegislativa
3.1.163-RC23 / 2024-07-31
=========================
* Otimiza recuperação de normas relacionadas (#3734)
* hot-fix: resolve imcompatibilidade de regex com versão de produção do postgresql 9.6
3.1.163-RC21 / 2024-06-25
=========================
* fix: descomenta código pertencente ao commit anterior
* fix: elimina dupla extração de assinaturas eletrônicas
* fix: remove listagem pública de OperadorAutor
* fix: altera campo de 'resultado' ao registrar leitura em bloco
* bump version gunicorn in setup.py
* rebuild frontend
* fix dependabot alerts #61 - bump version axios
* fix: ajuste no conjunto inicial de permissões na construção de classe crud
* Impl Mixin para gerar arquivos de pesq em diversos formatos (#3710)
* Fix estatistica norma view (#3707)
* bump version gunicorn, pillow
* Build(deps): Bump pillow from 10.0.1 to 10.3.0 in /requirements (#3709)
* fix: corrige display de data e hora de protocolor manual
* fix: corrige carga de permissões públicas do crud
* Add blank space when SAPN
* fix: corrige bug na pesquisa de impressos etiquetas (#3695)
* feat: adiciona o turno na info de materias na pauta de sessao (#3694)
* fix: invert lógica do sapln_switch
* fix: Update docker-compose.yaml
* Update CHANGES.md
3.1.163-RC20 / 2023-12-04
=========================
* HOT-FIX: Bump easy-thumbnails
3.1.163-RC19 / 2023-11-29
=========================
* Bump Pillow
3.1.163-RC18 / 2023-11-29
=========================
* Bump Pillow
* Adiciona feature flag lib
* Conserta relatoria de matérias
* Bump Pillow version
* feat: adiciona script para ajuste to tamanho de fontes de seçoes do painel eletronico
* Conserta bug na paginação do Relatório de Matérias por Tramitação
* fix: ajusta ordenacao de votos nominais por ordem alfabetica no extrato da sessao
* fix: corrige erro de loaddata cargomesa
* feat: adiciona check de presenca e sessao aberta na leitura em bloco da ordem do dia
* fix: padroniza nome_parlamentar para lista de presenca e votacoes nominais no resumo da ata
* Apaga Numeração se TipoMateriaLegislativa é apagado
* Adiciona opção de remover formatação
* Adiciona ordenacao em cargo mesa
* Remove tags de considerações finais e ocorrências de sessão
* fix: padroniza nome_parlamentar para lista de presenca e votacoes nominais no resumo da ata
3.1.163-RC17 / 2023-09-30 3.1.163-RC17 / 2023-09-30
========================= =========================

2
docker/Dockerfile

@ -68,6 +68,6 @@ ENV DEBIAN_FRONTEND teletype
EXPOSE 80/tcp 443/tcp EXPOSE 80/tcp 443/tcp
VOLUME ["/var/interlegis/sapl/data", "/var/interlegis/sapl/media"] VOLUME ["/var/interlegis/sapl/data", "/var/interlegis/sapl/media", "/var/log/sapl/"]
CMD ["/var/interlegis/sapl/start.sh"] CMD ["/var/interlegis/sapl/start.sh"]

4
docker/docker-compose.yaml

@ -1,4 +1,3 @@
version: "3.7"
services: services:
sapldb: sapldb:
image: postgres:10.5-alpine image: postgres:10.5-alpine
@ -32,7 +31,7 @@ services:
networks: networks:
- sapl-net - sapl-net
sapl: sapl:
image: interlegis/sapl:3.1.163-RC17 image: interlegis/sapl:3.1.164-RC0
# build: # build:
# context: ../ # context: ../
# dockerfile: ./docker/Dockerfile # dockerfile: ./docker/Dockerfile
@ -54,6 +53,7 @@ services:
SOLR_COLLECTION: sapl SOLR_COLLECTION: sapl
SOLR_URL: http://solr:solr@saplsolr:8983 SOLR_URL: http://solr:solr@saplsolr:8983
IS_ZK_EMBEDDED: 'True' IS_ZK_EMBEDDED: 'True'
ENABLE_SAPN: 'False'
TZ: America/Sao_Paulo TZ: America/Sao_Paulo
volumes: volumes:
- sapl_data:/var/interlegis/sapl/data - sapl_data:/var/interlegis/sapl/data

17
docker/start.sh

@ -39,6 +39,7 @@ create_env() {
echo "SOLR_COLLECTION = ""${SOLR_COLLECTION-sapl}" >> $FILENAME echo "SOLR_COLLECTION = ""${SOLR_COLLECTION-sapl}" >> $FILENAME
echo "SOLR_URL = ""${SOLR_URL-http://localhost:8983}" >> $FILENAME echo "SOLR_URL = ""${SOLR_URL-http://localhost:8983}" >> $FILENAME
echo "IS_ZK_EMBEDDED = ""${IS_ZK_EMBEDDED-False}" >> $FILENAME echo "IS_ZK_EMBEDDED = ""${IS_ZK_EMBEDDED-False}" >> $FILENAME
echo "ENABLE_SAPN = ""${ENABLE_SAPN-False}" >> $FILENAME
echo "[ENV FILE] done." echo "[ENV FILE] done."
} }
@ -85,14 +86,30 @@ if [ "${USE_SOLR-False}" == "True" ] || [ "${USE_SOLR-False}" == "true" ]; then
fi fi
python3 solr_cli.py -u $SOLR_URL -c $SOLR_COLLECTION -s $NUM_SHARDS -rf $RF -ms $MAX_SHARDS_PER_NODE $ZK_EMBEDDED & python3 solr_cli.py -u $SOLR_URL -c $SOLR_COLLECTION -s $NUM_SHARDS -rf $RF -ms $MAX_SHARDS_PER_NODE $ZK_EMBEDDED &
# Enable SOLR switch on, creating if it doesn't exist on database
./manage.py waffle_switch SOLR_SWITCH on --create
else else
echo "Solr is offline, not possible to connect." echo "Solr is offline, not possible to connect."
# Disable Solr switch off, creating if it doesn't exist on database
./manage.py waffle_switch SOLR_SWITCH off --create
fi fi
else else
echo "Solr support is not initialized." echo "Solr support is not initialized."
# Disable Solr switch off, creating if it doesn't exist on database
./manage.py waffle_switch SOLR_SWITCH off --create
fi fi
## Enable/Disable SAPN
if [ "${ENABLE_SAPN-False}" == "True" ] || [ "${ENABLE_SAPN-False}" == "true" ]; then
echo "Enabling SAPN"
./manage.py waffle_switch SAPLN_SWITCH on --create
else
echo "Enabling SAPL"
./manage.py waffle_switch SAPLN_SWITCH off --create
fi
echo "Creating admin user..." echo "Creating admin user..."
user_created=$(python3 create_admin.py 2>&1) user_created=$(python3 create_admin.py 2>&1)

45
docs/feature-flags.md

@ -0,0 +1,45 @@
### Types
Waffle supports three types of feature flippers:
### Flags
_"Flags can be used to enable a feature for specific users, groups, users meeting
certain criteria (such as being authenticated, or superusers) or a certain percentage
of visitors."_
https://waffle.readthedocs.io/en/stable/types/flag.html
### Switches
_"Switches are simple booleans: they are on or off, for everyone, all the time.
They do not require a request object and can be used in other contexts, such
as management commands and tasks."_
Enabling/Disabling via CLI (example):
./manage.py waffle_switch SOLR_SWITCH on --create
./manage.py waffle_switch SOLR_SWITCH off --create
https://waffle.readthedocs.io/en/stable/types/switch.html
### Samples
_"Samples are on a given percentage of the time. They do not require a request
object and can be used in other contexts, such as management commands and tasks."_
Liga e desliga baseado em amostragem aleatória.
https://waffle.readthedocs.io/en/stable/types/sample.html
### Reference
* Documentation: https://waffle.readthedocs.io/
* Managing from CLI: https://waffle.readthedocs.io/en/stable/usage/cli.html
* Templates: https://waffle.readthedocs.io/en/stable/usage/templates.html
* Views: https://waffle.readthedocs.io/en/stable/usage/views.html
* Decorators: https://waffle.readthedocs.io/en/stable/usage/decorators.html

24
frontend/src/__apps/compilacao/js/old/compilacao_edit.js

@ -1,4 +1,3 @@
const _$ = window.$ const _$ = window.$
window.DispositivoEdit = function () { window.DispositivoEdit = function () {
@ -500,7 +499,11 @@ window.DispositivoEdit = function () {
} }
instance.reloadFunctionsDraggables = function () { instance.reloadFunctionsDraggables = function () {
_$('.dpt-alts').sortable({ const dptAlts = _$('.dpt-alts')
if (dptAlts.length > 0) {
dptAlts.sortable({
connectWith: '.dpt-alts',
items: '.sorting-initialize',
revert: true, revert: true,
distance: 15, distance: 15,
start: function (event, ui) { start: function (event, ui) {
@ -511,11 +514,14 @@ window.DispositivoEdit = function () {
const url = pk + '/refresh?action=json_drag_move_dpt_alterado&index=' + ui.item.index() + '&bloco_pk=' + bloco_pk const url = pk + '/refresh?action=json_drag_move_dpt_alterado&index=' + ui.item.index() + '&bloco_pk=' + bloco_pk
_$.get(url).done(function (data) { _$.get(url).done(function (data) {
// console.log(pk + ' - ' + bloco_pk) // handle data if needed
// reloadFunctionsForObjectsOfCompilacao();
}) })
} }
}) })
dptAlts.find('.dpt').one('mouseenter', function () {
$(this).addClass('sorting-initialize')
dptAlts.sortable('refresh')
})
_$('.dpt-alts .dpt').draggable({ _$('.dpt-alts .dpt').draggable({
connectToSortable: '.dpt-alts', connectToSortable: '.dpt-alts',
@ -523,7 +529,6 @@ window.DispositivoEdit = function () {
zIndex: 1, zIndex: 1,
distance: 15, distance: 15,
drag: function (event, ui) { drag: function (event, ui) {
// _$('.dpt-comp-selected').removeClass('dpt-comp-selected');
_$('.dpt-alts').addClass('drag') _$('.dpt-alts').addClass('drag')
}, },
stop: function (event, ui) { stop: function (event, ui) {
@ -531,7 +536,10 @@ window.DispositivoEdit = function () {
} }
}) })
_$('.dpt-alts').disableSelection() dptAlts.disableSelection()
} else {
console.warn("No '.dpt-alts' elements found to make sortable/draggable.")
}
} }
instance.scrollTo = function (dpt) { instance.scrollTo = function (dpt) {
try { try {
@ -570,7 +578,9 @@ window.DispositivoEdit = function () {
instance.triggerBtnDptEdit(href[1]) instance.triggerBtnDptEdit(href[1])
} }
_$('main').click(function (event) { _$('main').click(function (event) {
if (event.target === this || event.target === this.firstElementChild) { instance.clearEditSelected() } if (event.target === this || event.target === this.firstElementChild) {
instance.clearEditSelected()
}
}) })
instance.waitHide() instance.waitHide()
} }

6
frontend/src/__global/js/tinymce/index.js

@ -20,11 +20,11 @@ window.initTextRichEditor = function (elements, readonly = false, paste_as_text
language: 'pt_BR', language: 'pt_BR',
branding: false, branding: false,
forced_root_block: 'p', forced_root_block: 'p',
toolbar: "...| removeformat | ...",
paste_as_text, paste_as_text,
plugins: 'table lists advlist link code', plugins: 'table lists advlist link code',
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link | code', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link | code | removeformat ',
menubar: 'file edit view insert format table' menubar: 'file edit view insert format table',
license_key: 'gpl'
} }
if (readonly) { if (readonly) {
configTinymce.readonly = 1 configTinymce.readonly = 1

754
frontend/webpack-stats.json

File diff suppressed because it is too large

14525
package-lock.json

File diff suppressed because it is too large

4
package.json

@ -9,7 +9,7 @@
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.1.2", "@fortawesome/fontawesome-free": "^6.1.2",
"axios": "^0.27.2", "axios": "^1.7.2",
"bootstrap": "^4.6.2", "bootstrap": "^4.6.2",
"bootstrap-vue": "^2.22.0", "bootstrap-vue": "^2.22.0",
"diff": "^5.1.0", "diff": "^5.1.0",
@ -21,7 +21,7 @@
"moment": "^2.29.4", "moment": "^2.29.4",
"moment-locales-webpack-plugin": "^1.2.0", "moment-locales-webpack-plugin": "^1.2.0",
"popper.js": "^1.16.1", "popper.js": "^1.16.1",
"tinymce": "^6.1.2", "tinymce": "^7.2.0",
"vue": "^2.7.9" "vue": "^2.7.9"
}, },
"devDependencies": { "devDependencies": {

14
release.sh

@ -5,7 +5,6 @@
## ##
## IMPORTANT: requires gh and git-extras commands installed ## 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) # TODO: verificar porque só pega versões superiores (3.1.200 ao invés de 3.1.200-RC9)
@ -44,14 +43,19 @@ function change_files {
echo "Updating from "$OLD_VERSION" to "$FINAL_VERSION"" echo "Updating from "$OLD_VERSION" to "$FINAL_VERSION""
if [[ "$OSTYPE" == "darwin"* ]]; then
# MacOS (BSD sed)
sed -E -i "" "s|$OLD_VERSION|$FINAL_VERSION|g" docker/docker-compose.yaml sed -E -i "" "s|$OLD_VERSION|$FINAL_VERSION|g" docker/docker-compose.yaml
sed -E -i "" "s|$OLD_VERSION|$FINAL_VERSION|g" setup.py sed -E -i "" "s|$OLD_VERSION|$FINAL_VERSION|g" setup.py
sed -E -i "" "s|$OLD_VERSION|$FINAL_VERSION|g" sapl/templates/base.html 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 sed -E -i "" "s|$OLD_VERSION|$FINAL_VERSION|g" sapl/settings.py
else
# Linux (GNU sed)
sed -i -E "s|$OLD_VERSION|$FINAL_VERSION|g" docker/docker-compose.yaml
sed -i -E "s|$OLD_VERSION|$FINAL_VERSION|g" setup.py
sed -i -E "s|$OLD_VERSION|$FINAL_VERSION|g" sapl/templates/base.html
sed -i -E "s|$OLD_VERSION|$FINAL_VERSION|g" sapl/settings.py
fi
} }
function set_major_version { function set_major_version {

4
requirements/dev-requirements.txt

@ -4,6 +4,8 @@ autopep8==1.2.4
beautifulsoup4==4.9.1 beautifulsoup4==4.9.1
django-debug-toolbar==1.11.1 django-debug-toolbar==1.11.1
ipdb==0.13.3 ipdb==0.13.3
pdbpp==0.9.2 fancycompleter==0.9.1
pdbpp==0.10.3
pip-review==0.4 pip-review==0.4
pipdeptree==0.10.1 pipdeptree==0.10.1
pydevd-pycharm~=203.7148.7

9
requirements/requirements.txt

@ -6,15 +6,15 @@ dj-database-url==0.5.0
django-braces==1.14.0 django-braces==1.14.0
django-crispy-forms==1.7.2 django-crispy-forms==1.7.2
django-contrib-postgres==0.0.1 django-contrib-postgres==0.0.1
django-floppyforms==1.8.0
django-extra-views==0.12.0 django-extra-views==0.12.0
django-model-utils==3.1.2 django-model-utils==3.1.2
django-extensions==2.1.4 django-extensions==2.1.4
django-image-cropping==1.2 django-image-cropping==1.2
django-waffle==3.0.0
django-webpack-loader==1.6.0 django-webpack-loader==1.6.0
drf-spectacular==0.18.2 drf-spectacular==0.18.2
django-ratelimit==3.0.1 django-ratelimit==3.0.1
easy-thumbnails==2.5 easy-thumbnails==2.8.5
python-decouple==3.1 python-decouple==3.1
psycopg2-binary==2.8.6 psycopg2-binary==2.8.6
pyyaml==6.0.1 pyyaml==6.0.1
@ -22,8 +22,8 @@ pytz==2019.3
python-magic==0.4.15 python-magic==0.4.15
unipath==1.1 unipath==1.1
WeasyPrint==51 WeasyPrint==51
Pillow==9.3.0 Pillow==10.3.0
gunicorn==19.9.0 gunicorn==22.0.0
more-itertools==8.2.0 more-itertools==8.2.0
pysolr==3.6.0 pysolr==3.6.0
PyPDF4==1.27.0 PyPDF4==1.27.0
@ -35,6 +35,7 @@ kazoo==2.8.0
django-prometheus==2.2.0 django-prometheus==2.2.0
asn1crypto==1.5.1 asn1crypto==1.5.1
XlsxWriter==3.2.0
git+https://github.com/interlegis/trml2pdf git+https://github.com/interlegis/trml2pdf
git+https://github.com/interlegis/django-admin-bootstrapped git+https://github.com/interlegis/django-admin-bootstrapped

17
sapl/api/views_comissoes.py

@ -3,6 +3,10 @@ from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \ from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action customize, wrapper_queryset_response_for_drf_action
from sapl.comissoes.models import Comissao
from rest_framework.decorators import action
from sapl.materia.models import MateriaEmTramitacao
ApiViewSetConstrutor.build_class( ApiViewSetConstrutor.build_class(
@ -10,3 +14,16 @@ ApiViewSetConstrutor.build_class(
apps.get_app_config('comissoes') apps.get_app_config('comissoes')
] ]
) )
@customize(Comissao)
class _ComissaoViewSet:
@action(detail=True)
def materiaemtramitacao(self, request, *args, **kwargs):
return self.get_materiaemtramitacao(**kwargs)
@wrapper_queryset_response_for_drf_action(model=MateriaEmTramitacao)
def get_materiaemtramitacao(self, **kwargs):
return self.get_queryset().filter(
unidade_tramitacao_atual__comissao=kwargs['pk'],
)

18
sapl/audiencia/migrations/0019_auto_20240711_1400.py

@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2024-07-11 17:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audiencia', '0018_auto_20230529_1641'),
]
operations = [
migrations.AlterField(
model_name='audienciapublica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2025, 2025), (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'),
),
]

33
sapl/base/forms.py

@ -40,7 +40,7 @@ from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField,
FilterOverridesMetaMixin, FileFieldCheckMixin, FilterOverridesMetaMixin, FileFieldCheckMixin,
ImageThumbnailFileInput, qs_override_django_filter, ImageThumbnailFileInput, qs_override_django_filter,
RANGE_ANOS, YES_NO_CHOICES, choice_tipos_normas, RANGE_ANOS, YES_NO_CHOICES, choice_tipos_normas,
GoogleRecapthaMixin, parlamentares_ativos, RANGE_MESES) GoogleRecapthaMixin, parlamentares_ativos, RANGE_MESES, is_weak_password, delete_cached_entry)
from .models import AppConfig, CasaLegislativa from .models import AppConfig, CasaLegislativa
@ -288,8 +288,13 @@ class UserAdminForm(ModelForm):
) )
else: else:
if new_password1 and new_password2: if new_password1 and new_password2:
if is_weak_password(new_password1):
raise forms.ValidationError(_(
'A senha deve ter pelo menos 8 caracteres e incluir uma combinação '
'de letras maiúsculas e minúsculas, números e caracteres especiais.'
))
password_validation.validate_password( password_validation.validate_password(
new_password2, self.instance) new_password1, self.instance)
parlamentar = data.get('parlamentar', None) parlamentar = data.get('parlamentar', None)
if parlamentar and parlamentar.votante_set.exists() and \ if parlamentar and parlamentar.votante_set.exists() and \
@ -916,22 +921,26 @@ class CasaLegislativaForm(FileFieldCheckMixin, ModelForm):
# chama __clean de FileFieldCheckMixin # chama __clean de FileFieldCheckMixin
# por estar em clean de campo # por estar em clean de campo
super(CasaLegislativaForm, self)._check() super(CasaLegislativaForm, self)._check()
logotipo = self.cleaned_data.get('logotipo') logotipo = self.cleaned_data.get('logotipo')
if logotipo: if logotipo and logotipo.size > MAX_IMAGE_UPLOAD_SIZE:
if logotipo.size > MAX_IMAGE_UPLOAD_SIZE:
raise ValidationError("Imagem muito grande. ( > 2MB )") raise ValidationError("Imagem muito grande. ( > 2MB )")
return logotipo return logotipo
def save(self, commit=True):
casa = super(CasaLegislativaForm, self).save(commit=commit)
delete_cached_entry("site-title")
return casa
class LoginForm(AuthenticationForm): class LoginForm(AuthenticationForm):
username = forms.CharField( username = forms.CharField(
label="Username", max_length=30, label="Usuário", max_length=30,
widget=forms.TextInput( widget=forms.TextInput(
attrs={ attrs={
'class': 'form-control', 'name': 'username'})) 'class': 'form-control', 'name': 'username'}))
password = forms.CharField( password = forms.CharField(
label="Password", max_length=30, label="Senha", max_length=30,
widget=forms.PasswordInput( widget=forms.PasswordInput(
attrs={ attrs={
'class': 'form-control', 'name': 'password'})) 'class': 'form-control', 'name': 'password'}))
@ -1009,7 +1018,6 @@ class ConfiguracoesAppForm(ModelForm):
'google_recaptcha_site_key', 'google_recaptcha_site_key',
'google_recaptcha_secret_key', 'google_recaptcha_secret_key',
'google_analytics_id_metrica', 'google_analytics_id_metrica',
'sapl_as_sapn',
'identificacao_de_documentos', 'identificacao_de_documentos',
] ]
@ -1140,12 +1148,15 @@ class AlterarSenhaForm(Form):
# TODO: caracteres alfanuméricos, maiúsculas (?), # TODO: caracteres alfanuméricos, maiúsculas (?),
# TODO: senha atual igual a senha anterior, etc # TODO: senha atual igual a senha anterior, etc
if len(new_password1) < 6: if is_weak_password(new_password1):
self.logger.warning( self.logger.warning(
'A senha informada não tem o mínimo de 6 caracteres.' 'A senha deve ter pelo menos 8 caracteres e incluir uma combinação '
'de letras maiúsculas e minúsculas, números e caracteres especiais.'
) )
raise ValidationError( raise ValidationError(
"A senha informada deve ter no mínimo 6 caracteres") 'A senha deve ter pelo menos 8 caracteres e incluir uma combinação '
'de letras maiúsculas e minúsculas, números e caracteres especiais.'
)
username = data['username'] username = data['username']
old_password = data['old_password'] old_password = data['old_password']

17
sapl/base/migrations/0059_remove_appconfig_sapl_as_sapn.py

@ -0,0 +1,17 @@
# Generated by Django 2.2.28 on 2023-11-27 00:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('base', '0058_appconfig_ordenacao_pesquisa_materia'),
]
operations = [
migrations.RemoveField(
model_name='appconfig',
name='sapl_as_sapn',
),
]

18
sapl/base/migrations/0060_auto_20240812_1628.py

@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2024-08-12 19:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0059_remove_appconfig_sapl_as_sapn'),
]
operations = [
migrations.AlterField(
model_name='casalegislativa',
name='cep',
field=models.CharField(blank=True, max_length=100, verbose_name='CEP'),
),
]

6
sapl/base/models.py

@ -56,7 +56,7 @@ class CasaLegislativa(models.Model):
nome = models.CharField(max_length=100, verbose_name=_('Nome')) nome = models.CharField(max_length=100, verbose_name=_('Nome'))
sigla = models.CharField(max_length=100, verbose_name=_('Sigla')) sigla = models.CharField(max_length=100, verbose_name=_('Sigla'))
endereco = models.CharField(max_length=100, verbose_name=_('Endereço')) endereco = models.CharField(max_length=100, verbose_name=_('Endereço'))
cep = models.CharField(max_length=100, verbose_name=_('CEP')) cep = models.CharField(max_length=100, blank=True, verbose_name=_('CEP'))
municipio = models.CharField(max_length=50, verbose_name=_('Município')) municipio = models.CharField(max_length=50, verbose_name=_('Município'))
uf = models.CharField(max_length=2, uf = models.CharField(max_length=2,
choices=LISTA_DE_UFS, choices=LISTA_DE_UFS,
@ -107,10 +107,6 @@ class AppConfig(models.Model):
default="", default="",
verbose_name=_('Esfera Federação'), verbose_name=_('Esfera Federação'),
choices=ESFERA_FEDERACAO_CHOICES) choices=ESFERA_FEDERACAO_CHOICES)
sapl_as_sapn = models.BooleanField(
verbose_name=_(
'Utilizar SAPL apenas como SAPL-Normas?'),
choices=YES_NO_CHOICES, default=False)
# MÓDULO PARLAMENTARES # MÓDULO PARLAMENTARES

12
sapl/base/receivers.py

@ -283,13 +283,18 @@ def signed_files_extraction_function(sender, instance, **kwargs):
return signs return signs
# tenta extrair via /Fields # tenta extrair via /Fields
fields_br = []
try: try:
pdf = PdfFileReader(file) pdf = PdfFileReader(file)
fields = pdf.getFields() fields = pdf.getFields()
fields_br = list(
map(lambda x: x.get('/V', {}).get('/ByteRange', []), fields.values()))
except Exception as e: except Exception as e:
try: try:
pdf = PdfFileReader(file, strict=False) pdf = PdfFileReader(file, strict=False)
fields = pdf.getFields() fields = pdf.getFields()
fields_br = list(
map(lambda x: x.get('/V', {}).get('/ByteRange', []), fields.values()))
except Exception as ee: except Exception as ee:
fields = ee fields = ee
@ -309,13 +314,17 @@ def signed_files_extraction_function(sender, instance, **kwargs):
n += 1 n += 1
br = [int(i, 10) for i in pdfdata[start + 1: stop].split()] br = [int(i, 10) for i in pdfdata[start + 1: stop].split()]
if br in fields_br:
continue
contents = pdfdata[br[0] + br[1] + 1: br[2] - 1] contents = pdfdata[br[0] + br[1] + 1: br[2] - 1]
bcontents = bytes.fromhex(contents.decode("utf8")) bcontents = bytes.fromhex(contents.decode("utf8"))
data1 = pdfdata[br[0]: br[0] + br[1]] data1 = pdfdata[br[0]: br[0] + br[1]]
data2 = pdfdata[br[2]: br[2] + br[3]] data2 = pdfdata[br[2]: br[2] + br[3]]
#signedData = data1 + data2 #signedData = data1 + data2
nome = 'Nome do assinante não localizado.' not_nome = nome = 'Nome do assinante não localizado.'
oname = '' oname = ''
try: try:
info = cms.ContentInfo.load(bcontents) info = cms.ContentInfo.load(bcontents)
@ -348,6 +357,7 @@ def signed_files_extraction_function(sender, instance, **kwargs):
pass pass
fd = None fd = None
if nome != not_nome:
signs.append((nome, [fd, oname])) signs.append((nome, [fd, oname]))
except Exception as e: except Exception as e:

8
sapl/base/templatetags/common_tags.py

@ -84,6 +84,9 @@ def desc_operation(value):
@register.filter @register.filter
def format_user(user): def format_user(user):
if not user:
return ""
if user.first_name: if user.first_name:
return user.first_name + " " + user.last_name + " (" + user.username + ")" return user.first_name + " " + user.last_name + " (" + user.username + ")"
else: else:
@ -409,3 +412,8 @@ def parse_datetime(value):
@register.filter @register.filter
def is_report_visible(request, url_path=None): def is_report_visible(request, url_path=None):
return is_report_allowed(request, url_path) return is_report_allowed(request, url_path)
@register.filter
def sort_by_index(queryset, index):
return sorted(queryset, key=lambda x: x[index])

10
sapl/base/templatetags/menus.py

@ -183,17 +183,21 @@ def resolve_urls_inplace(menu, pk, rm, context):
as funcionalidades diretas do MasterDetailCrud, como: as funcionalidades diretas do MasterDetailCrud, como:
- visualização de detalhes, adição, edição, remoção. - visualização de detalhes, adição, edição, remoção.
""" """
try:
if 'view' in context: if 'view' in context:
view = context['view'] view = context['view']
if hasattr(view, '__class__') and\ if hasattr(view, 'crud'):
hasattr(view.__class__, 'crud'): urls = view.crud.get_urls()
urls = view.__class__.crud.get_urls()
for u in urls: for u in urls:
if (u.name == url_name or if (u.name == url_name or
'urls_extras' in menu and 'urls_extras' in menu and
u.name in menu['urls_extras']): u.name in menu['urls_extras']):
menu['active'] = 'active' menu['active'] = 'active'
break break
except:
url_active = menu.get('url', '')
logger.warning(
f'Não foi possível definir se url {url_active} é a url ativa.')
elif 'check_permission' in menu and not context[ elif 'check_permission' in menu and not context[
'request'].user.has_perm(menu['check_permission']): 'request'].user.has_perm(menu['check_permission']):
menu['active'] = '' menu['active'] = ''

25
sapl/base/views.py

@ -6,7 +6,7 @@ import os
from django.apps.registry import apps from django.apps.registry import apps
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import get_user_model, views from django.contrib.auth import authenticate, login, get_user_model, views
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
@ -50,8 +50,8 @@ from sapl.relatorios.views import (relatorio_estatisticas_acesso_normas)
from sapl.sessao.models import (Bancada, SessaoPlenaria) from sapl.sessao.models import (Bancada, SessaoPlenaria)
from sapl.settings import EMAIL_SEND_USER from sapl.settings import EMAIL_SEND_USER
from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured, from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured,
SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, google_recaptcha_configured, sapl_as_sapn, SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, google_recaptcha_configured,
get_client_ip) get_client_ip, sapn_is_enabled, is_weak_password)
from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, EstatisticasAcessoNormasForm) from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, EstatisticasAcessoNormasForm)
from .models import AppConfig, CasaLegislativa from .models import AppConfig, CasaLegislativa
@ -62,7 +62,7 @@ def get_casalegislativa():
class IndexView(TemplateView): class IndexView(TemplateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if sapl_as_sapn(): if sapn_is_enabled():
return redirect('/norma/pesquisar') return redirect('/norma/pesquisar')
return TemplateView.get(self, request, *args, **kwargs) return TemplateView.get(self, request, *args, **kwargs)
@ -75,6 +75,21 @@ class LoginSapl(views.LoginView):
template_name = 'base/login.html' template_name = 'base/login.html'
authentication_form = LoginForm authentication_form = LoginForm
def form_valid(self, form):
"""Override do comportamento padrão para verificar senha fraca"""
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
user = authenticate(self.request, username=username, password=password)
if user is not None:
login(self.request, user)
if is_weak_password(password):
self.request.session['weak_password'] = True
return redirect(self.get_success_url())
# Fallback se falhar a autenticação (tecnicamente não devia chegar aqui)
return super().form_invalid(form)
class ConfirmarEmailView(TemplateView): class ConfirmarEmailView(TemplateView):
template_name = "email/confirma.html" template_name = "email/confirma.html"
@ -1481,6 +1496,8 @@ class AlterarSenha(FormView):
user.set_password(new_password) user.set_password(new_password)
user.save() user.save()
self.request.session.pop('weak_password', None)
return super().form_valid(form) return super().form_valid(form)

19
sapl/compilacao/models.py

@ -390,7 +390,6 @@ class TextoArticulado(TimestampedMixin):
@classonlymethod @classonlymethod
def update_or_create(cls, view_integracao, obj): def update_or_create(cls, view_integracao, obj):
map_fields = view_integracao.map_fields map_fields = view_integracao.map_fields
ta_values = getattr(view_integracao, 'ta_values', {}) ta_values = getattr(view_integracao, 'ta_values', {})
@ -1182,6 +1181,24 @@ class Dispositivo(BaseModel, TimestampedMixin):
help_text=_('O recorte de imagem ' help_text=_('O recorte de imagem '
'é possível após a atualização.')) 'é possível após a atualização.'))
# define custom manager
class SelectRelatedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('tipo_dispositivo',
'publicacao',
'ta',
'ta_publicado',
'dispositivo_subsequente',
'dispositivo_substituido',
'dispositivo_pai',
'dispositivo_pai__tipo_dispositivo',
'dispositivo_raiz',
'dispositivo_vigencia',
'dispositivo_atualizador'
)
# Replace the default manager with custom manager
objects = SelectRelatedManager()
class Meta: class Meta:
verbose_name = _('Dispositivo') verbose_name = _('Dispositivo')
verbose_name_plural = _('Dispositivos') verbose_name_plural = _('Dispositivos')

2
sapl/compilacao/urls.py

@ -23,7 +23,7 @@ urlpatterns_compilacao = [
url(r'^(?P<ta_id>[0-9]+)/text$', url(r'^(?P<ta_id>[0-9]+)/text$',
views.TextView.as_view(), name='ta_text'), views.TextView.as_view(), name='ta_text'),
url(r'^(?P<ta_id>[0-9]+)/text/vigencia/(?P<sign>.+)/$', url(r'^(?P<ta_id>[0-9]+)/text/vigencia/(?P<sign>.*:[A-Za-z0-9_-]+)/$',
views.TextView.as_view(), name='ta_vigencia'), views.TextView.as_view(), name='ta_vigencia'),
url(r'^(?P<ta_id>[0-9]+)/text/edit', url(r'^(?P<ta_id>[0-9]+)/text/edit',

27
sapl/context_processors.py

@ -1,15 +1,13 @@
import logging import logging
from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from sapl.utils import google_recaptcha_configured as google_recaptcha_configured_utils,\ from sapl.utils import google_recaptcha_configured as \
sapl_as_sapn as sapl_as_sapn_utils google_recaptcha_configured_utils, sapn_is_enabled, cached_call, get_base_url
from sapl.utils import mail_service_configured as mail_service_configured_utils from sapl.utils import mail_service_configured as mail_service_configured_utils
def parliament_info(request): def parliament_info(request):
from sapl.base.views import get_casalegislativa from sapl.base.views import get_casalegislativa
casa = get_casalegislativa() casa = get_casalegislativa()
if casa: if casa:
@ -19,7 +17,6 @@ def parliament_info(request):
def mail_service_configured(request): def mail_service_configured(request):
if not mail_service_configured_utils(request): if not mail_service_configured_utils(request):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.warning(_('Servidor de email não configurado.')) logger.warning(_('Servidor de email não configurado.'))
@ -28,7 +25,6 @@ def mail_service_configured(request):
def google_recaptcha_configured(request): def google_recaptcha_configured(request):
if not google_recaptcha_configured_utils(): if not google_recaptcha_configured_utils():
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.warning(_('Google Recaptcha não configurado.')) logger.warning(_('Google Recaptcha não configurado.'))
@ -36,10 +32,19 @@ def google_recaptcha_configured(request):
return {'google_recaptcha_configured': True} return {'google_recaptcha_configured': True}
def sapl_as_sapn(request): @cached_call("site-title", timeout=60 * 2)
return { def enable_sapn(request):
'sapl_as_sapn': sapl_as_sapn_utils(), verbose_name = _('Sistema de Apoio ao Processo Legislativo') \
'nome_sistema': _('Sistema de Apoio ao Processo Legislativo') if not sapn_is_enabled() \
if not sapl_as_sapn_utils()
else _('Sistema de Apoio à Publicação de Leis e Normas') else _('Sistema de Apoio à Publicação de Leis e Normas')
from sapl.base.models import CasaLegislativa
casa_legislativa = CasaLegislativa.objects.first()
nome_casa = casa_legislativa.nome if casa_legislativa and casa_legislativa.nome else ''
return {
'sapl_as_sapn': sapn_is_enabled(),
'nome_sistema': verbose_name,
'nome_casa': nome_casa,
'base_url': get_base_url(request),
} }

10
sapl/crispy_layout_mixin.py

@ -284,7 +284,7 @@ class CrispyLayoutFormMixin:
'text': field_display, 'text': field_display,
} }
def fk_urlize_for_detail(self, obj, fieldname): def fk_urlify_for_detail(self, obj, fieldname):
field = obj._meta.get_field(fieldname) field = obj._meta.get_field(fieldname)
value = getattr(obj, fieldname) value = getattr(obj, fieldname)
@ -298,6 +298,14 @@ class CrispyLayoutFormMixin:
return field.verbose_name, display return field.verbose_name, display
def fk_urlify_for_list(self, obj, field):
value = getattr(obj, field)
return reverse(
'%s:%s_detail' % (
value._meta.app_config.name,
value._meta.model_name),
kwargs={'pk': value.id}),
def m2m_urlize_for_detail(self, obj, fieldname): def m2m_urlize_for_detail(self, obj, fieldname):
manager, fieldname = tuple(fieldname.split('__')) manager, fieldname = tuple(fieldname.split('__'))

78
sapl/crud/base.py

@ -263,18 +263,18 @@ class CrudBaseMixin(CrispyLayoutFormMixin):
self.model_name_set = getattr( self.model_name_set = getattr(
obj.model, obj.model_set).field.model._meta.model_name obj.model, obj.model_set).field.model._meta.model_name
if hasattr(self, 'permission_required') and self.permission_required: if not hasattr(obj, 'public'):
if hasattr(obj, 'public'):
self.permission_required = list(
set(self.permission_required) - set(obj.public))
else:
obj.public = [] obj.public = []
self.permission_required = tuple(( if hasattr(self, 'permission_required') and self.permission_required:
self.permission(pr) for pr in self.permission_required))
else: self.permission_required = tuple(
obj.public = [] (
self.permission(pr) for pr in (
set(self.permission_required) - set(obj.public)
)
)
)
@classmethod @classmethod
def url_name(cls, suffix): def url_name(cls, suffix):
@ -413,7 +413,7 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
'composicao__periodo__data_inicio', 'composicao__periodo__data_fim')] 'composicao__periodo__data_inicio', 'composicao__periodo__data_fim')]
""" """
r = [] r = []
for fieldname in self.list_field_names: for (fieldname, _) in self._parse_field_names(self.list_field_names):
if not isinstance(fieldname, tuple): if not isinstance(fieldname, tuple):
fieldname = fieldname, fieldname = fieldname,
s = [] s = []
@ -446,11 +446,24 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
r.append(s) r.append(s)
return r return r
def _parse_field_names(self, field_name_list):
parsed_list = []
for field_name in field_name_list:
field_tuple = tuple(field_name.split('|')) \
if '|' in field_name else (field_name, None)
parsed_list.append(field_tuple)
return parsed_list
def _as_row(self, obj): def _as_row(self, obj):
r = [] r = []
for i, name in enumerate(self.list_field_names): for i, (name, func) in enumerate(self._parse_field_names(self.list_field_names)):
# URL padrão para primeira coluna da listagem
url = self.resolve_url( url = self.resolve_url(
ACTION_DETAIL, args=(obj.id,)) if i == 0 else None ACTION_DETAIL, args=(obj.id,)) if i == 0 else None
# gera URL para matéria a partir de fk_urlify_for_list em
# layouts.yaml
if i > 0 and func is not None:
url = getattr(self, func)(obj, name)[0]
"""Caso o crud list seja para uma relação ManyToManyField""" """Caso o crud list seja para uma relação ManyToManyField"""
if url and hasattr(self, 'crud') and \ if url and hasattr(self, 'crud') and \
@ -935,6 +948,8 @@ class CrudDeleteView(PermissionRequiredContainerCrudMixin,
class Crud: class Crud:
__abstract__ = True
BaseMixin = CrudBaseMixin BaseMixin = CrudBaseMixin
ListView = CrudListView ListView = CrudListView
CreateView = CrudCreateView CreateView = CrudCreateView
@ -943,16 +958,32 @@ class Crud:
DeleteView = CrudDeleteView DeleteView = CrudDeleteView
help_topic = '' help_topic = ''
class PublicMixin:
permission_required = []
@classonlymethod @classonlymethod
def get_urls(cls): def get_urls(cls):
def _add_base(view): def _add_base(view):
if view:
if not view:
return
if not cls.__abstract__:
return view
pr = set(view.permission_required) if hasattr(
view, 'permission_required') else set()
if hasattr(view, 'permission_required') and \
view.permission_required and \
hasattr(cls, 'public') and \
cls.public:
#print(view.permission_required, view)
#print(cls.public, cls)
pr = pr - set(cls.public)
class CrudViewWithBase(cls.BaseMixin, view): class CrudViewWithBase(cls.BaseMixin, view):
permission_required = tuple(pr)
model = cls.model model = cls.model
help_topic = cls.help_topic help_topic = cls.help_topic
crud = cls crud = cls
@ -966,6 +997,21 @@ class Crud:
CrudUpdateView = _add_base(cls.UpdateView) CrudUpdateView = _add_base(cls.UpdateView)
CrudDeleteView = _add_base(cls.DeleteView) CrudDeleteView = _add_base(cls.DeleteView)
cruds = CrudListView, CrudCreateView, CrudDetailView, CrudUpdateView, CrudDeleteView
if cls.__abstract__:
class CRUD(cls):
__abstract__ = False
ListView = CrudListView
CreateView = CrudCreateView
DetailView = CrudDetailView
UpdateView = CrudUpdateView
DeleteView = CrudDeleteView
for c in cruds:
if c:
c.crud = CRUD
cruds_base = [ cruds_base = [
(CrudListView.get_url_regex() (CrudListView.get_url_regex()
if CrudListView else None, CrudListView, ACTION_LIST), if CrudListView else None, CrudListView, ACTION_LIST),
@ -1525,7 +1571,7 @@ class MasterDetailCrud(Crud):
class CrudBaseForListAndDetailExternalAppView(MasterDetailCrud): class CrudBaseForListAndDetailExternalAppView(MasterDetailCrud):
CreateView, UpdateView, DeleteView = None, None, None CreateView, UpdateView, DeleteView = None, None, None
class BaseMixin(Crud.PublicMixin, MasterDetailCrud.BaseMixin): class BaseMixin(MasterDetailCrud.BaseMixin):
def resolve_url(self, suffix, args=None): def resolve_url(self, suffix, args=None):
obj = self.crud if hasattr(self, 'crud') else self obj = self.crud if hasattr(self, 'crud') else self

9
sapl/materia/forms.py

@ -42,7 +42,7 @@ from sapl.utils import (autor_label, autor_modal, timing,
models_with_gr_for_model, qs_override_django_filter, models_with_gr_for_model, qs_override_django_filter,
SEPARADOR_HASH_PROPOSICAO, SEPARADOR_HASH_PROPOSICAO,
validar_arquivo, YES_NO_CHOICES, validar_arquivo, YES_NO_CHOICES,
GoogleRecapthaMixin) GoogleRecapthaMixin, get_client_ip)
from .models import (AcompanhamentoMateria, Anexada, Autoria, from .models import (AcompanhamentoMateria, Anexada, Autoria,
DespachoInicial, DocumentoAcessorio, Numeracao, DespachoInicial, DocumentoAcessorio, Numeracao,
@ -1045,6 +1045,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
'ano_origem_externa', 'ano_origem_externa',
'data_origem_externa', 'data_origem_externa',
'local_origem_externa', 'local_origem_externa',
'regime_tramitacao',
] ]
def filter_ementa(self, queryset, name, value): def filter_ementa(self, queryset, name, value):
@ -1105,7 +1106,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
('tramitacao__status', 6), ('tramitacao__status', 6),
]) ])
row9 = to_row( row9 = to_row(
[('materiaassunto__assunto', 6), ('indexacao', 6)]) [('materiaassunto__assunto', 4), ('indexacao', 4), ('regime_tramitacao', 4)])
row8 = to_row( row8 = to_row(
[ [
@ -2659,7 +2660,9 @@ class ConfirmarProposicaoForm(ProposicaoForm):
protocolo.save() protocolo.save()
HistoricoProposicao.objects.create(proposicao=proposicao, HistoricoProposicao.objects.create(proposicao=proposicao,
status='E') status='E',
user=self.initial['user'],
ip=self.initial['ip'])
self.instance.results['messages']['success'].append(_( self.instance.results['messages']['success'].append(_(
'Protocolo realizado com sucesso')) 'Protocolo realizado com sucesso'))

19
sapl/materia/migrations/0085_auto_20231028_1838.py

@ -0,0 +1,19 @@
# Generated by Django 2.2.28 on 2023-10-28 21:38
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('materia', '0084_auto_20231007_2149'),
]
operations = [
migrations.AlterField(
model_name='numeracao',
name='tipo_materia',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='materia.TipoMateriaLegislativa', verbose_name='Tipo de Matéria'),
),
]

33
sapl/materia/migrations/0086_auto_20240711_1400.py

@ -0,0 +1,33 @@
# Generated by Django 2.2.28 on 2024-07-11 17:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0085_auto_20231028_1838'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2025, 2025), (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=[(2025, 2025), (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=[(2025, 2025), (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=[(2025, 2025), (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'),
),
]

26
sapl/materia/migrations/0087_update_viewdb_materiaemtramitacao.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-27 20:13
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0086_auto_20240711_1400'),
]
operations = [
migrations.RunSQL("""
create or replace view materia_materiaemtramitacao as
select m.id as id,
m.id as materia_id,
t.id as tramitacao_id,
t.unidade_tramitacao_destino_id as unidade_tramitacao_atual_id
from materia_materialegislativa m
inner join materia_tramitacao t on (m.id = t.materia_id)
where t.id = (select max(id) from materia_tramitacao where materia_id = m.id)
order by m.id DESC
"""),
]

27
sapl/materia/models.py

@ -1,4 +1,6 @@
from datetime import datetime
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -14,13 +16,13 @@ from sapl.comissoes.models import Comissao, Reuniao
from sapl.compilacao.models import (PerfilEstruturalTextoArticulado, from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
TextoArticulado) TextoArticulado)
from sapl.parlamentares.models import Parlamentar from sapl.parlamentares.models import Parlamentar
#from sapl.protocoloadm.models import Protocolo
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey, from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey,
SaplGenericRelation, restringe_tipos_de_arquivo_txt, SaplGenericRelation, restringe_tipos_de_arquivo_txt,
texto_upload_path, get_settings_auth_user_model, texto_upload_path, get_settings_auth_user_model,
OverwriteStorage) OverwriteStorage)
#from sapl.protocoloadm.models import Protocolo
EM_TRAMITACAO = [(1, 'Sim'), EM_TRAMITACAO = [(1, 'Sim'),
(0, 'Não')] (0, 'Não')]
@ -340,7 +342,13 @@ class MateriaLegislativa(models.Model):
if protocolo.timestamp: if protocolo.timestamp:
return protocolo.timestamp return protocolo.timestamp
elif protocolo.timestamp_data_hora_manual: elif protocolo.timestamp_data_hora_manual:
return protocolo.timestamp_data_hora_manual tz = timezone.localtime().tzinfo
return tz.localize(
datetime.combine(
protocolo.data,
protocolo.hora
)
)
elif protocolo.data: elif protocolo.data:
return protocolo.data return protocolo.data
@ -629,7 +637,7 @@ class Numeracao(models.Model):
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE) materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE)
tipo_materia = models.ForeignKey( tipo_materia = models.ForeignKey(
TipoMateriaLegislativa, TipoMateriaLegislativa,
on_delete=models.PROTECT, on_delete=models.CASCADE,
verbose_name=_('Tipo de Matéria')) verbose_name=_('Tipo de Matéria'))
numero_materia = models.CharField(max_length=5, numero_materia = models.CharField(max_length=5,
verbose_name=_('Número')) verbose_name=_('Número'))
@ -1027,7 +1035,8 @@ class Proposicao(models.Model):
def save(self, force_insert=False, force_update=False, using=None, def save(self, force_insert=False, force_update=False, using=None,
update_fields=None): update_fields=None):
# atualiza o usuario baseado no status da proposição (que esta sendo calculado pela data) # 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: if self.data_envio is not None and not self.usuario_envio:
self.usuario_envio = self.user self.usuario_envio = self.user
elif self.data_recebimento is not None and not self.usuario_recebimento: elif self.data_recebimento is not None and not self.usuario_recebimento:
@ -1259,6 +1268,16 @@ class MateriaEmTramitacao(models.Model):
MateriaLegislativa, on_delete=models.DO_NOTHING) MateriaLegislativa, on_delete=models.DO_NOTHING)
tramitacao = models.ForeignKey(Tramitacao, on_delete=models.DO_NOTHING) tramitacao = models.ForeignKey(Tramitacao, on_delete=models.DO_NOTHING)
unidade_tramitacao_atual = models.ForeignKey(
UnidadeTramitacao,
related_name='materiaemtramitacao_set',
on_delete=models.DO_NOTHING,
verbose_name=_('Unidade de Tramitação Atual'),
db_column='unidade_tramitacao_atual_id',
null=True,
blank=True
)
class Meta: class Meta:
managed = False managed = False
db_table = "materia_materiaemtramitacao" db_table = "materia_materiaemtramitacao"

39
sapl/materia/views.py

@ -24,6 +24,7 @@ from django.shortcuts import render
from django.template import loader from django.template import loader
from django.urls import reverse from django.urls import reverse
from django.utils import formats, timezone from django.utils import formats, timezone
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic import CreateView, ListView, TemplateView, UpdateView
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
@ -53,7 +54,7 @@ from sapl.utils import (autor_label, autor_modal, gerar_hash_arquivo, get_base_u
get_client_ip, get_mime_type_from_file_extension, lista_anexados, get_client_ip, get_mime_type_from_file_extension, lista_anexados,
mail_service_configured, montar_row_autor, SEPARADOR_HASH_PROPOSICAO, mail_service_configured, montar_row_autor, SEPARADOR_HASH_PROPOSICAO,
show_results_filter_set, get_tempfile_dir, show_results_filter_set, get_tempfile_dir,
google_recaptcha_configured) google_recaptcha_configured, MultiFormatOutputMixin)
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
AnexadaEmLoteFilterSet, AdicionarVariasAutoriasFilterSet, AnexadaEmLoteFilterSet, AdicionarVariasAutoriasFilterSet,
@ -1337,6 +1338,9 @@ class RelatoriaCrud(MasterDetailCrud):
return {'comissao': localizacao} return {'comissao': localizacao}
class ListView(MasterDetailCrud.ListView):
layout_key = 'RelatoriaList'
class UpdateView(MasterDetailCrud.UpdateView): class UpdateView(MasterDetailCrud.UpdateView):
form_class = RelatoriaForm form_class = RelatoriaForm
layout_key = None layout_key = None
@ -2076,11 +2080,27 @@ class AcompanhamentoExcluirView(TemplateView):
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
class MateriaLegislativaPesquisaView(FilterView): class MateriaLegislativaPesquisaView(MultiFormatOutputMixin, FilterView):
model = MateriaLegislativa model = MateriaLegislativa
filterset_class = MateriaLegislativaFilterSet filterset_class = MateriaLegislativaFilterSet
paginate_by = 50 paginate_by = 50
fields_base_report = [
'id', 'ano', 'numero', 'tipo__sigla', 'tipo__descricao', 'autoria__autor__nome', 'texto_original', 'ementa'
]
fields_report = {
'csv': fields_base_report,
'xlsx': fields_base_report,
'json': fields_base_report,
}
def hook_texto_original(self, obj):
url = self.request.build_absolute_uri('/')[:-1]
texto_original = obj.texto_original if not isinstance(
obj, dict) else obj["texto_original"]
return f'{url}/media/{texto_original}'
def get_filterset_kwargs(self, filterset_class): def get_filterset_kwargs(self, filterset_class):
super().get_filterset_kwargs(filterset_class) super().get_filterset_kwargs(filterset_class)
@ -2115,6 +2135,9 @@ class MateriaLegislativaPesquisaView(FilterView):
"anexadas", "anexadas",
"tipo", "tipo",
"texto_articulado", "texto_articulado",
"relatoria_set",
"relatoria_set__comissao",
"relatoria_set__parlamentar",
"tramitacao_set", "tramitacao_set",
"tramitacao_set__status", "tramitacao_set__status",
"tramitacao_set__unidade_tramitacao_local", "tramitacao_set__unidade_tramitacao_local",
@ -2136,7 +2159,8 @@ class MateriaLegislativaPesquisaView(FilterView):
qs = qs.filter(materiaassunto__isnull=True) qs = qs.filter(materiaassunto__isnull=True)
if 'o' in self.request.GET and not self.request.GET['o']: if 'o' in self.request.GET and not self.request.GET['o']:
args = ['-ano', 'tipo__sequencia_regimental', '-numero'] if BaseAppConfig.attr('ordenacao_pesquisa_materia') == 'R' else ['-ano', 'tipo__sigla', '-numero'] args = ['-ano', 'tipo__sequencia_regimental', '-numero'] if BaseAppConfig.attr(
'ordenacao_pesquisa_materia') == 'R' else ['-ano', 'tipo__sigla', '-numero']
qs = qs.order_by(*args) qs = qs.order_by(*args)
@ -2169,9 +2193,6 @@ class MateriaLegislativaPesquisaView(FilterView):
context['show_results'] = show_results_filter_set(qr) context['show_results'] = show_results_filter_set(qr)
context['USE_SOLR'] = settings.USE_SOLR if hasattr(
settings, 'USE_SOLR') else False
return context return context
@ -2700,9 +2721,9 @@ class EtiquetaPesquisaView(PermissionRequiredMixin, FormView):
if form.cleaned_data['processo_inicial']: if form.cleaned_data['processo_inicial']:
materias = materias.filter( materias = materias.filter(
numeracao__numero_materia__gte=form.cleaned_data[ numero__gte=form.cleaned_data[
'processo_inicial'], 'processo_inicial'],
numeracao__numero_materia__lte=form.cleaned_data[ numero__lte=form.cleaned_data[
'processo_final']) 'processo_final'])
context['quantidade'] = len(materias) context['quantidade'] = len(materias)
@ -3012,7 +3033,7 @@ def create_pdf_docacessorios(materia,excluir_restritos):
materia.pk, materia.pk,
time.mktime(datetime.now().timetuple())) time.mktime(datetime.now().timetuple()))
merger = PdfFileMerger() merger = PdfFileMerger(strict=False)
for f in docs_path: for f in docs_path:
merger.append(fileobj=f) merger.append(fileobj=f)

21
sapl/middleware.py

@ -0,0 +1,21 @@
import logging
from django.shortcuts import redirect
from django.urls import reverse
class CheckWeakPasswordMiddleware:
logger = logging.getLogger(__name__)
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated and \
request.session.get('weak_password', False) and \
request.path != reverse('sapl.base:alterar_senha') and \
request.path != reverse('sapl.base:logout'):
logging.warning(f"Usuário {request.user.username} possui senha fraca.")
return redirect('sapl.base:alterar_senha')
return self.get_response(request)

22
sapl/norma/forms.py

@ -1,10 +1,11 @@
import logging import logging
import re
from crispy_forms.layout import (Button, Fieldset, HTML, Layout) from crispy_forms.layout import (Button, Fieldset, HTML, Layout)
from django import forms from django import forms
from django.contrib.postgres.search import SearchVector from django.contrib.postgres.search import SearchVector
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db.models import Q from django.db.models import Q, F, Func, Value
from django.forms import ModelChoiceField, ModelForm, widgets from django.forms import ModelChoiceField, ModelForm, widgets
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -69,6 +70,10 @@ class NormaFilterSet(django_filters.FilterSet):
label='Ano', label='Ano',
choices=choice_anos_com_normas) choices=choice_anos_com_normas)
numero = django_filters.CharFilter(
method='filter_numero',
label=_('Número'))
ementa = django_filters.CharFilter( ementa = django_filters.CharFilter(
method='filter_ementa', method='filter_ementa',
label=_('Pesquisar expressões na ementa da norma')) label=_('Pesquisar expressões na ementa da norma'))
@ -129,6 +134,19 @@ class NormaFilterSet(django_filters.FilterSet):
form_actions(label='Pesquisar')) form_actions(label='Pesquisar'))
) )
def filter_numero(self, queryset, name, value):
p = r'(\W|_)'
value = re.sub(p, '', value, flags=re.IGNORECASE)
return queryset.annotate(
numero_clean=Func(
F('numero'),
Value(p),
Value(''),
Value('g'),
function='REGEXP_REPLACE'
)
).filter(numero_clean=value)
def filter_ementa(self, queryset, name, value): def filter_ementa(self, queryset, name, value):
return queryset.annotate(search=SearchVector('ementa', return queryset.annotate(search=SearchVector('ementa',
config='portuguese')).filter(search=value) config='portuguese')).filter(search=value)
@ -264,7 +282,7 @@ class NormaJuridicaForm(FileFieldCheckMixin, ModelForm):
texto_integral = self.cleaned_data.get('texto_integral', False) texto_integral = self.cleaned_data.get('texto_integral', False)
if texto_integral: if texto_integral:
validar_arquivo(texto_integral, "Texto Integral") validar_arquivo(texto_integral, "Texto Original")
return texto_integral return texto_integral

46
sapl/norma/migrations/0045_auto_20240711_1405.py

@ -0,0 +1,46 @@
# Generated by Django 2.2.28 on 2024-07-11 17:05
from django.db import migrations, models
import sapl.norma.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('norma', '0044_auto_20230529_1641'),
]
operations = [
migrations.AlterField(
model_name='anexonormajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2025, 2025), (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=[(2025, 2025), (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=[(2025, 2025), (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='normajuridica',
name='texto_integral',
field=models.FileField(blank=True,
max_length=300,
null=True,
storage=sapl.utils.OverwriteStorage(),
upload_to=sapl.norma.models.norma_upload_path,
validators=[
sapl.utils.restringe_tipos_de_arquivo_txt
],
verbose_name='Texto Original'),
),
]

31
sapl/norma/models.py

@ -138,7 +138,7 @@ class NormaJuridica(models.Model):
blank=True, blank=True,
null=True, null=True,
upload_to=norma_upload_path, upload_to=norma_upload_path,
verbose_name=_('Texto Integral'), verbose_name=_('Texto Original'),
storage=OverwriteStorage(), storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt]) validators=[restringe_tipos_de_arquivo_txt])
tipo = models.ForeignKey( tipo = models.ForeignKey(
@ -231,11 +231,21 @@ class NormaJuridica(models.Model):
ordering = ['-data', '-numero'] ordering = ['-data', '-numero']
def get_normas_relacionadas(self): def get_normas_relacionadas(self):
principais = NormaRelacionada.objects.filter( principais = NormaRelacionada.objects.\
norma_principal=self.id).order_by('norma_principal__data', select_related('tipo_vinculo',
'norma_principal',
'norma_relacionada',
'norma_principal__tipo',
'norma_relacionada__tipo').\
filter(norma_principal=self.id).order_by('norma_principal__data',
'norma_relacionada__data') 'norma_relacionada__data')
relacionadas = NormaRelacionada.objects.filter( relacionadas = NormaRelacionada.objects.\
norma_relacionada=self.id).order_by('norma_principal__data', select_related('tipo_vinculo',
'norma_principal',
'norma_relacionada',
'norma_principal__tipo',
'norma_relacionada__tipo').\
filter(norma_relacionada=self.id).order_by('norma_principal__data',
'norma_relacionada__data') 'norma_relacionada__data')
return (principais, relacionadas) return (principais, relacionadas)
@ -259,6 +269,17 @@ class NormaJuridica(models.Model):
def epigrafe(self): def epigrafe(self):
return self.__str__() return self.__str__()
@property
def epigrafe_simplificada(self):
numero_norma = self.numero
if numero_norma.isnumeric():
numero_norma = '{0:,}'.format(int(self.numero)).replace(',', '.')
return _('%(tipo)s%(numero)s, de %(data)s') % {
'tipo': self.tipo,
'numero': numero_norma,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y").lower()}
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):
texto_integral = self.texto_integral texto_integral = self.texto_integral
result = super().delete(using=using, keep_parents=keep_parents) result = super().delete(using=using, keep_parents=keep_parents)

22
sapl/norma/views.py

@ -28,7 +28,7 @@ from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud, make_pagination) MasterDetailCrud, make_pagination)
from sapl.materia.models import Orgao from sapl.materia.models import Orgao
from sapl.utils import show_results_filter_set, get_client_ip,\ from sapl.utils import show_results_filter_set, get_client_ip,\
sapl_as_sapn sapn_is_enabled, MultiFormatOutputMixin
from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm, from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm,
NormaPesquisaSimplesForm, NormaRelacionadaForm, NormaPesquisaSimplesForm, NormaRelacionadaForm,
@ -147,11 +147,27 @@ class NormaRelacionadaCrud(MasterDetailCrud):
layout_key = 'NormaRelacionadaDetail' layout_key = 'NormaRelacionadaDetail'
class NormaPesquisaView(FilterView): class NormaPesquisaView(MultiFormatOutputMixin, FilterView):
model = NormaJuridica model = NormaJuridica
filterset_class = NormaFilterSet filterset_class = NormaFilterSet
paginate_by = 50 paginate_by = 50
fields_base_report = [
'id', 'ano', 'numero', 'tipo__sigla', 'tipo__descricao', 'texto_integral', 'ementa'
]
fields_report = {
'csv': fields_base_report,
'xlsx': fields_base_report,
'json': fields_base_report,
}
def hook_texto_integral(self, obj):
url = self.request.build_absolute_uri('/')[:-1]
texto_integral = obj.texto_integral if not isinstance(
obj, dict) else obj["texto_integral"]
return f'{url}/media/{texto_integral}'
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
@ -187,8 +203,6 @@ class NormaPesquisaView(FilterView):
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr) context['show_results'] = show_results_filter_set(qr)
context['USE_SOLR'] = settings.USE_SOLR if hasattr(
settings, 'USE_SOLR') else False
return context return context

5
sapl/parlamentares/forms.py

@ -13,7 +13,6 @@ from django.forms import ModelForm
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django_filters import django_filters
from floppyforms.widgets import ClearableFileInput
from image_cropping.widgets import CropWidget, ImageCropWidget from image_cropping.widgets import CropWidget, ImageCropWidget
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
@ -26,10 +25,6 @@ from .models import (Coligacao, ComposicaoColigacao, Filiacao, Frente, Legislatu
Mandato, Parlamentar, Partido, Votante, Bloco, FrenteParlamentar, BlocoMembro) Mandato, Parlamentar, Partido, Votante, Bloco, FrenteParlamentar, BlocoMembro)
class ImageThumbnailFileInput(ClearableFileInput):
template_name = 'floppyforms/image_thumbnail.html'
class CustomImageCropWidget(ImageCropWidget): class CustomImageCropWidget(ImageCropWidget):
""" """
Custom ImageCropWidget that doesn't show the initial value of the field. Custom ImageCropWidget that doesn't show the initial value of the field.

27
sapl/parlamentares/migrations/0008_adiciona_cargos_mesa.py

@ -1,31 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations
import json
import os
from django.core.management import call_command
from django.db import migrations
def gera_cargos_mesa(apps, schema_editor):
CargoMesa = apps.get_model("parlamentares", "CargoMesa")
db_alias = schema_editor.connection.alias
cargos_mesa = CargoMesa.objects.all().exists()
if cargos_mesa:
# Caso haja algum CargoMesa cadastrado na base de dados,
# a migração não deve ser carregada para evitar duplicações de dados.
print("Carga de {} não efetuada. Já Existem {} cadastrados...".format(
CargoMesa._meta.verbose_name, CargoMesa._meta.verbose_name_plural))
else:
fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures'))
# pega partidos listados em fixtures/pre_popula_partidos.json
fixture_filename = 'pre_popula_cargosmesa.json'
fixture_file = os.path.join(fixture_dir, fixture_filename)
call_command('loaddata', fixture_file)
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
@ -37,5 +15,6 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.RunPython(gera_cargos_mesa), # obsoleto, migrado para 0044 devido a dependência gerada por 0043
# migrations.RunPython(gera_cargos_mesa),
] ]

41
sapl/parlamentares/migrations/0044_adiciona_cargos_mesa.py

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
from django.core.management import call_command
from django.db import migrations
def gera_cargos_mesa(apps, schema_editor):
CargoMesa = apps.get_model("parlamentares", "CargoMesa")
db_alias = schema_editor.connection.alias
cargos_mesa = CargoMesa.objects.all().exists()
if cargos_mesa:
# Caso haja algum CargoMesa cadastrado na base de dados,
# a migração não deve ser carregada para evitar duplicações de dados.
print("Carga de {} não efetuada. Já Existem {} cadastrados...".format(
CargoMesa._meta.verbose_name, CargoMesa._meta.verbose_name_plural))
else:
fixture_dir = os.path.abspath(os.path.join(
os.path.dirname(__file__), '../fixtures'))
# pega partidos listados em fixtures/pre_popula_partidos.json
fixture_filename = 'pre_popula_cargosmesa.json'
fixture_file = os.path.join(fixture_dir, fixture_filename)
call_command('loaddata', fixture_file)
class Migration(migrations.Migration):
dependencies = [
# A dependencia real desse script é o arquivo 0001_initial.py, mas
# isso gera um erro (Conflicting migrations detected; multiple leaf
# nodes in the migration graph). para não ocasionar problemas de migração,
# vamos manter a ordem padrão do django.
('parlamentares', '0043_auto_20231007_2149'),
]
operations = [
migrations.RunPython(gera_cargos_mesa),
]

26
sapl/parlamentares/urls.py

@ -37,6 +37,8 @@ urlpatterns = [
ProposicaoParlamentarCrud.get_urls() + ProposicaoParlamentarCrud.get_urls() +
RelatoriaParlamentarCrud.get_urls() + RelatoriaParlamentarCrud.get_urls() +
VotanteView.get_urls() VotanteView.get_urls()
)), )),
url(r'^parlamentar/pesquisar-parlamentar/', url(r'^parlamentar/pesquisar-parlamentar/',
@ -48,16 +50,21 @@ urlpatterns = [
url(r'^parlamentar/(?P<pk>\d+)/normas$', url(r'^parlamentar/(?P<pk>\d+)/normas$',
ParlamentarNormasView.as_view(), name='parlamentar_normas'), ParlamentarNormasView.as_view(), name='parlamentar_normas'),
url(r'^parlamentar/(?P<pk>\d+)/frentes/$', get_parlamentar_frentes, name='parlamentar_frentes'), url(r'^parlamentar/(?P<pk>\d+)/frentes/$',
get_parlamentar_frentes, name='parlamentar_frentes'),
url(r'^parlamentar/vincular-parlamentar/$', url(r'^parlamentar/vincular-parlamentar/$',
VincularParlamentarView.as_view(), name='vincular_parlamentar'), VincularParlamentarView.as_view(), name='vincular_parlamentar'),
url(r'^parlamentar/coligacao-legislatura/', coligacao_legislatura, name="coligacao_legislatura"), url(r'^parlamentar/coligacao-legislatura/',
url(r'^sistema/coligacao/', include(ColigacaoCrud.get_urls() + ComposicaoColigacaoCrud.get_urls())), coligacao_legislatura, name="coligacao_legislatura"),
url(r'^sistema/pesquisar-coligacao/', PesquisarColigacaoView.as_view(), name='pesquisar_coligacao'), url(r'^sistema/coligacao/', include(ColigacaoCrud.get_urls() +
ComposicaoColigacaoCrud.get_urls())),
url(r'^sistema/pesquisar-coligacao/',
PesquisarColigacaoView.as_view(), name='pesquisar_coligacao'),
url(r'^sistema/coligacao/', include(ColigacaoCrud.get_urls() + ComposicaoColigacaoCrud.get_urls())), url(r'^sistema/coligacao/', include(ColigacaoCrud.get_urls() +
ComposicaoColigacaoCrud.get_urls())),
url(r'^sistema/bloco/', include(BlocoCrud.get_urls())), url(r'^sistema/bloco/', include(BlocoCrud.get_urls())),
url(r'^sistema/bloco-cargo/', include(BlocoCargoCrud.get_urls())), url(r'^sistema/bloco-cargo/', include(BlocoCargoCrud.get_urls())),
@ -65,7 +72,8 @@ urlpatterns = [
url(r'^sistema/frente/', include(FrenteCrud.get_urls())), url(r'^sistema/frente/', include(FrenteCrud.get_urls())),
url(r'^sistema/frente-cargo/', include(FrenteCargoCrud.get_urls())), url(r'^sistema/frente-cargo/', include(FrenteCargoCrud.get_urls())),
url(r'^sistema/frente-parlamentares/', include(FrenteParlamentarCrud.get_urls())), url(r'^sistema/frente-parlamentares/',
include(FrenteParlamentarCrud.get_urls())),
url(r'^sistema/frente/atualiza-lista-parlamentares', url(r'^sistema/frente/atualiza-lista-parlamentares',
frente_atualiza_lista_parlamentares, frente_atualiza_lista_parlamentares,
@ -86,8 +94,10 @@ urlpatterns = [
include(TipoMilitarCrud.get_urls())), include(TipoMilitarCrud.get_urls())),
url(r'^sistema/parlamentar/partido/', include(PartidoCrud.get_urls())), url(r'^sistema/parlamentar/partido/', include(PartidoCrud.get_urls())),
url(r'^sistema/parlamentar/pesquisar-partido/', PesquisarPartidoView.as_view(), name='pesquisar_partido'), url(r'^sistema/parlamentar/pesquisar-partido/',
url(r'^sistema/parlamentar/partido/(?P<pk>\d+)/filiados$', parlamentares_filiados, name='parlamentares_filiados'), PesquisarPartidoView.as_view(), name='pesquisar_partido'),
url(r'^sistema/parlamentar/partido/(?P<pk>\d+)/filiados$',
parlamentares_filiados, name='parlamentares_filiados'),
url(r'^sistema/mesa-diretora/sessao-legislativa/', url(r'^sistema/mesa-diretora/sessao-legislativa/',
include(SessaoLegislativaCrud.get_urls())), include(SessaoLegislativaCrud.get_urls())),

12
sapl/parlamentares/views.py

@ -113,12 +113,13 @@ class VotanteView(MasterDetailCrud):
class FrenteList(MasterDetailCrud): class FrenteList(MasterDetailCrud):
public = [RP_DETAIL, RP_LIST]
model = Frente model = Frente
is_m2m = True is_m2m = True
parent_field = 'parlamentares' parent_field = 'parlamentares'
CreateView, UpdateView, DeleteView = None, None, None CreateView, UpdateView, DeleteView = None, None, None
class BaseMixin(Crud.PublicMixin, MasterDetailCrud.BaseMixin): class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['nome', 'data_criacao', 'data_extincao'] list_field_names = ['nome', 'data_criacao', 'data_extincao']
@classmethod @classmethod
@ -127,6 +128,7 @@ class FrenteList(MasterDetailCrud):
class RelatoriaParlamentarCrud(CrudBaseForListAndDetailExternalAppView): class RelatoriaParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
public = [RP_DETAIL, RP_LIST]
model = Relatoria model = Relatoria
parent_field = 'parlamentar' parent_field = 'parlamentar'
help_topic = 'tramitacao_relatoria' help_topic = 'tramitacao_relatoria'
@ -355,6 +357,7 @@ class PesquisarPartidoView(FilterView):
class ParticipacaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView): class ParticipacaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
public = [RP_DETAIL, RP_LIST]
model = Participacao model = Participacao
parent_field = 'parlamentar' parent_field = 'parlamentar'
namespace = AppConfig.name namespace = AppConfig.name
@ -589,6 +592,7 @@ def get_parlamentar_frentes(request, pk):
context = { context = {
'subnav_template_name': 'parlamentares/subnav.yaml', 'subnav_template_name': 'parlamentares/subnav.yaml',
'root_pk': pk, 'root_pk': pk,
'sexo_parlamentar': Parlamentar.objects.get(id=pk).sexo,
'nome_parlamentar': Parlamentar.objects.get(id=pk).nome_parlamentar, 'nome_parlamentar': Parlamentar.objects.get(id=pk).nome_parlamentar,
'frentes': frentes, 'frentes': frentes,
'num_frentes': len(frentes) 'num_frentes': len(frentes)
@ -1388,7 +1392,8 @@ def altera_field_mesa_public_view(request):
partido_parlamentar_sessao_legislativa(sessao, parlamentar)) partido_parlamentar_sessao_legislativa(sessao, parlamentar))
if parlamentar.fotografia: if parlamentar.fotografia:
try: try:
logger.warning(f"Iniciando cropping da imagem {parlamentar.fotografia}") logger.warning(
f"Iniciando cropping da imagem {parlamentar.fotografia}")
thumbnail_url = get_backend().get_thumbnail_url( thumbnail_url = get_backend().get_thumbnail_url(
parlamentar.fotografia, parlamentar.fotografia,
{ {
@ -1398,7 +1403,8 @@ def altera_field_mesa_public_view(request):
'detail': True, 'detail': True,
} }
) )
logger.warning(f"Cropping da imagem {parlamentar.fotografia} realizado com sucesso") logger.warning(
f"Cropping da imagem {parlamentar.fotografia} realizado com sucesso")
lista_fotos.append(thumbnail_url) lista_fotos.append(thumbnail_url)
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)

23
sapl/protocoloadm/migrations/0045_auto_20240711_1405.py

@ -0,0 +1,23 @@
# Generated by Django 2.2.28 on 2024-07-11 17:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0044_auto_20230529_1641'),
]
operations = [
migrations.AlterField(
model_name='documentoadministrativo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2025, 2025), (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=[(2025, 2025), (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'),
),
]

14
sapl/protocoloadm/views.py

@ -47,7 +47,7 @@ from sapl.relatorios.views import relatorio_doc_administrativos
from sapl.utils import (create_barcode, get_base_url, get_client_ip, from sapl.utils import (create_barcode, get_base_url, get_client_ip,
get_mime_type_from_file_extension, lista_anexados, get_mime_type_from_file_extension, lista_anexados,
show_results_filter_set, mail_service_configured, from_date_to_datetime_utc, show_results_filter_set, mail_service_configured, from_date_to_datetime_utc,
google_recaptcha_configured, get_tempfile_dir) google_recaptcha_configured, get_tempfile_dir, MultiFormatOutputMixin)
from .forms import (AcompanhamentoDocumentoForm, AnexadoEmLoteFilterSet, AnexadoForm, from .forms import (AcompanhamentoDocumentoForm, AnexadoEmLoteFilterSet, AnexadoForm,
AnularProtocoloAdmForm, compara_tramitacoes_doc, AnularProtocoloAdmForm, compara_tramitacoes_doc,
@ -174,7 +174,7 @@ def create_pdf_docacessorios(docadministrativo):
logger.info("Gerando compilado PDF de documentos acessorios com {} documentos" logger.info("Gerando compilado PDF de documentos acessorios com {} documentos"
.format(docs_path)) .format(docs_path))
merger = PdfFileMerger() merger = PdfFileMerger(strict=False)
for f in docs_path: for f in docs_path:
merger.append(fileobj=f) merger.append(fileobj=f)
@ -1039,6 +1039,7 @@ class ProtocoloMateriaTemplateView(PermissionRequiredMixin, TemplateView):
class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin, class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
MultiFormatOutputMixin,
PermissionRequiredMixin, PermissionRequiredMixin,
FilterView): FilterView):
model = DocumentoAdministrativo model = DocumentoAdministrativo
@ -1046,6 +1047,15 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
paginate_by = 10 paginate_by = 10
permission_required = ('protocoloadm.list_documentoadministrativo', ) permission_required = ('protocoloadm.list_documentoadministrativo', )
fields_base_report = [
'id', 'ano', 'numero', 'tipo__sigla', 'tipo__descricao', 'assunto'
]
fields_report = {
'csv': fields_base_report,
'xlsx': fields_base_report,
'json': fields_base_report,
}
def get_filterset_kwargs(self, filterset_class): def get_filterset_kwargs(self, filterset_class):
super(PesquisarDocumentoAdministrativoView, super(PesquisarDocumentoAdministrativoView,
self).get_filterset_kwargs(filterset_class) self).get_filterset_kwargs(filterset_class)

2
sapl/rules/group_geral.py

@ -26,7 +26,7 @@ rules_group_geral = {
[RP_ADD], __perms_publicas__), [RP_ADD], __perms_publicas__),
(base.TipoAutor, __base__, __perms_publicas__), (base.TipoAutor, __base__, __perms_publicas__),
(base.Autor, __base__, __perms_publicas__), (base.Autor, __base__, __perms_publicas__),
(base.OperadorAutor, __base__, __perms_publicas__), (base.OperadorAutor, __base__, set()),
(base.AuditLog, __base__, set()), (base.AuditLog, __base__, set()),
(base.Metadata, __base__, set()), (base.Metadata, __base__, set()),

121
sapl/sessao/views.py

@ -45,7 +45,8 @@ from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm, OrdemExpedien
CorrespondenciaForm, CorrespondenciaEmLoteFilterSet CorrespondenciaForm, CorrespondenciaEmLoteFilterSet
from sapl.sessao.models import Correspondencia from sapl.sessao.models import Correspondencia
from sapl.settings import TIME_ZONE from sapl.settings import TIME_ZONE
from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip,\
MultiFormatOutputMixin, PautaMultiFormatOutputMixin
from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm,
ExpedienteForm, JustificativaAusenciaForm, OcorrenciaSessaoForm, ListMateriaForm, ExpedienteForm, JustificativaAusenciaForm, OcorrenciaSessaoForm, ListMateriaForm,
@ -147,7 +148,8 @@ def verifica_votacoes_abertas(request):
for sessao in votacoes_abertas: for sessao in votacoes_abertas:
ordens = sessao.ordemdia_set.filter(votacao_aberta=True) ordens = sessao.ordemdia_set.filter(votacao_aberta=True)
expediente = sessao.expedientemateria_set.filter(votacao_aberta=True) expediente = sessao.expedientemateria_set.filter(
votacao_aberta=True)
for o in ordens: for o in ordens:
o.votacao_aberta = False o.votacao_aberta = False
o.save() o.save()
@ -1825,7 +1827,8 @@ def insere_parlamentar_composicao(request):
if parlamentar_ja_inserido: if parlamentar_ja_inserido:
logger.debug( logger.debug(
"user=" + username + ". Parlamentar (id={}) já inserido na sessao_plenaria(id={}) e cargo(ìd={})." "user=" + username +
". Parlamentar (id={}) já inserido na sessao_plenaria(id={}) e cargo(ìd={})."
.format(request.POST['parlamentar'], composicao.sessao_plenaria.id, composicao.cargo.id)) .format(request.POST['parlamentar'], composicao.sessao_plenaria.id, composicao.cargo.id))
return JsonResponse({'msg': ('Parlamentar já inserido!', 0)}) return JsonResponse({'msg': ('Parlamentar já inserido!', 0)})
@ -2213,7 +2216,7 @@ def get_materias_ordem_do_dia(sessao_plenaria):
if o.tipo_votacao == 2: if o.tipo_votacao == 2:
for voto in VotoParlamentar.objects.filter(ordem=o.id): for voto in VotoParlamentar.objects.filter(ordem=o.id):
voto_nominal.append( voto_nominal.append(
(voto.parlamentar.nome_completo, voto.voto)) (voto.parlamentar.nome_parlamentar, voto.voto))
voto = RegistroVotacao.objects.filter(ordem=o.id).last() voto = RegistroVotacao.objects.filter(ordem=o.id).last()
if voto: if voto:
@ -2534,7 +2537,8 @@ class ExpedienteView(FormMixin, DetailView):
msg = _('Registro salvo com sucesso') msg = _('Registro salvo com sucesso')
messages.add_message(self.request, messages.SUCCESS, msg) messages.add_message(self.request, messages.SUCCESS, msg)
self.logger.info( self.logger.info(
'user=' + username + '. ExpedienteSessao(sessao_plenaria_id={} e tipo_id={}) salvo com sucesso.' 'user=' + username +
'. ExpedienteSessao(sessao_plenaria_id={} e tipo_id={}) salvo com sucesso.'
.format(self.object.id, tipo)) .format(self.object.id, tipo))
return self.form_valid(form) return self.form_valid(form)
@ -3805,10 +3809,27 @@ class PautaSessaoView(TemplateView):
reverse('sapl.sessao:pauta_sessao_detail', kwargs={'pk': sessao.pk})) reverse('sapl.sessao:pauta_sessao_detail', kwargs={'pk': sessao.pk}))
class PautaSessaoDetailView(DetailView): class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView):
template_name = "sessao/pauta_sessao_detail.html" template_name = "sessao/pauta_sessao_detail.html"
model = SessaoPlenaria model = SessaoPlenaria
queryset_values_for_formats = False
fields_base_report = [
[('id', 'ID'), ('titulo', 'Matéria'), ('autor', 'Autor'), ('ementa', 'Ementa'), ('situacao', 'Situação')],
[('id', 'ID'), ('titulo', 'Matéria'), ('autor', 'Autor'), ('ementa', 'Ementa'), ('situacao', 'Situação')]
]
fields_report = {
'csv': fields_base_report,
'xlsx': fields_base_report,
'json': fields_base_report,
}
item_context = [
('materia_expediente', 'Matérias do Expediente'),
('materias_ordem', 'Matérias da Ordem do Dia')
]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
from sapl.relatorios.views import relatorio_pauta_sessao_weasy # Evitar import ciclico from sapl.relatorios.views import relatorio_pauta_sessao_weasy # Evitar import ciclico
@ -3866,7 +3887,8 @@ class PautaSessaoDetailView(DetailView):
'resultado_observacao': resultado_observacao, 'resultado_observacao': resultado_observacao,
'situacao': ultima_tramitacao.status if ultima_tramitacao else _("Não informada"), 'situacao': ultima_tramitacao.status if ultima_tramitacao else _("Não informada"),
'processo': f'{str(numeracao.numero_materia)}/{str(numeracao.ano_materia)}' if numeracao else '-', 'processo': f'{str(numeracao.numero_materia)}/{str(numeracao.ano_materia)}' if numeracao else '-',
'autor': [str(x.autor) for x in m.materia.autoria_set.select_related('autor').all()] 'autor': [str(x.autor) for x in m.materia.autoria_set.select_related('autor').all()],
'turno': get_turno(ultima_tramitacao.turno) if ultima_tramitacao else ''
}) })
context.update({'materia_expediente': materias_expediente}) context.update({'materia_expediente': materias_expediente})
@ -3949,7 +3971,8 @@ class PautaSessaoDetailView(DetailView):
'resultado_observacao': resultado_observacao, 'resultado_observacao': resultado_observacao,
'situacao': ultima_tramitacao.status if ultima_tramitacao else _("Não informada"), 'situacao': ultima_tramitacao.status if ultima_tramitacao else _("Não informada"),
'processo': f'{str(numeracao.numero_materia)}/{str(numeracao.ano_materia)}' if numeracao else '-', 'processo': f'{str(numeracao.numero_materia)}/{str(numeracao.ano_materia)}' if numeracao else '-',
'autor': [str(x.autor) for x in Autoria.objects.select_related("autor").filter(materia_id=o.materia_id)] 'autor': [str(x.autor) for x in Autoria.objects.select_related("autor").filter(materia_id=o.materia_id)],
'turno': get_turno(ultima_tramitacao.turno) if ultima_tramitacao else ''
}) })
context.update({ context.update({
@ -3964,13 +3987,26 @@ class PautaSessaoDetailView(DetailView):
return self.render_to_response(context) return self.render_to_response(context)
class PesquisarSessaoPlenariaView(FilterView): class PesquisarSessaoPlenariaView(MultiFormatOutputMixin, FilterView):
model = SessaoPlenaria model = SessaoPlenaria
filterset_class = SessaoPlenariaFilterSet filterset_class = SessaoPlenariaFilterSet
paginate_by = 10 paginate_by = 10
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
viewname = 'sapl.sessao:pesquisar_sessao'
queryset_values_for_formats = False
fields_base_report = [
'id', 'data_inicio', 'hora_inicio', 'data_fim', 'hora_fim', '',
]
fields_report = {
'csv': fields_base_report,
'xlsx': fields_base_report,
'json': fields_base_report,
}
def get_filterset_kwargs(self, filterset_class): def get_filterset_kwargs(self, filterset_class):
super().get_filterset_kwargs(filterset_class) super().get_filterset_kwargs(filterset_class)
@ -3987,50 +4023,60 @@ class PesquisarSessaoPlenariaView(FilterView):
}) })
return kwargs return kwargs
def hook_header_(self):
return force_text(_('Título'))
def hook_(self, obj):
return str(obj)
def hook_data_inicio(self, obj):
return str(obj.data_inicio or '')
def hook_data_fim(self, obj):
return str(obj.data_fim or '')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['title'] = _('Pesquisar Sessão Plenária') context['title'] = _('Pesquisar Sessão Plenária')
paginator = context['paginator'] paginator = context['paginator']
page_obj = context['page_obj'] page_obj = context['page_obj']
context['page_range'] = make_pagination( context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages) page_obj.number, paginator.num_pages)
context['USE_SOLR'] = settings.USE_SOLR if hasattr( context['show_results'] = show_results_filter_set(
settings, 'USE_SOLR') else False self.request.GET.copy())
return context
def get(self, request, *args, **kwargs):
super().get(request)
# Se a pesquisa estiver quebrando com a paginação
# Olhe esta função abaixo
# Provavelmente você criou um novo campo no Form/FilterSet
# Então a ordem da URL está diferente
data = self.filterset.data data = self.filterset.data
if data and data.get('data_inicio__year') is not None: if data and data.get('data_inicio__year') is not None:
url = "&" + str(self.request.META['QUERY_STRING']) url = "&" + str(self.request.META['QUERY_STRING'])
if url.startswith("&page"): if url.startswith("&page"):
ponto_comeco = url.find('data_inicio__year=') - 1 ponto_comeco = url.find('data_inicio__year=') - 1
url = url[ponto_comeco:] url = url[ponto_comeco:]
else: context['filter_url'] = url
url = ''
context = self.get_context_data(filter=self.filterset, context['numero_res'] = len(self.object_list)
object_list=self.object_list,
filter_url=url,
numero_res=len(self.object_list)
)
context['show_results'] = show_results_filter_set( return context
self.request.GET.copy())
def get(self, request, *args, **kwargs):
r = super().get(request)
data = self.filterset.data
if not data:
return HttpResponseRedirect(
reverse(
self.viewname
) + f'?data_inicio__year={timezone.now().year}'
)
username = request.user.username username = request.user.username
self.logger.debug('user=' + username + '. Pesquisa de SessaoPlenaria.') self.logger.debug('user=' + username + '. Pesquisa de SessaoPlenaria.')
return self.render_to_response(context) return r
class PesquisarPautaSessaoView(PesquisarSessaoPlenariaView): class PesquisarPautaSessaoView(PesquisarSessaoPlenariaView):
@ -4039,6 +4085,8 @@ class PesquisarPautaSessaoView(PesquisarSessaoPlenariaView):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
viewname = 'sapl.sessao:pesquisar_pauta'
def get_filterset_kwargs(self, filterset_class): def get_filterset_kwargs(self, filterset_class):
kwargs = super().get_filterset_kwargs(filterset_class) kwargs = super().get_filterset_kwargs(filterset_class)
qs = kwargs.get('queryset') qs = kwargs.get('queryset')
@ -4319,7 +4367,7 @@ def mudar_ordem_materia_sessao(request):
class JustificativaAusenciaCrud(MasterDetailCrud): class JustificativaAusenciaCrud(MasterDetailCrud):
model = JustificativaAusencia model = JustificativaAusencia
public = [RP_LIST, RP_DETAIL, ] public = [RP_LIST]
parent_field = 'sessao_plenaria' parent_field = 'sessao_plenaria'
class BaseMixin(MasterDetailCrud.BaseMixin): class BaseMixin(MasterDetailCrud.BaseMixin):
@ -4425,8 +4473,14 @@ class LeituraEmBloco(PermissionRequiredForAppCrudMixin, ListView):
selectedlist = request.POST.getlist('marcadas_4') selectedlist = request.POST.getlist('marcadas_4')
if request.POST['origem'] == 'ordem': if request.POST['origem'] == 'ordem':
models = OrdemDia.objects.filter(id__in=selectedlist) models = OrdemDia.objects.filter(id__in=selectedlist)
presenca_model = PresencaOrdemDia
elif request.POST['origem'] == 'expediente': elif request.POST['origem'] == 'expediente':
models = ExpedienteMateria.objects.filter(id__in=selectedlist) models = ExpedienteMateria.objects.filter(id__in=selectedlist)
presenca_model = SessaoPlenariaPresenca
if (not verifica_presenca(request, presenca_model, self.kwargs['pk'], True) or
not verifica_sessao_iniciada(request, self.kwargs['pk'], True)):
return self.get(request, self.kwargs)
if not models: if not models:
messages.add_message(self.request, messages.ERROR, messages.add_message(self.request, messages.ERROR,
@ -4451,8 +4505,11 @@ class LeituraEmBloco(PermissionRequiredForAppCrudMixin, ListView):
leituras.append(obj) leituras.append(obj)
RegistroLeitura.objects.bulk_create(leituras) RegistroLeitura.objects.bulk_create(leituras)
models.update(resultado='Matéria Lida')
else: else:
messages.add_message(self.request, messages.ERROR, _('Nenhuma matéria selecionada para leitura em Bloco')) messages.add_message(self.request, messages.ERROR, _(
'Nenhuma matéria selecionada para leitura em Bloco'))
return self.get(request, self.kwargs) return self.get(request, self.kwargs)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())

34
sapl/settings.py

@ -30,7 +30,7 @@ BASE_DIR = Path(__file__).ancestor(1)
PROJECT_DIR = Path(__file__).ancestor(2) PROJECT_DIR = Path(__file__).ancestor(2)
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY', default='') SECRET_KEY = config('SECRET_KEY', default='32jk1h412l3kjh421lkj4hlkj234')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=False, cast=bool) DEBUG = config('DEBUG', default=False, cast=bool)
@ -41,7 +41,7 @@ ALLOWED_HOSTS = ['*']
LOGIN_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/?next=' LOGIN_URL = '/login/?next='
SAPL_VERSION = '3.1.163-RC17' SAPL_VERSION = '3.1.164-RC0'
if DEBUG: if DEBUG:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
@ -75,10 +75,13 @@ INSTALLED_APPS = (
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.forms',
'django_extensions', 'django_extensions',
'crispy_forms', 'crispy_forms',
'floppyforms',
'waffle',
'drf_spectacular', 'drf_spectacular',
'rest_framework', 'rest_framework',
@ -137,6 +140,8 @@ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware',
'django_prometheus.middleware.PrometheusAfterMiddleware', 'django_prometheus.middleware.PrometheusAfterMiddleware',
'waffle.middleware.WaffleMiddleware',
'sapl.middleware.CheckWeakPasswordMiddleware',
] ]
if DEBUG: if DEBUG:
INSTALLED_APPS += ('debug_toolbar',) INSTALLED_APPS += ('debug_toolbar',)
@ -189,11 +194,14 @@ CACHES = {
'default': { 'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache', 'LOCATION': '/var/tmp/django_cache',
'OPTIONS': {"MAX_ENTRIES": 1000},
} }
} }
ROOT_URLCONF = 'sapl.urls' ROOT_URLCONF = 'sapl.urls'
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
@ -210,8 +218,7 @@ TEMPLATES = [
'sapl.context_processors.parliament_info', 'sapl.context_processors.parliament_info',
'sapl.context_processors.mail_service_configured', 'sapl.context_processors.mail_service_configured',
'sapl.context_processors.google_recaptcha_configured', 'sapl.context_processors.google_recaptcha_configured',
'sapl.context_processors.sapl_as_sapn', 'sapl.context_processors.enable_sapn',
], ],
'debug': DEBUG 'debug': DEBUG
}, },
@ -255,6 +262,16 @@ DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='')
SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='') SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='')
EMAIL_RUNNING = None EMAIL_RUNNING = None
# Feature Flag
WAFFLE_FLAG_DEFAULT = False
WAFFLE_SWITCH_DEFAULT = False
WAFFLE_CREATE_MISSING_FLAGS = True
WAFFLE_LOG_MISSING_FLAGS = True
WAFFLE_CREATE_MISSING_SWITCHES = True
WAFFLE_LOG_MISSING_SWITCHES = True
WAFFLE_ENABLE_ADMIN_PAGES = True
MAX_DOC_UPLOAD_SIZE = 150 * 1024 * 1024 # 150MB MAX_DOC_UPLOAD_SIZE = 150 * 1024 * 1024 # 150MB
MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10MB DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10MB
@ -321,9 +338,6 @@ DAB_FIELD_RENDERER = \
CRISPY_TEMPLATE_PACK = 'bootstrap4' CRISPY_TEMPLATE_PACK = 'bootstrap4'
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap4' CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap4'
CRISPY_FAIL_SILENTLY = not DEBUG CRISPY_FAIL_SILENTLY = not DEBUG
FLOPPY_FORMS_USE_GIS = False
FORM_RENDERER = 'django.forms.renderers.DjangoTemplates'
# suprime texto de ajuda default do django-filter # suprime texto de ajuda default do django-filter
FILTERS_HELP_TEXT_FILTER = False FILTERS_HELP_TEXT_FILTER = False
@ -375,12 +389,12 @@ LOGGING = {
}, },
'loggers': { 'loggers': {
'sapl': { 'sapl': {
'handlers': ['applogfile'] + ['console_verbose'] if LOGGING_CONSOLE_VERBOSE else [], 'handlers': ['applogfile'] + (['console_verbose'] if LOGGING_CONSOLE_VERBOSE else []),
'level': 'DEBUG' if LOGGING_CONSOLE_VERBOSE else 'INFO', 'level': 'DEBUG' if LOGGING_CONSOLE_VERBOSE else 'INFO',
'propagate': True, 'propagate': True,
}, },
'django': { 'django': {
'handlers': ['applogfile'] + ['console_verbose'] if LOGGING_CONSOLE_VERBOSE else [], 'handlers': ['applogfile'] + (['console_verbose'] if LOGGING_CONSOLE_VERBOSE else []),
'level': 'ERROR', 'level': 'ERROR',
'propagate': True, 'propagate': True,
}, },

13
sapl/static/sapl/frontend/css/chunk-vendors.045ec640.css

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/css/chunk-vendors.045ec640.css.gz

Binary file not shown.

13
sapl/static/sapl/frontend/css/chunk-vendors.299c587b.css

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/css/chunk-vendors.299c587b.css.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/css/compilacao.0baf3580.css.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/css/global.45591136.css.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/css/painel.e2b9504e.css.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-brands-400.5d18d427.ttf

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-brands-400.5d18d427.ttf.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-brands-400.87587a68.woff2

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-brands-400.9a905705.ttf

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-brands-400.9a905705.ttf.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-brands-400.b6033b54.woff2

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-regular-400.3580b4a9.woff2

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-regular-400.3ccdbd3d.woff2

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-regular-400.67a0fb74.ttf

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-regular-400.67a0fb74.ttf.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-regular-400.81482cd4.ttf

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-regular-400.81482cd4.ttf.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-solid-900.0b0cc8a6.woff2

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-solid-900.69d3141a.ttf

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-solid-900.69d3141a.ttf.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-solid-900.6a8db53d.ttf

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-solid-900.6a8db53d.ttf.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-solid-900.fd0b155c.woff2

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-v4compatibility.2c070fd2.ttf

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-v4compatibility.2c070fd2.ttf.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-v4compatibility.e4efb16c.ttf

Binary file not shown.

BIN
sapl/static/sapl/frontend/fonts/fa-v4compatibility.e4efb16c.ttf.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/img/down_arrow_select.jpg.gz

Binary file not shown.

19
sapl/static/sapl/frontend/js/chunk-vendors.5e41c7a6.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/chunk-vendors.5e41c7a6.js.LICENSE.txt.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/js/chunk-vendors.5e41c7a6.js.gz

Binary file not shown.

19
sapl/static/sapl/frontend/js/chunk-vendors.e8ab4373.js

File diff suppressed because one or more lines are too long

244
sapl/static/sapl/frontend/js/chunk-vendors.5e41c7a6.js.LICENSE.txt → sapl/static/sapl/frontend/js/chunk-vendors.e8ab4373.js.LICENSE.txt

@ -5,8 +5,8 @@
*/ */
/*! /*!
* Vue.js v2.7.14 * Vue.js v2.7.16
* (c) 2014-2022 Evan You * (c) 2014-2023 Evan You
* Released under the MIT License. * Released under the MIT License.
*/ */
@ -22,273 +22,273 @@
*/ */
/*! /*!
* jQuery UI :data 1.13.2 * jQuery UI :data 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Autocomplete 1.13.2 * jQuery UI Autocomplete 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Button 1.13.2 * jQuery UI Button 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Checkboxradio 1.13.2 * jQuery UI Checkboxradio 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Controlgroup 1.13.2 * jQuery UI Controlgroup 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Datepicker 1.13.2 * jQuery UI Datepicker 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Dialog 1.13.2 * jQuery UI Dialog 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Disable Selection 1.13.2 * jQuery UI Disable Selection 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Draggable 1.13.2 * jQuery UI Draggable 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Droppable 1.13.2 * jQuery UI Droppable 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Effects 1.13.2 * jQuery UI Effects 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Focusable 1.13.2 * jQuery UI Focusable 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Form Reset Mixin 1.13.2 * jQuery UI Form Reset Mixin 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Keycode 1.13.2 * jQuery UI Keycode 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Labels 1.13.2 * jQuery UI Labels 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Menu 1.13.2 * jQuery UI Menu 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Mouse 1.13.2 * jQuery UI Mouse 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Position 1.13.2 * jQuery UI Position 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
* *
* http://api.jqueryui.com/position/ * https://api.jqueryui.com/position/
*/ */
/*! /*!
* jQuery UI Progressbar 1.13.2 * jQuery UI Progressbar 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Resizable 1.13.2 * jQuery UI Resizable 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Scroll Parent 1.13.2 * jQuery UI Scroll Parent 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Sortable 1.13.2 * jQuery UI Sortable 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Spinner 1.13.2 * jQuery UI Spinner 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Support for jQuery core 1.8.x and newer 1.13.2 * jQuery UI Support for jQuery core 1.8.x and newer 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
* *
*/ */
/*! /*!
* jQuery UI Tabbable 1.13.2 * jQuery UI Tabbable 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Tabs 1.13.2 * jQuery UI Tabs 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Tooltip 1.13.2 * jQuery UI Tooltip 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Unique ID 1.13.2 * jQuery UI Unique ID 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! /*!
* jQuery UI Widget 1.13.2 * jQuery UI Widget 1.13.3
* http://jqueryui.com * https://jqueryui.com
* *
* Copyright jQuery Foundation and other contributors * Copyright OpenJS Foundation and other contributors
* Released under the MIT license. * Released under the MIT license.
* http://jquery.org/license * https://jquery.org/license
*/ */
/*! jQuery UI - v1.13.2 - 2022-07-14 /*! jQuery UI - v1.13.3 - 2024-04-26
* http://jqueryui.com * https://jqueryui.com
* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-patch.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js * Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-patch.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js
* Copyright jQuery Foundation and other contributors; Licensed MIT */ * Copyright OpenJS Foundation and other contributors; Licensed MIT */
/** /**
* @license * @license

BIN
sapl/static/sapl/frontend/js/chunk-vendors.e8ab4373.js.LICENSE.txt.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/js/chunk-vendors.e8ab4373.js.gz

Binary file not shown.

1
sapl/static/sapl/frontend/js/compilacao.57574b17.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/compilacao.57574b17.js.gz

Binary file not shown.

1
sapl/static/sapl/frontend/js/compilacao.d3adb1c1.js

File diff suppressed because one or more lines are too long

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

Binary file not shown.

2
sapl/static/sapl/frontend/js/global.2f5aff92.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/global.2f5aff92.js.gz

Binary file not shown.

2
sapl/static/sapl/frontend/js/global.8ef6e932.js

File diff suppressed because one or more lines are too long

0
sapl/static/sapl/frontend/js/global.2f5aff92.js.LICENSE.txt → sapl/static/sapl/frontend/js/global.8ef6e932.js.LICENSE.txt

BIN
sapl/static/sapl/frontend/js/global.8ef6e932.js.gz

Binary file not shown.

2
sapl/static/sapl/frontend/js/painel.aa1df64a.js → sapl/static/sapl/frontend/js/painel.73a4fb17.js

@ -1 +1 @@
(()=>{"use strict";var u,f,e,o={4709:(e,r,o)=>{o(6992),o(8674),o(9601),o(7727)}},t={};function s(e){var r=t[e];return void 0!==r||(r=t[e]={id:e,loaded:!1,exports:{}},o[e].call(r.exports,r,r.exports,s),r.loaded=!0),r.exports}s.m=o,u=[],s.O=(e,r,o,t)=>{if(!r){for(var n=1/0,l=0;l<u.length;l++){for(var i,[r,o,t]=u[l],a=!0,d=0;d<r.length;d++)(!1&t||t<=n)&&Object.keys(s.O).every(e=>s.O[e](r[d]))?r.splice(d--,1):(a=!1,t<n&&(n=t));a&&(u.splice(l--,1),void 0!==(i=o()))&&(e=i)}return e}t=t||0;for(var l=u.length;0<l&&u[l-1][2]>t;l--)u[l]=u[l-1];u[l]=[r,o,t]},s.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return s.d(r,{a:r}),r},s.d=(e,r)=>{for(var o in r)s.o(r,o)&&!s.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:r[o]})},s.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),s.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),s.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},s.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),s.j=567,f={567:0},s.O.j=e=>0===f[e],r=(e,r)=>{var o,t,n,[l,i,a]=r,d=0;if(l.some(e=>0!==f[e])){for(o in i)s.o(i,o)&&(s.m[o]=i[o]);a&&(n=a(s))}for(e&&e(r);d<l.length;d++)t=l[d],s.o(f,t)&&f[t]&&f[t][0](),f[t]=0;return s.O(n)},(e=self.webpackChunksapl_frontend=self.webpackChunksapl_frontend||[]).forEach(r.bind(null,0)),e.push=r.bind(null,e.push.bind(e));var r=s.O(void 0,[998],()=>s(4709));s.O(r)})(); (()=>{"use strict";var u,f,e,o={2918:(e,r,o)=>{o(3792),o(3362),o(9085),o(9391)}},t={};function s(e){var r=t[e];return void 0!==r||(r=t[e]={id:e,loaded:!1,exports:{}},o[e].call(r.exports,r,r.exports,s),r.loaded=!0),r.exports}s.m=o,u=[],s.O=(e,r,o,t)=>{if(!r){for(var n=1/0,l=0;l<u.length;l++){for(var i,[r,o,t]=u[l],a=!0,d=0;d<r.length;d++)(!1&t||t<=n)&&Object.keys(s.O).every(e=>s.O[e](r[d]))?r.splice(d--,1):(a=!1,t<n&&(n=t));a&&(u.splice(l--,1),void 0!==(i=o()))&&(e=i)}return e}t=t||0;for(var l=u.length;0<l&&u[l-1][2]>t;l--)u[l]=u[l-1];u[l]=[r,o,t]},s.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return s.d(r,{a:r}),r},s.d=(e,r)=>{for(var o in r)s.o(r,o)&&!s.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:r[o]})},s.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),s.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),s.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},s.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),s.j=772,f={772:0},s.O.j=e=>0===f[e],r=(e,r)=>{var o,t,n,[l,i,a]=r,d=0;if(l.some(e=>0!==f[e])){for(o in i)s.o(i,o)&&(s.m[o]=i[o]);a&&(n=a(s))}for(e&&e(r);d<l.length;d++)t=l[d],s.o(f,t)&&f[t]&&f[t][0](),f[t]=0;return s.O(n)},(e=self.webpackChunksapl_frontend=self.webpackChunksapl_frontend||[]).forEach(r.bind(null,0)),e.push=r.bind(null,e.push.bind(e));var r=s.O(void 0,[504],()=>s(2918));s.O(r)})();

BIN
sapl/static/sapl/frontend/js/painel.73a4fb17.js.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/js/painel.aa1df64a.js.gz

Binary file not shown.

1
sapl/static/sapl/frontend/js/parlamentar.f0710dca.js

@ -0,0 +1 @@
(()=>{"use strict";var u,p,e,t={8020:(e,a,t)=>{t(3792),t(3362),t(9085),t(9391),t(6918),t(2008),t(2712),t(3288),t(6099),t(8781);var r=t(2893),i=t(8246),s=t(7906),n=t(2543);s.A.defaults.xsrfCookieName="csrftoken",s.A.defaults.xsrfHeaderName="X-CSRFToken",r.Ay.use(i.m0),new r.Ay({delimiters:["[[","]]"],el:"#app2",data:function(){return{nome_pesquisa:"",is_pesquisa:!1,legislatura_selecionada:"",legislaturas:[],parlamentares:[],visible_parlamentares:[],size_parlamentares:0,filter_ativo:!0,filter_titular:""}},watch:{nome_pesquisa:function(e){this.debouncepesquisaParlamentar()}},created:function(){this.debouncepesquisaParlamentar=n.debounce(this.pesquisaParlamentar,500)},methods:{getParlamentares:function(e){var a=this;!this.legislatura_selecionada&&"0"!==this.legislatura_selecionada.toString()||s.A.get("/api/parlamentares/legislatura/"+this.legislatura_selecionada+"/parlamentares/?get_all=true").then(function(e){a.parlamentares=e.data,a.visible_parlamentares=a.parlamentares,a.size_parlamentares=a.visible_parlamentares.length,a.checkTitularAtivo()}).catch(function(e){console.error("Ocorreu um erro ao obter os dados de parlamentares:"+e)})},pesquisaParlamentar:function(e){var a=this;s.A.get("/api/parlamentares/parlamentar/search_parlamentares/",{params:{nome_parlamentar:this.nome_pesquisa}}).then(function(e){a.parlamentares=e.data,a.visible_parlamentares=a.parlamentares,a.size_parlamentares=a.visible_parlamentares.length}).catch(function(e){console.error("Erro ao procurar parlamentar:"+e)})},checkTitularAtivo:function(e){this.visible_parlamentares=this.parlamentares,this.filter_ativo&&(this.visible_parlamentares=this.visible_parlamentares.filter(function(e){return e.ativo})),this.filter_titular&&(this.visible_parlamentares=this.visible_parlamentares.filter(function(e){return"Sim"===e.titular})),this.size_parlamentares=this.visible_parlamentares.length},pesquisaChange:function(e){this.is_pesquisa=!this.is_pesquisa,this.filter_ativo=!0,this.is_pesquisa?this.parlamentares=[]:this.getParlamentares()}},mounted:function(){var a=this;s.A.get("/api/parlamentares/legislatura/?get_all=true").then(function(e){a.legislaturas=e.data;var i=(new Date).getFullYear();a.legislatura_selecionada=a.legislaturas.reduce(function(e,a){var t=new Date(a.data_inicio+" 00:00").getFullYear(),r=new Date(a.data_fim+" 00:00").getFullYear();return e=t<=i&&i<=r?a.id:e},"")}).then(function(e){a.getParlamentares()}).catch(function(e){console.error("Ocorreu um erro ao obter os dados de legislação: "+e)})}})}},r={};function c(e){var a=r[e];return void 0!==a||(a=r[e]={id:e,loaded:!1,exports:{}},t[e].call(a.exports,a,a.exports,c),a.loaded=!0),a.exports}c.m=t,u=[],c.O=(e,a,t,r)=>{if(!a){for(var i=1/0,s=0;s<u.length;s++){for(var n,[a,t,r]=u[s],l=!0,o=0;o<a.length;o++)(!1&r||r<=i)&&Object.keys(c.O).every(e=>c.O[e](a[o]))?a.splice(o--,1):(l=!1,r<i&&(i=r));l&&(u.splice(s--,1),void 0!==(n=t()))&&(e=n)}return e}r=r||0;for(var s=u.length;0<s&&u[s-1][2]>r;s--)u[s]=u[s-1];u[s]=[a,t,r]},c.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return c.d(a,{a:a}),a},c.d=(e,a)=>{for(var t in a)c.o(a,t)&&!c.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),c.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),c.j=788,p={788:0},c.O.j=e=>0===p[e],a=(e,a)=>{var t,r,i,[s,n,l]=a,o=0;if(s.some(e=>0!==p[e])){for(t in n)c.o(n,t)&&(c.m[t]=n[t]);l&&(i=l(c))}for(e&&e(a);o<s.length;o++)r=s[o],c.o(p,r)&&p[r]&&p[r][0](),p[r]=0;return c.O(i)},(e=self.webpackChunksapl_frontend=self.webpackChunksapl_frontend||[]).forEach(a.bind(null,0)),e.push=a.bind(null,e.push.bind(e));var a=c.O(void 0,[504],()=>c(8020));c.O(a)})();

BIN
sapl/static/sapl/frontend/js/parlamentar.f0710dca.js.gz

Binary file not shown.

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

Loading…
Cancel
Save