From 69efd858b2b8ee523756b992d231b7aab86ff6dd Mon Sep 17 00:00:00 2001 From: root Date: Mon, 11 Aug 2025 10:22:51 -0300 Subject: [PATCH 1/8] =?UTF-8?q?Seleciona=20tipo=20de=20vota=C3=A7=C3=A3o?= =?UTF-8?q?=20para=20m=C3=BAltiplas=20mat=C3=A9rias=20ao=20incluir=20na=20?= =?UTF-8?q?Ordem=20do=20Dia=20ou=20Expediente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adicionar_varias_materias_expediente.html | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/sapl/templates/sessao/adicionar_varias_materias_expediente.html b/sapl/templates/sessao/adicionar_varias_materias_expediente.html index 76cf600b4..b7812a18e 100644 --- a/sapl/templates/sessao/adicionar_varias_materias_expediente.html +++ b/sapl/templates/sessao/adicionar_varias_materias_expediente.html @@ -32,6 +32,23 @@ + + + + {% if paginator.count > 1 %}

{% blocktrans with paginator.count as total_materias %}Pesquisa concluída com sucesso! Foram encontradas {{ total_materias }} matérias.{% endblocktrans %}

@@ -126,5 +143,22 @@ $(window).on('beforeunload', function() { $("input[type=submit], input[type=button]").prop("disabled", "disabled") }); + function checkAll(elem) { + let checkboxes = document.getElementsByName('materia_id'); + for (let i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].type == 'checkbox') + checkboxes[i].checked = elem.checked; + } + } + + function marcaTipoVotacao() { + materias = document.getElementsByName('materia_id'); + for (var i=0; i {% endblock extra_js %} From feeaeae15e9335693b9b1390762c81bd58fb556c Mon Sep 17 00:00:00 2001 From: cristian-longhi Date: Wed, 3 Sep 2025 13:50:48 -0300 Subject: [PATCH 2/8] =?UTF-8?q?Ajustes=20solicitados=20na=20sele=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20m=C3=BAltiplas=20Mat=C3=A9rias=20para=20Ordem/Ex?= =?UTF-8?q?pediente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/sessao/forms.py | 19 +++++++++++++++- sapl/sessao/views.py | 4 +++- .../adicionar_varias_materias_expediente.html | 22 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index 73ac911f0..c78bbd1a5 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -22,7 +22,8 @@ from sapl.materia.models import (MateriaLegislativa, StatusTramitacao, from sapl.parlamentares.models import Mandato, Parlamentar from sapl.protocoloadm.models import TipoDocumentoAdministrativo,\ DocumentoAdministrativo -from sapl.sessao.models import Correspondencia +from sapl.sessao.models import Correspondencia, AbstractOrdemDia + from sapl.utils import (autor_label, autor_modal, choice_anos_com_sessaoplenaria, FileFieldCheckMixin, @@ -563,6 +564,22 @@ class SessaoPlenariaFilterSet(django_filters.FilterSet): ) +class AdicionarVariasMateriasForm(forms.Form): + todos = forms.BooleanField( + label='Marcar/Desmarcar Todos', + required=False, + widget=forms.CheckboxInput( + attrs={'onchange':'checkAll(this)'}), + ) + + tipo_votacao = forms.ChoiceField(required=False, + choices= AbstractOrdemDia.TIPO_VOTACAO_CHOICES, + initial=False, + widget=forms.RadioSelect( + attrs={'onchange':'marcaTipoVotacao()'}), + ) + + class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet): o = MateriaPesquisaOrderingFilter() diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 4835a17b6..7d8ec4bd1 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -48,7 +48,7 @@ from sapl.settings import TIME_ZONE from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip,\ MultiFormatOutputMixin, PautaMultiFormatOutputMixin -from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, +from .forms import (AdicionarVariasMateriasFilterSet, AdicionarVariasMateriasForm, BancadaForm, ExpedienteForm, JustificativaAusenciaForm, OcorrenciaSessaoForm, ListMateriaForm, MesaForm, OradorExpedienteForm, OradorForm, PautaSessaoFilterSet, PresencaForm, ResumoOrdenacaoForm, SessaoPlenariaFilterSet, @@ -4174,6 +4174,8 @@ class AdicionarVariasMateriasExpediente(PermissionRequiredForAppCrudMixin, qr = self.request.GET.copy() + form = AdicionarVariasMateriasForm + context['form'] = form context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['pk_sessao'] = self.kwargs['pk'] diff --git a/sapl/templates/sessao/adicionar_varias_materias_expediente.html b/sapl/templates/sessao/adicionar_varias_materias_expediente.html index 76cf600b4..4fd066709 100644 --- a/sapl/templates/sessao/adicionar_varias_materias_expediente.html +++ b/sapl/templates/sessao/adicionar_varias_materias_expediente.html @@ -38,6 +38,16 @@ {% elif paginator.count == 1 %}

{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 matéria.'%}

{% endif %} +
+ + + {% for m in page_obj %}

{% trans "Matérias" %}

{% trans "Tipo de Votação" %}

+ + + +
+ +
+ +
+ +
+
+ {{ form.todos }} {{ form.todos.label }} + + {% for tipo in form.tipo_votacao %} + {{ tipo }}
+ {% endfor %} +
@@ -126,5 +136,17 @@ $(window).on('beforeunload', function() { $("input[type=submit], input[type=button]").prop("disabled", "disabled") }); + + function checkAll(elem) { + $('input[name="materia_id"]:checkbox').prop('checked', elem.checked); + } + + function marcaTipoVotacao() { + $('input[name="materia_id"]:checked').each(function() { + var tipoVotacao = $('input[name="tipo_votacao"]:checked').val(); + var idMateria = "#tipo_votacao_" + $(this).val() + "_" + tipoVotacao; + $(idMateria).prop('checked', true); + }); + } {% endblock extra_js %} From abbfa48af9dccae057a77a16b2766a63aa7813b4 Mon Sep 17 00:00:00 2001 From: cristian-longhi Date: Thu, 4 Sep 2025 11:52:51 -0300 Subject: [PATCH 3/8] Update adicionar_varias_materias_expediente.html --- .../adicionar_varias_materias_expediente.html | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/sapl/templates/sessao/adicionar_varias_materias_expediente.html b/sapl/templates/sessao/adicionar_varias_materias_expediente.html index 5c2bc7c18..4fd066709 100644 --- a/sapl/templates/sessao/adicionar_varias_materias_expediente.html +++ b/sapl/templates/sessao/adicionar_varias_materias_expediente.html @@ -32,23 +32,6 @@ - - - - {% if paginator.count > 1 %}

{% blocktrans with paginator.count as total_materias %}Pesquisa concluída com sucesso! Foram encontradas {{ total_materias }} matérias.{% endblocktrans %}

From e5a8a851bd9e26497f4af4ecdc52763ef50ddb25 Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Wed, 10 Sep 2025 16:34:51 -0300 Subject: [PATCH 4/8] Fix read-only mount on k8s --- docker/Dockerfile | 2 +- docker/docker-compose.yaml | 10 +++++----- docker/startup_scripts/start.sh | 21 +++++++++++++++------ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9fe3d6b75..831627ec8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -62,7 +62,7 @@ RUN set -eux; \ # Usuários/grupos (idempotente) RUN useradd --system --no-create-home --shell /usr/sbin/nologin sapl || true \ - && groupadd -r nginx || true \ + && groupadd -g 101 -r nginx || true \ && usermod -aG nginx www-data || true \ && usermod -aG nginx sapl || true diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index e84924050..21eb5915f 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -33,11 +33,11 @@ services: networks: - sapl-net sapl: - image: interlegis/sapl:3.1.164-RC2 -# build: -# context: ../ -# dockerfile: ./docker/Dockerfile -# container_name: sapl +# image: eribeiro/sapl:debug-k8s + build: + context: ../ + dockerfile: ./docker/Dockerfile + container_name: sapl labels: NAME: "sapl" restart: always diff --git a/docker/startup_scripts/start.sh b/docker/startup_scripts/start.sh index b612532bc..69f1333f4 100755 --- a/docker/startup_scripts/start.sh +++ b/docker/startup_scripts/start.sh @@ -2,12 +2,24 @@ set -Eeuo pipefail IFS=$'\n\t' +APP_DIR="/var/interlegis/sapl" DATA_DIR="/var/interlegis/sapl/data" -APP_DIR="/var/interlegis/sapl/sapl" +MEDIA_DIR="/var/interlegis/sapl/media" +RUN_DIR="/var/interlegis/sapl/run" +GUNICORN_DIR="/run/gunicorn" + ENV_FILE="$APP_DIR/.env" SECRET_FILE="$DATA_DIR/secret.key" -mkdir -p "$DATA_DIR" "$APP_DIR" +chown -R root:nginx "$RUN_DIR" || true +chown -R root:nginx "$MEDIA_DIR" || true +chown -R root:nginx "$GUNICORN_DIR" || true +chmod -R g+rwX "$RUN_DIR" || true +chmod -R g+rwX "$MEDIA_DIR" || true +chmod -R g+rwX "$GUNICORN_DIR" || true + +# setgid bit on our writable trees (not data/) +find "$RUN_DIR" "$MEDIA_DIR" -type d -exec chmod g+s {} + 2>/dev/null || true log() { printf '[%s] %s\n' "$(date -Is)" "$*"; } err() { printf '[%s] ERROR: %s\n' "$(date -Is)" "$*" >&2; } @@ -76,7 +88,6 @@ create_secret() { SECRET_KEY="$(python3 genkey.py)" umask 177 printf '%s\n' "$SECRET_KEY" > "$SECRET_FILE" - chmod 600 "$SECRET_FILE" fi export SECRET_KEY } @@ -225,9 +236,7 @@ fix_logging_and_socket_perms() { # dirs mkdir -p "$APP_DIR/run" - chown -R root:nginx "$APP_DIR" - chmod 2775 "$APP_DIR" "$APP_DIR/run" - chmod -R g+rwX "$APP_DIR" + chmod 2775 "$APP_DIR/run" # new files/sockets → 660 umask 0007 From 3c0638b02e886b7b5bd63e2681a282276ae94018 Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Wed, 17 Sep 2025 20:06:57 -0300 Subject: [PATCH 5/8] =?UTF-8?q?Fix=20recibo=20proposi=C3=A7=C3=A3o=20e=20a?= =?UTF-8?q?diciona=20rate=20limiter=20em=20mat=C3=A9ria=20e=20norma?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/config/nginx/sapl.conf | 2 +- docker/startup_scripts/gunicorn.conf.py | 3 ++- requirements/requirements.txt | 3 +-- sapl/base/views.py | 2 +- sapl/materia/views.py | 6 +++++- sapl/norma/views.py | 5 +++++ sapl/utils.py | 28 ++++++++++++++----------- 7 files changed, 31 insertions(+), 18 deletions(-) diff --git a/docker/config/nginx/sapl.conf b/docker/config/nginx/sapl.conf index 18641045e..a55731d02 100644 --- a/docker/config/nginx/sapl.conf +++ b/docker/config/nginx/sapl.conf @@ -7,7 +7,7 @@ upstream sapl_server { server { listen 80; - server_name sapl.test; + server_name sapl.prod; client_max_body_size 4G; diff --git a/docker/startup_scripts/gunicorn.conf.py b/docker/startup_scripts/gunicorn.conf.py index 95d2f0256..6bdcacb02 100644 --- a/docker/startup_scripts/gunicorn.conf.py +++ b/docker/startup_scripts/gunicorn.conf.py @@ -52,7 +52,8 @@ graceful_timeout = 30 keepalive = 10 backlog = 2048 max_requests = MAX_REQUESTS -max_requests_jitter = 100 +max_requests_jitter = 200 +worker_max_memory_per_child = 300 * 1024 * 1024 # 300 MB cap # Environment (same as exporting before running) raw_env = [ diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 56c2459b4..aea9f52db 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -22,8 +22,7 @@ pytz==2019.3 python-magic==0.4.15 unipath==1.1 Pillow==10.3.0 -rlPyCairo==0.3.0 -reportlab==4.2.0 +reportlab==3.6.13 WeasyPrint==66 trml2pdf==0.6 gunicorn==23.0.0 diff --git a/sapl/base/views.py b/sapl/base/views.py index ce3a0a717..7c935d421 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -68,7 +68,7 @@ class IndexView(TemplateView): @method_decorator(ratelimit(key=lambda group, request: get_client_ip(request), - rate='20/m', + rate='10/m', method=ratelimit.UNSAFE, block=True), name='dispatch') class LoginSapl(views.LoginView): diff --git a/sapl/materia/views.py b/sapl/materia/views.py index f64d8134b..5a05149da 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -24,7 +24,6 @@ from django.shortcuts import render from django.template import loader from django.urls import reverse from django.utils import formats, timezone -from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic.base import RedirectView @@ -32,6 +31,9 @@ from django.views.generic.edit import FormView from django_filters.views import FilterView import weasyprint +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator + import sapl from sapl.base.email_utils import do_envia_email_confirmacao from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig @@ -1908,6 +1910,7 @@ class MateriaLegislativaCrud(Crud): def get_success_url(self): return self.search_url + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class DetailView(Crud.DetailView): layout_key = 'MateriaLegislativaDetail' @@ -1920,6 +1923,7 @@ class MateriaLegislativaCrud(Crud): pk=self.kwargs['pk']) return context + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class ListView(Crud.ListView, RedirectView): def get_redirect_url(self, *args, **kwargs): diff --git a/sapl/norma/views.py b/sapl/norma/views.py index 27758a1ca..bcd059805 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -19,6 +19,9 @@ from django.views.generic.edit import FormView from django_filters.views import FilterView import weasyprint +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator + from sapl import settings import sapl from sapl.base.models import AppConfig @@ -280,6 +283,7 @@ class NormaCrud(Crud): namespace = self.model._meta.app_config.name return reverse('%s:%s' % (namespace, 'norma_pesquisa')) + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class DetailView(Crud.DetailView): def get(self, request, *args, **kwargs): estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas @@ -337,6 +341,7 @@ class NormaCrud(Crud): layout_key = 'NormaJuridicaCreate' + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class ListView(Crud.ListView): def get(self, request, *args, **kwargs): diff --git a/sapl/utils.py b/sapl/utils.py index 9d6399c5e..6554c5aea 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -419,21 +419,25 @@ def get_base_url(request): return "{0}://{1}".format(protocol, current_domain) -def create_barcode(value, width=170, height=50): - ''' - creates a base64 encoded barcode PNG image - ''' +def create_barcode(value, width=170, height=50, dpi=72): + """ + creates a base64 encoded barcode PNG image + """ from base64 import b64encode from reportlab.graphics.barcode import createBarcodeDrawing + value_bytes = bytes(value, "ascii") - barcode = createBarcodeDrawing('Code128', - value=value_bytes, - barWidth=width, - height=height, - fontSize=2, - humanReadable=True) - data = b64encode(barcode.asString('png')) - return data.decode('utf-8') + barcode = createBarcodeDrawing( + 'Code128', + value=value_bytes, + barWidth=width, + height=height, + fontSize=2, + humanReadable=True + ) + # Lower DPI prevents Cairo surface from blowing up + png_bytes = barcode.asString("png", dpi=dpi) + return b64encode(png_bytes).decode("utf-8") YES_NO_CHOICES = [(True, _('Sim')), (False, _('Não'))] From 4af561f1e49fece82faa4bafff6720ad91043721 Mon Sep 17 00:00:00 2001 From: cristian-longhi Date: Thu, 18 Sep 2025 09:00:38 -0300 Subject: [PATCH 6/8] Update forms.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alteração do nome do campo todos, conforme solicitação. --- sapl/sessao/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index c78bbd1a5..491cdeaca 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -565,7 +565,7 @@ class SessaoPlenariaFilterSet(django_filters.FilterSet): class AdicionarVariasMateriasForm(forms.Form): - todos = forms.BooleanField( + check_all = forms.BooleanField( label='Marcar/Desmarcar Todos', required=False, widget=forms.CheckboxInput( From 77e103407a97da93ac1da5b4075e335013fc92ee Mon Sep 17 00:00:00 2001 From: cristian-longhi Date: Thu, 18 Sep 2025 09:01:37 -0300 Subject: [PATCH 7/8] Update adicionar_varias_materias_expediente.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajuste no template por alteração do nome do campo "todos". --- sapl/templates/sessao/adicionar_varias_materias_expediente.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sapl/templates/sessao/adicionar_varias_materias_expediente.html b/sapl/templates/sessao/adicionar_varias_materias_expediente.html index 4fd066709..876247216 100644 --- a/sapl/templates/sessao/adicionar_varias_materias_expediente.html +++ b/sapl/templates/sessao/adicionar_varias_materias_expediente.html @@ -40,7 +40,7 @@ {% endif %}

{% trans "Matérias" %}

{% trans "Tipo de Votação" %}

- - - -
- -
- -
- -
-
- {{ form.todos }} {{ form.todos.label }} + {{ form.check_all }} {{ form.check_all.label }} {% for tipo in form.tipo_votacao %} From f92c461e589eca9b70e4148ce94b9f984c6031fa Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Wed, 17 Sep 2025 20:06:57 -0300 Subject: [PATCH 8/8] =?UTF-8?q?Fix=20recibo=20proposi=C3=A7=C3=A3o=20e=20a?= =?UTF-8?q?diciona=20rate=20limiter=20em=20mat=C3=A9ria=20e=20norma?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 7 +++++++ docker/config/nginx/sapl.conf | 2 +- docker/docker-compose.yaml | 10 ++++----- docker/startup_scripts/gunicorn.conf.py | 3 ++- docker/startup_scripts/start.sh | 5 +---- requirements/requirements.txt | 3 +-- sapl/base/views.py | 2 +- sapl/materia/views.py | 9 +++++++- sapl/norma/views.py | 7 +++++++ sapl/sessao/views.py | 5 +++++ sapl/utils.py | 28 ++++++++++++++----------- scripts/gunicorn_start.sh | 4 ++-- 12 files changed, 56 insertions(+), 29 deletions(-) diff --git a/.dockerignore b/.dockerignore index 318700ad6..4ff7fdc82 100644 --- a/.dockerignore +++ b/.dockerignore @@ -12,3 +12,10 @@ bower .travis.yml .env .idea +.DS_Store +.coveragerc +*.swp +.coveragerc +.drone.yml +.github +release.sh diff --git a/docker/config/nginx/sapl.conf b/docker/config/nginx/sapl.conf index 18641045e..a55731d02 100644 --- a/docker/config/nginx/sapl.conf +++ b/docker/config/nginx/sapl.conf @@ -7,7 +7,7 @@ upstream sapl_server { server { listen 80; - server_name sapl.test; + server_name sapl.prod; client_max_body_size 4G; diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 20c75374a..9de3d4612 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -33,11 +33,11 @@ services: networks: - sapl-net sapl: - image: interlegis/sapl:3.1.164-RC3 -# build: -# context: ../ -# dockerfile: ./docker/Dockerfile -# container_name: sapl +# image: interlegis/sapl:3.1.164-RC3 + build: + context: ../ + dockerfile: ./docker/Dockerfile + container_name: sapl labels: NAME: "sapl" restart: always diff --git a/docker/startup_scripts/gunicorn.conf.py b/docker/startup_scripts/gunicorn.conf.py index 95d2f0256..6bdcacb02 100644 --- a/docker/startup_scripts/gunicorn.conf.py +++ b/docker/startup_scripts/gunicorn.conf.py @@ -52,7 +52,8 @@ graceful_timeout = 30 keepalive = 10 backlog = 2048 max_requests = MAX_REQUESTS -max_requests_jitter = 100 +max_requests_jitter = 200 +worker_max_memory_per_child = 300 * 1024 * 1024 # 300 MB cap # Environment (same as exporting before running) raw_env = [ diff --git a/docker/startup_scripts/start.sh b/docker/startup_scripts/start.sh index 316d73f8a..bd98bdfc0 100755 --- a/docker/startup_scripts/start.sh +++ b/docker/startup_scripts/start.sh @@ -4,19 +4,16 @@ IFS=$'\n\t' APP_DIR="/var/interlegis/sapl" DATA_DIR="/var/interlegis/sapl/data" -MEDIA_DIR="/var/interlegis/sapl/media" RUN_DIR="/var/interlegis/sapl/run" ENV_FILE="$APP_DIR/.env" SECRET_FILE="$DATA_DIR/secret.key" chown -R root:nginx "$RUN_DIR" || true -chown -R root:nginx "$MEDIA_DIR" || true chmod -R g+rwX "$RUN_DIR" || true -chmod -R g+rwX "$MEDIA_DIR" || true # setgid bit on our writable trees (not data/) -find "$RUN_DIR" "$MEDIA_DIR" -type d -exec chmod g+s {} + 2>/dev/null || true +find "$RUN_DIR" -type d -exec chmod g+s {} + 2>/dev/null || true log() { printf '[%s] %s\n' "$(date -Is)" "$*"; } err() { printf '[%s] ERROR: %s\n' "$(date -Is)" "$*" >&2; } diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 56c2459b4..aea9f52db 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -22,8 +22,7 @@ pytz==2019.3 python-magic==0.4.15 unipath==1.1 Pillow==10.3.0 -rlPyCairo==0.3.0 -reportlab==4.2.0 +reportlab==3.6.13 WeasyPrint==66 trml2pdf==0.6 gunicorn==23.0.0 diff --git a/sapl/base/views.py b/sapl/base/views.py index ce3a0a717..7c935d421 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -68,7 +68,7 @@ class IndexView(TemplateView): @method_decorator(ratelimit(key=lambda group, request: get_client_ip(request), - rate='20/m', + rate='10/m', method=ratelimit.UNSAFE, block=True), name='dispatch') class LoginSapl(views.LoginView): diff --git a/sapl/materia/views.py b/sapl/materia/views.py index f64d8134b..ce158228b 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -24,7 +24,6 @@ from django.shortcuts import render from django.template import loader from django.urls import reverse from django.utils import formats, timezone -from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic.base import RedirectView @@ -32,6 +31,9 @@ from django.views.generic.edit import FormView from django_filters.views import FilterView import weasyprint +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator + import sapl from sapl.base.email_utils import do_envia_email_confirmacao from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig @@ -1459,6 +1461,7 @@ class TramitacaoCrud(MasterDetailCrud): return initial + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class ListView(MasterDetailCrud.ListView): def get_queryset(self): @@ -1531,6 +1534,7 @@ class TramitacaoCrud(MasterDetailCrud): return HttpResponseRedirect(url) + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class DetailView(MasterDetailCrud.DetailView): template_name = "materia/tramitacao_detail.html" @@ -1908,6 +1912,7 @@ class MateriaLegislativaCrud(Crud): def get_success_url(self): return self.search_url + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class DetailView(Crud.DetailView): layout_key = 'MateriaLegislativaDetail' @@ -1920,6 +1925,7 @@ class MateriaLegislativaCrud(Crud): pk=self.kwargs['pk']) return context + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class ListView(Crud.ListView, RedirectView): def get_redirect_url(self, *args, **kwargs): @@ -2040,6 +2046,7 @@ class AcompanhamentoExcluirView(TemplateView): return HttpResponseRedirect(self.get_success_url()) +@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class MateriaLegislativaPesquisaView(MultiFormatOutputMixin, FilterView): model = MateriaLegislativa filterset_class = MateriaLegislativaFilterSet diff --git a/sapl/norma/views.py b/sapl/norma/views.py index 27758a1ca..488e73c90 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -19,6 +19,9 @@ from django.views.generic.edit import FormView from django_filters.views import FilterView import weasyprint +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator + from sapl import settings import sapl from sapl.base.models import AppConfig @@ -147,6 +150,7 @@ class NormaRelacionadaCrud(MasterDetailCrud): layout_key = 'NormaRelacionadaDetail' +@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class NormaPesquisaView(MultiFormatOutputMixin, FilterView): model = NormaJuridica filterset_class = NormaFilterSet @@ -232,6 +236,7 @@ class AnexoNormaJuridicaCrud(MasterDetailCrud): initial['ano'] = self.object.ano return initial + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class DetailView(MasterDetailCrud.DetailView): form_class = AnexoNormaJuridicaForm layout_key = 'AnexoNormaJuridica' @@ -280,6 +285,7 @@ class NormaCrud(Crud): namespace = self.model._meta.app_config.name return reverse('%s:%s' % (namespace, 'norma_pesquisa')) + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class DetailView(Crud.DetailView): def get(self, request, *args, **kwargs): estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas @@ -337,6 +343,7 @@ class NormaCrud(Crud): layout_key = 'NormaJuridicaCreate' + @method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class ListView(Crud.ListView): def get(self, request, *args, **kwargs): diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 4835a17b6..7df367379 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -28,6 +28,9 @@ from django.views.generic.edit import FormMixin from django_filters.views import FilterView import pytz +from ratelimit.decorators import ratelimit +from django.utils.decorators import method_decorator + from sapl.base.models import AppConfig as AppsAppConfig from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud, @@ -3794,6 +3797,7 @@ class SessaoListView(ListView): return context +@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class PautaSessaoView(TemplateView): model = SessaoPlenaria template_name = "sessao/pauta_inexistente.html" @@ -3809,6 +3813,7 @@ class PautaSessaoView(TemplateView): reverse('sapl.sessao:pauta_sessao_detail', kwargs={'pk': sessao.pk})) +@method_decorator(ratelimit(key='ip', rate='10/m', block=True), name='dispatch') class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView): template_name = "sessao/pauta_sessao_detail.html" model = SessaoPlenaria diff --git a/sapl/utils.py b/sapl/utils.py index 9d6399c5e..6554c5aea 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -419,21 +419,25 @@ def get_base_url(request): return "{0}://{1}".format(protocol, current_domain) -def create_barcode(value, width=170, height=50): - ''' - creates a base64 encoded barcode PNG image - ''' +def create_barcode(value, width=170, height=50, dpi=72): + """ + creates a base64 encoded barcode PNG image + """ from base64 import b64encode from reportlab.graphics.barcode import createBarcodeDrawing + value_bytes = bytes(value, "ascii") - barcode = createBarcodeDrawing('Code128', - value=value_bytes, - barWidth=width, - height=height, - fontSize=2, - humanReadable=True) - data = b64encode(barcode.asString('png')) - return data.decode('utf-8') + barcode = createBarcodeDrawing( + 'Code128', + value=value_bytes, + barWidth=width, + height=height, + fontSize=2, + humanReadable=True + ) + # Lower DPI prevents Cairo surface from blowing up + png_bytes = barcode.asString("png", dpi=dpi) + return b64encode(png_bytes).decode("utf-8") YES_NO_CHOICES = [(True, _('Sim')), (False, _('Não'))] diff --git a/scripts/gunicorn_start.sh b/scripts/gunicorn_start.sh index d01503235..bd2932985 100755 --- a/scripts/gunicorn_start.sh +++ b/scripts/gunicorn_start.sh @@ -15,8 +15,8 @@ then fi NAME="SAPL" # Name of the application (*) -DJANGODIR=$SAPL_DIR/ # Django project directory (*) -SOCKFILE=$SAPL_DIR/run/gunicorn.sock # we will communicate using this unix socket (*) +DJANGODIR="$SAPL_DIR/" # Django project directory (*) +SOCKFILE="$SAPL_DIR/run/gunicorn.sock" # we will communicate using this unix socket (*) USER=`whoami` # the user to run as (*) GROUP=`whoami` # the group to run as (*) NUM_WORKERS=3 # how many worker processes should Gunicorn spawn (*)