From 69bf3b7d431f6383369dbe132bf3f8f2137253e9 Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Wed, 27 Aug 2025 00:49:28 -0300 Subject: [PATCH] Fix timezone bug --- sapl/crispy_layout_mixin.py | 27 +++++++++++++++---------- sapl/materia/models.py | 11 ++++------- sapl/materia/views.py | 39 ++++++++++++++++++++----------------- sapl/norma/views.py | 13 ++++++------- sapl/protocoloadm/views.py | 36 +++++++++++++++++----------------- sapl/sessao/views.py | 18 ++++++++--------- sapl/utils.py | 10 +++++----- setup.py | 1 - 8 files changed, 80 insertions(+), 75 deletions(-) diff --git a/sapl/crispy_layout_mixin.py b/sapl/crispy_layout_mixin.py index d608b7777..1737d2ced 100644 --- a/sapl/crispy_layout_mixin.py +++ b/sapl/crispy_layout_mixin.py @@ -214,19 +214,26 @@ class CrispyLayoutFormMixin: for fieldname, span in row] def get_form(self, form_class=None): - try: - form = super(CrispyLayoutFormMixin, self).get_form(form_class) - except AttributeError: - # simply return None if there is no get_form on super - pass + # Only handle the “no get_form in MRO” case; let real errors bubble up. + super_get_form = getattr(super(CrispyLayoutFormMixin, self), 'get_form', None) + if super_get_form is None: + # Either raise, or (if you want to support non-form views) construct a form when form_class exists. + if getattr(self, 'form_class', None): + form_class = self.get_form_class() + form = form_class(**self.get_form_kwargs()) + else: + raise NotImplementedError( + f"{self.__class__.__name__} requires get_form() in the MRO or form_class set" + ) else: - if self.layout_key: - form.helper = SaplFormHelper() - layout = self.get_layout() + form = super_get_form(form_class) - form.helper.layout = SaplFormLayout(*layout) + if self.layout_key: + form.helper = SaplFormHelper() + layout = self.get_layout() + form.helper.layout = SaplFormLayout(*layout) - return form + return form @property def list_field_names(self): diff --git a/sapl/materia/models.py b/sapl/materia/models.py index db7be1bc0..a66dba6db 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -339,13 +339,10 @@ class MateriaLegislativa(models.Model): if protocolo.timestamp: return protocolo.timestamp elif protocolo.timestamp_data_hora_manual: - tz = timezone.localtime().tzinfo - return tz.localize( - datetime.combine( - protocolo.data, - protocolo.hora - ) - ) + dt = datetime.combine(protocolo.data, protocolo.hora) + if timezone.is_naive(dt): # when USE_TZ=True this will be True + dt = timezone.make_aware(dt, timezone.get_current_timezone()) + return dt elif protocolo.data: return protocolo.data diff --git a/sapl/materia/views.py b/sapl/materia/views.py index d51f477c4..0f7a71f3b 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -236,8 +236,7 @@ class CriarProtocoloMateriaView(CreateView): materia.user = self.request.user materia.ip = get_client_ip(self.request) - tz = timezone.get_current_timezone() - materia.ultima_edicao = tz.localize(datetime.now()) + materia.ultima_edicao = timezone.now() materia.save() @@ -1067,7 +1066,8 @@ class ProposicaoCrud(Crud): or tipo_texto == 'T' and not objeto_antigo.texto_articulado.exists(): self.object.user = self.request.user self.object.ip = get_client_ip(self.request) - self.object.ultima_edicao = tz.localize(datetime.now()) + from django.utils import timezone + self.object.ultima_edicao = timezone.now() self.object.save() self.object = form.save() @@ -1085,7 +1085,8 @@ class ProposicaoCrud(Crud): if dict_objeto_antigo[atributo] != dict_objeto_novo[atributo]: self.object.user = self.request.user self.object.ip = get_client_ip(self.request) - self.object.ultima_edicao = tz.localize(datetime.now()) + from django.utils import timezone + self.object.ultima_edicao = timezone.now() self.object.save() break @@ -1142,8 +1143,8 @@ class ProposicaoCrud(Crud): initial['user'] = self.request.user initial['ip'] = get_client_ip(self.request) - tz = timezone.get_current_timezone() - initial['ultima_edicao'] = tz.localize(datetime.now()) + from django.utils import timezone + initial['ultima_edicao'] = timezone.now() return initial @@ -1401,8 +1402,8 @@ class TramitacaoCrud(MasterDetailCrud): initial['ip'] = get_client_ip(self.request) initial['user'] = self.request.user - tz = timezone.get_current_timezone() - initial['ultima_edicao'] = tz.localize(datetime.now()) + from django.utils import timezone + initial['ultima_edicao'] = timezone.now() return initial @@ -1454,8 +1455,8 @@ class TramitacaoCrud(MasterDetailCrud): initial['ip'] = get_client_ip(self.request) initial['user'] = self.request.user - tz = timezone.get_current_timezone() - initial['ultima_edicao'] = tz.localize(datetime.now()) + from django.utils import timezone + initial['ultima_edicao'] = timezone.now() return initial @@ -1848,8 +1849,8 @@ class MateriaLegislativaCrud(Crud): initial['user'] = self.request.user initial['ip'] = get_client_ip(self.request) - tz = timezone.get_current_timezone() - initial['ultima_edicao'] = tz.localize(datetime.now()) + from django.utils import timezone + initial['ultima_edicao'] = timezone.now() return initial @@ -1883,8 +1884,8 @@ class MateriaLegislativaCrud(Crud): self.object.user = self.request.user self.object.ip = get_client_ip(self.request) - tz = timezone.get_current_timezone() - self.object.ultima_edicao = tz.localize(datetime.now()) + from django.utils import timezone + self.object.ultima_edicao = timezone.now() self.object.save() break @@ -2354,8 +2355,10 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView): for chunk in request.FILES['arquivo'].chunks(): destination.write(chunk) try: - doc_data = tz.localize(datetime.strptime( - request.POST['data'], "%d/%m/%Y")) + dt = datetime.strptime(request.POST['data'], "%d/%m/%Y") + if timezone.is_naive(dt): + dt = timezone.make_aware(dt, timezone.get_current_timezone()) + return dt except Exception as e: msg = _( 'Formato da data incorreto. O formato deve ser da forma dd/mm/aaaa.') @@ -2577,8 +2580,8 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): user = request.user ip = get_client_ip(request) - tz = timezone.get_current_timezone() - ultima_edicao = tz.localize(datetime.now()) + from django.utils import timezone + ultima_edicao = timezone.now() materias_ids = request.POST.getlist('materias') if not materias_ids: diff --git a/sapl/norma/views.py b/sapl/norma/views.py index b84a324e1..8df5b570c 100644 --- a/sapl/norma/views.py +++ b/sapl/norma/views.py @@ -323,8 +323,8 @@ class NormaCrud(Crud): initial['user'] = self.request.user initial['ip'] = get_client_ip(self.request) - tz = timezone.get_current_timezone() - initial['ultima_edicao'] = tz.localize(datetime.now()) + from django.utils import timezone + initial['ultima_edicao'] = timezone.now() username = self.request.user.username try: @@ -430,9 +430,8 @@ class NormaCrud(Crud): self.object.user = self.request.user self.object.ip = get_client_ip(self.request) - tz = timezone.get_current_timezone() - self.object.ultima_edicao = tz.localize(datetime.now()) - + from django.utils import timezone + self.object.ultima_edicao = timezone.now() self.object.save() break @@ -443,8 +442,8 @@ class NormaCrud(Crud): self.object.user = self.request.user self.object.ip = get_client_ip(self.request) - tz = timezone.get_current_timezone() - self.object.ultima_edicao = tz.localize(datetime.now()) + from django.utils import timezone + self.object.ultima_edicao = timezone.now() self.object.save() diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index 86884eb4d..a42f36dd7 100755 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -437,8 +437,8 @@ class DocumentoAdministrativoCrud(Crud): initial['user'] = self.request.user initial['ip'] = get_client_ip(self.request) - tz = timezone.get_current_timezone() - initial['ultima_edicao'] = tz.localize(datetime.now()) + from django.utils import timezone + initial['ultima_edicao'] = timezone.now() return initial @@ -474,8 +474,8 @@ class DocumentoAdministrativoCrud(Crud): self.object.user = self.request.user self.object.ip = get_client_ip(self.request) - tz = timezone.get_current_timezone() - self.object.ultima_edicao = tz.localize(datetime.now()) + from django.utils import timezone + self.object.ultima_edicao = timezone.now() self.object.save() break @@ -786,8 +786,8 @@ class CriarDocumentoProtocolo(PermissionRequiredMixin, CreateView): doc['user'] = self.request.user doc['ip'] = get_client_ip(self.request) - tz = timezone.get_current_timezone() - doc['ultima_edicao'] = tz.localize(datetime.now()) + from django.utils import timezone + doc['ultima_edicao'] = timezone.now() return doc @@ -982,8 +982,8 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): materia.user = self.request.user materia.ip = get_client_ip(self.request) - tz = timezone.get_current_timezone() - materia.ultima_edicao = tz.localize(datetime.now()) + from django.utils import timezone + materia.ultima_edicao = timezone.now() materia.save() @@ -1327,8 +1327,8 @@ class TramitacaoAdmCrud(MasterDetailCrud): initial['ip'] = get_client_ip(self.request) initial['user'] = self.request.user - tz = timezone.get_current_timezone() - initial['ultima_edicao'] = tz.localize(datetime.now()) + from django.utils import timezone + initial['ultima_edicao'] = timezone.now() return initial @@ -1376,8 +1376,8 @@ class TramitacaoAdmCrud(MasterDetailCrud): initial['ip'] = get_client_ip(self.request) initial['user'] = self.request.user - tz = timezone.get_current_timezone() - initial['ultima_edicao'] = tz.localize(datetime.now()) + from django.utils import timezone + initial['ultima_edicao'] = timezone.now() return initial @@ -1518,8 +1518,8 @@ class DesvincularDocumentoView(PermissionRequiredMixin, CreateView): documento.user = self.request.user documento.ip = get_client_ip(self.request) - tz = timezone.get_current_timezone() - documento.ultima_edicao = tz.localize(datetime.now()) + from django.utils import timezone + documento.ultima_edicao = timezone.now() documento.save() return redirect(self.get_success_url()) @@ -1546,8 +1546,8 @@ class DesvincularMateriaView(PermissionRequiredMixin, FormView): materia.user = self.request.user materia.ip = get_client_ip(self.request) - tz = timezone.get_current_timezone() - materia.ultima_edicao = tz.localize(datetime.now()) + from django.utils import timezone + materia.ultima_edicao = timezone.now() materia.save() return redirect(self.get_success_url()) @@ -1695,8 +1695,8 @@ class PrimeiraTramitacaoEmLoteAdmView(PermissionRequiredMixin, FilterView): user = request.user ip = get_client_ip(request) - tz = timezone.get_current_timezone() - ultima_edicao = tz.localize(datetime.now()) + from django.utils import timezone + ultima_edicao = timezone.now() documentos_ids = request.POST.getlist('documentos') if not documentos_ids: diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index 90df7de0f..bd0b8f2ae 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -26,7 +26,7 @@ from django.views.generic.base import RedirectView from django.views.generic.detail import DetailView from django.views.generic.edit import FormMixin from django_filters.views import FilterView -import pytz + from sapl.base.models import AppConfig as AppsAppConfig from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, @@ -3869,10 +3869,10 @@ class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView): sessao_plenaria = SessaoPlenaria.objects.get(id=self.object.id) data_sessao = sessao_plenaria.data_inicio.strftime("%Y-%m-%d ") - data_hora_sessao = datetime.strptime( - data_sessao + sessao_plenaria.hora_inicio, "%Y-%m-%d %H:%M") - data_hora_sessao_utc = pytz.timezone(TIME_ZONE).localize( - data_hora_sessao).astimezone(pytz.utc) + data_hora_sessao = datetime.strptime(data_sessao + sessao_plenaria.hora_inicio, "%Y-%m-%d %H:%M") + if timezone.is_naive(data_hora_sessao): + data_hora_sessao = timezone.make_aware(data_hora_sessao, timezone.get_current_timezone()) + data_hora_sessao_utc = data_hora_sessao.astimezone(timezone.utc) ultima_tramitacao = m.materia.tramitacao_set.filter(timestamp__lt=data_hora_sessao_utc).order_by( '-data_tramitacao', '-id').first() if m.tramitacao is None else m.tramitacao numeracao = m.materia.numeracao_set.first() @@ -3953,10 +3953,10 @@ class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView): sessao_plenaria = SessaoPlenaria.objects.get(id=self.object.id) data_sessao = sessao_plenaria.data_inicio.strftime("%Y-%m-%d ") - data_hora_sessao = datetime.strptime( - data_sessao + sessao_plenaria.hora_inicio, "%Y-%m-%d %H:%M") - data_hora_sessao_utc = pytz.timezone(TIME_ZONE).localize( - data_hora_sessao).astimezone(pytz.utc) + data_hora_sessao = datetime.strptime(data_sessao + sessao_plenaria.hora_inicio, "%Y-%m-%d %H:%M") + if timezone.is_naive(data_hora_sessao): + data_hora_sessao = timezone.make_aware(data_hora_sessao, timezone.get_current_timezone()) + data_hora_sessao_utc = data_hora_sessao.astimezone(timezone.utc) ultima_tramitacao = o.materia.tramitacao_set.filter(timestamp__lt=data_hora_sessao_utc).order_by( '-data_tramitacao', '-id').first() if o.tramitacao is None else o.tramitacao numeracao = o.materia.numeracao_set.first() diff --git a/sapl/utils.py b/sapl/utils.py index c39d02de8..35dc50515 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -1160,13 +1160,13 @@ def from_date_to_datetime_utc(data): :param data: datetime.date :return: datetime.timestamp com UTC """ - import pytz + from django.utils import timezone from datetime import datetime - # from date to datetime - dt_unware = datetime.combine(data, datetime.min.time()) - dt_utc = pytz.utc.localize(dt_unware) - return dt_utc + dt = datetime.combine(data, datetime.min.time()) + if timezone.is_naive(dt): + dt = timezone.make_aware(dt, timezone.get_current_timezone()) + return dt.astimezone(timezone.utc) class OverwriteStorage(FileSystemStorage): diff --git a/setup.py b/setup.py index 401ea19d1..f83642d2b 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,6 @@ install_requires = [ 'python-decouple==3.1', 'psycopg2-binary==2.7.6.1', 'pyyaml==5.4', - 'pytz==2018.9', 'python-magic==0.4.15', 'unipath==1.1', 'WeasyPrint==44',