From 27c7b298fff9ee53e225c76e47b515af3b77cbe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moraes?= Date: Tue, 23 Jun 2026 19:15:34 -0300 Subject: [PATCH 1/9] =?UTF-8?q?fix(sessao):=20ordena=20vota=C3=A7=C3=B5es?= =?UTF-8?q?=20nominais=20do=20resumo=20por=20numero=5Fordem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O resumo da Sessão Plenária (HTML e PDF) ordenava as votações nominais por order_by('-materia'), divergindo da ordem das Matérias da Ordem do Dia, que seguem numero_ordem. Passa a ordenar por numero_ordem nas listas de Expediente e de Ordem do Dia, tanto no resumo HTML quanto no gerador de PDF. --- sapl/relatorios/views.py | 4 ++-- sapl/sessao/views.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index bc28b3ffc..bd2069f69 100755 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -709,7 +709,7 @@ def get_sessao_plenaria(sessao, casa, user): lst_expediente_materia_vot_nom = [] materias_expediente_votacao_nominal = ExpedienteMateria.objects.filter(sessao_plenaria=sessao, tipo_votacao=2) \ - .order_by('-materia') + .order_by('numero_ordem') for mevn in materias_expediente_votacao_nominal: votos_materia = [] @@ -822,7 +822,7 @@ def get_sessao_plenaria(sessao, casa, user): lst_votacao_vot_nom = [] materias_ordem_dia_votacao_nominal = OrdemDia.objects.filter(sessao_plenaria=sessao, tipo_votacao=2) \ - .order_by('-materia') + .order_by('numero_ordem') for modvn in materias_ordem_dia_votacao_nominal: votos_materia_od = [] diff --git a/sapl/sessao/views.py b/sapl/sessao/views.py index ab30c2c6a..0eca8c3e5 100755 --- a/sapl/sessao/views.py +++ b/sapl/sessao/views.py @@ -2334,7 +2334,7 @@ class ResumoView(DetailView): # Votos de Votação Nominal de Matérias Expediente votacoes = [] for mevn in ExpedienteMateria.objects.filter(sessao_plenaria_id=self.object.id, tipo_votacao=2) \ - .order_by('-materia'): + .order_by('numero_ordem'): votos_materia = [] titulo_materia = mevn.materia registro = RegistroVotacao.objects.filter(expediente=mevn) @@ -2383,7 +2383,7 @@ class ResumoView(DetailView): # Matérias Ordem do Dia # Votos de Votação Nominal de Matérias Ordem do Dia votacoes_od = [] - for modvn in OrdemDia.objects.filter(sessao_plenaria_id=self.object.id, tipo_votacao=2).order_by('-materia'): + for modvn in OrdemDia.objects.filter(sessao_plenaria_id=self.object.id, tipo_votacao=2).order_by('numero_ordem'): votos_materia_od = [] t_materia = modvn.materia registro_od = RegistroVotacao.objects.filter(ordem=modvn) From 3893df94185fbddc4f37da09e0f1eba11d2fc47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moraes?= Date: Tue, 23 Jun 2026 19:19:56 -0300 Subject: [PATCH 2/9] =?UTF-8?q?fix(relatorios):=20lista=20todos=20os=20aut?= =?UTF-8?q?ores=20no=20relat=C3=B3rio=20de=20tramita=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O relatório "Imprimir Tramitação" exibia apenas o primeiro autor (autores.first()). Passa a exibir todos os autores/co-autores da matéria, ajustando a view (autores.all()) e o template (rótulo Autor/Autores e junção dos nomes). --- sapl/relatorios/views.py | 2 +- sapl/templates/relatorios/relatorio_materia_tramitacao.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index bd2069f69..1fd2c5a0c 100755 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -1790,7 +1790,7 @@ def relatorio_materia_tramitacao(request, pk): 'materia': materia_legislativa, 'ano': materia_legislativa.ano, 'numero': materia_legislativa.numero, - 'autor': materia_legislativa.autores.first(), + 'autores': materia_legislativa.autores.all(), 'tipo': materia_legislativa.tipo.descricao, 'rodape': rodape, 'data': dt.today().strftime('%d/%m/%Y'), diff --git a/sapl/templates/relatorios/relatorio_materia_tramitacao.html b/sapl/templates/relatorios/relatorio_materia_tramitacao.html index ddfe6e38b..a75a7344b 100644 --- a/sapl/templates/relatorios/relatorio_materia_tramitacao.html +++ b/sapl/templates/relatorios/relatorio_materia_tramitacao.html @@ -9,7 +9,7 @@

Histórico de Tramitações da Matéria: {{ numero }}/{{ ano }}

Tipo de matéria: {{ tipo }}
- Autor: {{ autor }}
+ {% if autores|length > 1 %}Autores: {% else %}Autor: {% endif %}{{ autores|join:", " }}
{% for tramitacao in object%} From 89f381c59f748e0a0e505602cc999d9902a04b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moraes?= Date: Tue, 23 Jun 2026 19:25:20 -0300 Subject: [PATCH 3/9] feat(uploads): aceita planilhas e CSV em documentos de texto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Amplia TIPOS_TEXTO_PERMITIDOS (validador restringe_tipos_de_arquivo_txt), usado nos campos de Pauta/Ata/Anexo, para aceitar formatos editáveis de planilha e CSV: .csv, .xls, .xlsx e .ods (e variantes de MIME). Não requer migração, pois o validador é referenciado nas migrations apenas pelo nome. --- sapl/utils.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sapl/utils.py b/sapl/utils.py index ee97094aa..1480150e9 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -620,6 +620,15 @@ TIPOS_TEXTO_PERMITIDOS = ( 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/xml', 'text/xml', + # Formatos editáveis de planilha/CSV (atende à gravação de relatórios do PNTP). + 'text/csv', + 'application/csv', + 'text/comma-separated-values', + 'application/vnd.ms-excel', # .xls + 'application/x-ole-storage', # .xls (detecção OLE2 legada do libmagic) + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', # .xlsx + 'application/vnd.oasis.opendocument.spreadsheet', # .ods / calc + 'application/x-vnd.oasis.opendocument.spreadsheet', ) TIPOS_IMG_PERMITIDOS = ( From 5e66298f7d10a9e554bd1ff7f3ab85171e46919a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moraes?= Date: Tue, 23 Jun 2026 19:30:06 -0300 Subject: [PATCH 4/9] =?UTF-8?q?feat(comissoes):=20filtro=20de=20pesquisa?= =?UTF-8?q?=20e=20coluna=20de=20pauta=20na=20lista=20de=20reuni=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adiciona ReuniaoFilterSet (número, nome, tema e período de data) à listagem de Reuniões de Comissão, permitindo localizar reuniões sem navegar página a página. Inclui a coluna 'Pauta da Reunião' ao lado de 'Ata da Reunião'. --- sapl/comissoes/forms.py | 25 +++++++++++++++++++++++++ sapl/comissoes/views.py | 11 +++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/sapl/comissoes/forms.py b/sapl/comissoes/forms.py index 76f511ed2..599b9a5b3 100644 --- a/sapl/comissoes/forms.py +++ b/sapl/comissoes/forms.py @@ -558,3 +558,28 @@ class DocumentoAcessorioEditForm(FileFieldCheckMixin, forms.ModelForm): # raise ValidationError(f'Favor anexar arquivo em {nome_arquivo}') return self.cleaned_data + + +class ReuniaoFilterSet(django_filters.FilterSet): + + nome = django_filters.CharFilter( + lookup_expr='icontains', label=_('Nome da Reunião')) + tema = django_filters.CharFilter( + lookup_expr='icontains', label=_('Tema da Reunião')) + + class Meta(FilterOverridesMetaMixin): + model = Reuniao + fields = ['numero', 'nome', 'tema', 'data'] + + def __init__(self, *args, **kwargs): + super(ReuniaoFilterSet, self).__init__(*args, **kwargs) + + row1 = to_row([('numero', 3), ('nome', 5), ('tema', 4)]) + row2 = to_row([('data', 6)]) + + self.form.helper = SaplFormHelper() + self.form.helper.form_method = 'GET' + self.form.helper.layout = Layout( + Fieldset(_('Pesquisar Reunião'), + row1, row2, + form_actions(label=_('Pesquisar')))) diff --git a/sapl/comissoes/views.py b/sapl/comissoes/views.py index 6017ae2b3..92cde447f 100644 --- a/sapl/comissoes/views.py +++ b/sapl/comissoes/views.py @@ -22,7 +22,7 @@ from sapl.comissoes.forms import (ComissaoForm, ComposicaoForm, ParticipacaoCreateForm, ParticipacaoEditForm, PautaReuniaoFilterSet, PautaReuniaoForm, - PeriodoForm, ReuniaoForm) + PeriodoForm, ReuniaoFilterSet, ReuniaoForm) from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud, PermissionRequiredForAppCrudMixin, RP_DETAIL, RP_LIST) @@ -216,7 +216,7 @@ class ReuniaoCrud(MasterDetailCrud): public = [RP_LIST, RP_DETAIL, ] class BaseMixin(MasterDetailCrud.BaseMixin): - list_field_names = ['data', 'nome', 'tema', 'upload_ata'] + list_field_names = ['data', 'nome', 'tema', 'upload_pauta', 'upload_ata'] class DetailView(MasterDetailCrud.DetailView): template_name = "comissoes/reuniao_detail.html" @@ -248,6 +248,11 @@ class ReuniaoCrud(MasterDetailCrud): logger = logging.getLogger(__name__) paginate_by = 10 + def get_queryset(self): + qs = super().get_queryset() + self.filterset = ReuniaoFilterSet(self.request.GET, queryset=qs) + return self.filterset.qs + def take_reuniao_pk(self): username = self.request.user.username @@ -276,6 +281,8 @@ class ReuniaoCrud(MasterDetailCrud): context['documentoacessorio_set'] = DocumentoAcessorio.objects.filter( reuniao__pk=context['reuniao_pk'] ).order_by('id') + + context['form'] = self.filterset.form return context class UpdateView(MasterDetailCrud.UpdateView): From 7c8bca3a0b94d80fdcbf8e558c6c6f81ae9e638c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moraes?= Date: Tue, 23 Jun 2026 19:34:58 -0300 Subject: [PATCH 5/9] =?UTF-8?q?feat(sessao):=20renomeia=20anexo=20para=20'?= =?UTF-8?q?Vota=C3=A7=C3=B5es=20Nominais'=20e=20exibe=20anexos=20na=20busc?= =?UTF-8?q?a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renomeia o verbose_name do campo upload_anexo de SessaoPlenaria de 'Anexo da Sessão' para 'Votações Nominais' (com migração) e passa a exibir os links de Pauta da Sessão e Votações Nominais ao lado da Ata na pesquisa pública de sessões plenárias. --- .../migrations/0070_auto_20260623_1932.py | 20 +++++++++++++++++++ sapl/sessao/models.py | 2 +- .../sessao/sessaoplenaria_filter.html | 6 ++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 sapl/sessao/migrations/0070_auto_20260623_1932.py diff --git a/sapl/sessao/migrations/0070_auto_20260623_1932.py b/sapl/sessao/migrations/0070_auto_20260623_1932.py new file mode 100644 index 000000000..bb79bfa51 --- /dev/null +++ b/sapl/sessao/migrations/0070_auto_20260623_1932.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.28 on 2026-06-23 22:32 + +from django.db import migrations, models +import sapl.sessao.models +import sapl.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('sessao', '0069_auto_20220919_1705'), + ] + + operations = [ + migrations.AlterField( + model_name='sessaoplenaria', + name='upload_anexo', + field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.sessao.models.anexo_upload_path, verbose_name='Votações Nominais'), + ), + ] diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index 613068afe..60a19f767 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -209,7 +209,7 @@ class SessaoPlenaria(models.Model): null=True, storage=OverwriteStorage(), upload_to=anexo_upload_path, - verbose_name=_('Anexo da Sessão')) + verbose_name=_('Votações Nominais')) iniciada = models.BooleanField( null=True, blank=True, diff --git a/sapl/templates/sessao/sessaoplenaria_filter.html b/sapl/templates/sessao/sessaoplenaria_filter.html index 51c2fdb17..d9179db76 100644 --- a/sapl/templates/sessao/sessaoplenaria_filter.html +++ b/sapl/templates/sessao/sessaoplenaria_filter.html @@ -53,6 +53,12 @@ {% if s.upload_ata %} Ata da Sessão
{% endif %} + {% if s.upload_pauta %} + Pauta da Sessão
+ {% endif %} + {% if s.upload_anexo %} + Votações Nominais
+ {% endif %} {% endfor %} From d548ee40ce6579c4548d058bbe5c3f3bcf316a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moraes?= Date: Tue, 23 Jun 2026 19:35:59 -0300 Subject: [PATCH 6/9] =?UTF-8?q?feat(base):=20exibe=20data=20de=20atualiza?= =?UTF-8?q?=C3=A7=C3=A3o=20no=20topo=20das=20p=C3=A1ginas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adiciona uma faixa no topo do layout com 'Informações atualizadas em DD/MM/AAAA' (data corrente, sem horário), oculta na impressão e em iframes. --- sapl/templates/base.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sapl/templates/base.html b/sapl/templates/base.html index 79b285a3a..67535ce7a 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -40,6 +40,12 @@
+ {% if not request|has_iframe %} +
+ {% trans 'Informações atualizadas em' %} {% now "d/m/Y" %} +
+ {% endif %} + {% if not sapl_as_sapn or user.is_authenticated%} {% if not request|has_iframe %} {% block navigation %} From 3de953259cf62e5d548af0c2788f79c77e981e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moraes?= Date: Tue, 23 Jun 2026 19:42:53 -0300 Subject: [PATCH 7/9] =?UTF-8?q?feat(audiencia):=20permite=20autor=20de=20q?= =?UTF-8?q?ualquer=20tipo=20na=20audi=C3=AAncia=20p=C3=BAblica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Substitui o campo parlamentar_autor (FK direta a Parlamentar) pelo Autor polimórfico do SAPL em AudienciaPublica, permitindo Parlamentar, Comissão, Bancada e demais tipos. Atualiza o formulário, o layout e a validação de autoria do requerimento. A migração preserva os autores já cadastrados. --- sapl/audiencia/forms.py | 20 +++---- .../migrations/0021_auto_20260623_1938.py | 59 +++++++++++++++++++ sapl/audiencia/models.py | 8 +-- sapl/templates/audiencia/layouts.yaml | 4 +- 4 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 sapl/audiencia/migrations/0021_auto_20260623_1938.py diff --git a/sapl/audiencia/forms.py b/sapl/audiencia/forms.py index 211db9855..adab88db6 100755 --- a/sapl/audiencia/forms.py +++ b/sapl/audiencia/forms.py @@ -10,9 +10,9 @@ from django.utils.translation import ugettext_lazy as _ from crispy_forms.layout import Button, Column, Fieldset, HTML, Layout from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica, AnexoAudienciaPublica +from sapl.base.models import Autor from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, SaplFormLayout, to_row from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa -from sapl.parlamentares.models import Parlamentar from sapl.utils import timezone, FileFieldCheckMixin, validar_arquivo @@ -44,10 +44,10 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm): widget=forms.HiddenInput(), queryset=MateriaLegislativa.objects.all()) - parlamentar_autor = forms.ModelChoiceField( - label=_("Parlamentar Autor"), + autor = forms.ModelChoiceField( + label=_("Autor"), required=False, - queryset=Parlamentar.objects.all()) + queryset=Autor.objects.all().order_by('nome')) requerimento = forms.ModelChoiceField( label=_("Requerimento"), @@ -58,7 +58,7 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm): model = AudienciaPublica fields = ['tipo', 'numero', 'ano', 'nome', 'tema', 'data', 'hora_inicio', 'hora_fim', - 'observacao', 'audiencia_cancelada', 'parlamentar_autor', 'requerimento', 'url_audio', + 'observacao', 'audiencia_cancelada', 'autor', 'requerimento', 'url_audio', 'url_video', 'upload_pauta', 'upload_ata', 'upload_anexo', 'tipo_materia', 'numero_materia', 'ano_materia', 'materia'] @@ -85,7 +85,7 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm): materia = cleaned_data['numero_materia'] ano_materia = cleaned_data['ano_materia'] tipo_materia = cleaned_data['tipo_materia'] - parlamentar_autor = cleaned_data["parlamentar_autor"] + autor = cleaned_data["autor"] requerimento = cleaned_data["requerimento"] if cleaned_data["ano"] != cleaned_data["data"].year: @@ -157,12 +157,12 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm): raise ValidationError(msg) # requerimento é optativo - if parlamentar_autor and requerimento: - if parlamentar_autor.autor.first() not in requerimento.autores.all(): - raise ValidationError("Parlamentar Autor selecionado não faz" + if autor and requerimento: + if autor not in requerimento.autores.all(): + raise ValidationError("Autor selecionado não faz" " parte da autoria do Requerimento " "selecionado.") - elif parlamentar_autor: + elif autor: raise ValidationError("Para informar um autor deve-se informar um requerimento.") elif requerimento: raise ValidationError("Para informar um requerimento deve-se informar um autor.") diff --git a/sapl/audiencia/migrations/0021_auto_20260623_1938.py b/sapl/audiencia/migrations/0021_auto_20260623_1938.py new file mode 100644 index 000000000..a2e101c64 --- /dev/null +++ b/sapl/audiencia/migrations/0021_auto_20260623_1938.py @@ -0,0 +1,59 @@ +# Generated by Django 2.2.28 on 2026-06-23 22:38 + +from django.db import migrations, models +import django.db.models.deletion + + +def copia_parlamentar_para_autor(apps, schema_editor): + """Converte o antigo parlamentar_autor no Autor genérico correspondente.""" + AudienciaPublica = apps.get_model('audiencia', 'AudienciaPublica') + Autor = apps.get_model('base', 'Autor') + ContentType = apps.get_model('contenttypes', 'ContentType') + try: + ct = ContentType.objects.get( + app_label='parlamentares', model='parlamentar') + except ContentType.DoesNotExist: + return + for ap in AudienciaPublica.objects.filter(parlamentar_autor__isnull=False): + autor = Autor.objects.filter( + content_type=ct, object_id=ap.parlamentar_autor_id).first() + if autor: + ap.autor = autor + ap.save(update_fields=['autor']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0060_auto_20240812_1628'), + ('audiencia', '0020_auto_20251201_1450'), + ] + + operations = [ + migrations.AddField( + model_name='audienciapublica', + name='autor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='base.Autor', verbose_name='Autor'), + ), + migrations.RunPython( + copia_parlamentar_para_autor, migrations.RunPython.noop), + # Mantém a coluna parlamentar_autor no banco (removida apenas do estado + # do Django). Assim uma casa pode dar rollback da imagem sem perder o + # vínculo, e o código novo simplesmente ignora a coluna. O DROP + # definitivo fica para uma migração posterior, quando toda a frota + # estiver estável nesta versão. + migrations.SeparateDatabaseAndState( + state_operations=[ + migrations.RemoveField( + model_name='audienciapublica', + name='parlamentar_autor', + ), + ], + database_operations=[], + ), + migrations.AlterField( + model_name='audienciapublica', + name='ano', + field=models.PositiveSmallIntegerField(choices=[(2027, 2027), (2026, 2026), (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'), + ), + ] diff --git a/sapl/audiencia/models.py b/sapl/audiencia/models.py index 6b74cffd0..a630e9c40 100755 --- a/sapl/audiencia/models.py +++ b/sapl/audiencia/models.py @@ -3,7 +3,7 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from model_utils import Choices from sapl.materia.models import MateriaLegislativa -from sapl.parlamentares.models import (CargoMesa, Parlamentar) +from sapl.parlamentares.models import CargoMesa from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericRelation, restringe_tipos_de_arquivo_txt, texto_upload_path, @@ -79,12 +79,12 @@ class AudienciaPublica(models.Model): default=False, choices=YES_NO_CHOICES, verbose_name=_('Audiência Cancelada?')) - parlamentar_autor = models.ForeignKey( - Parlamentar, + autor = models.ForeignKey( + 'base.Autor', on_delete=models.PROTECT, null=True, blank=True, - verbose_name=_('Parlamentar Autor')) + verbose_name=_('Autor')) requerimento = models.ForeignKey( MateriaLegislativa, null=True, diff --git a/sapl/templates/audiencia/layouts.yaml b/sapl/templates/audiencia/layouts.yaml index 916bfa287..18bc46e46 100644 --- a/sapl/templates/audiencia/layouts.yaml +++ b/sapl/templates/audiencia/layouts.yaml @@ -6,7 +6,7 @@ AudienciaPublica: {% trans 'Dados' %}: - tipo_materia numero_materia ano_materia - tipo:4 data:2 hora_inicio:3 hora_fim:3 - - parlamentar_autor requerimento + - autor requerimento - url_audio url_video - upload_pauta upload_ata upload_anexo - observacao @@ -19,7 +19,7 @@ AudienciaPublicaDetail: {% trans 'Dados' %}: - materia tipo - data hora_inicio hora_fim - - parlamentar_autor requerimento + - autor requerimento - url_audio url_video - upload_pauta upload_ata upload_anexo - observacao From 247c97bf88bda3cb803f079b8acb57d28f587078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moraes?= Date: Fri, 26 Jun 2026 12:24:04 -0300 Subject: [PATCH 8/9] =?UTF-8?q?feat(base):=20faixa=20de=20atualidade=20usa?= =?UTF-8?q?=20a=20data=20de=20=C3=BAltima=20atualiza=C3=A7=C3=A3o=20da=20e?= =?UTF-8?q?ntidade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Em telas de detalhe (CrudDetailView), a faixa do topo passa a exibir a data de última atualização do registro (campo data_ultima_atualizacao); nas demais telas (pesquisa/listagem) exibe a data atual. Adiciona data_ultima_atualizacao (auto_now) a Parlamentar, Comissao, DocumentoAdministrativo e AudienciaPublica. --- ...udienciapublica_data_ultima_atualizacao.py | 18 ++++++++++++ sapl/audiencia/models.py | 3 ++ .../0031_comissao_data_ultima_atualizacao.py | 18 ++++++++++++ sapl/comissoes/models.py | 3 ++ sapl/crud/base.py | 5 ++++ ...046_parlamentar_data_ultima_atualizacao.py | 18 ++++++++++++ sapl/parlamentares/models.py | 4 +++ .../migrations/0046_auto_20260626_1220.py | 28 +++++++++++++++++++ sapl/protocoloadm/models.py | 3 ++ sapl/templates/base.html | 3 +- 10 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 sapl/audiencia/migrations/0022_audienciapublica_data_ultima_atualizacao.py create mode 100644 sapl/comissoes/migrations/0031_comissao_data_ultima_atualizacao.py create mode 100644 sapl/parlamentares/migrations/0046_parlamentar_data_ultima_atualizacao.py create mode 100644 sapl/protocoloadm/migrations/0046_auto_20260626_1220.py diff --git a/sapl/audiencia/migrations/0022_audienciapublica_data_ultima_atualizacao.py b/sapl/audiencia/migrations/0022_audienciapublica_data_ultima_atualizacao.py new file mode 100644 index 000000000..15f49d3fa --- /dev/null +++ b/sapl/audiencia/migrations/0022_audienciapublica_data_ultima_atualizacao.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.28 on 2026-06-26 15:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audiencia', '0021_auto_20260623_1938'), + ] + + operations = [ + migrations.AddField( + model_name='audienciapublica', + name='data_ultima_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data da Última Atualização'), + ), + ] diff --git a/sapl/audiencia/models.py b/sapl/audiencia/models.py index a630e9c40..8ba14f29f 100755 --- a/sapl/audiencia/models.py +++ b/sapl/audiencia/models.py @@ -85,6 +85,9 @@ class AudienciaPublica(models.Model): null=True, blank=True, verbose_name=_('Autor')) + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, auto_now=True, + verbose_name=_('Data da Última Atualização')) requerimento = models.ForeignKey( MateriaLegislativa, null=True, diff --git a/sapl/comissoes/migrations/0031_comissao_data_ultima_atualizacao.py b/sapl/comissoes/migrations/0031_comissao_data_ultima_atualizacao.py new file mode 100644 index 000000000..e35f98e0a --- /dev/null +++ b/sapl/comissoes/migrations/0031_comissao_data_ultima_atualizacao.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.28 on 2026-06-26 15:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0030_auto_20231007_2149'), + ] + + operations = [ + migrations.AddField( + model_name='comissao', + name='data_ultima_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data da Última Atualização'), + ), + ] diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py index 2d9a323d3..694fdceaf 100644 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -34,6 +34,9 @@ class Comissao(models.Model): tipo = models.ForeignKey(TipoComissao, on_delete=models.PROTECT, verbose_name=_('Tipo')) + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, auto_now=True, + verbose_name=_('Data da Última Atualização')) nome = models.CharField(max_length=100, verbose_name=_('Nome')) sigla = models.CharField(max_length=10, verbose_name=_('Sigla')) data_criacao = models.DateField(verbose_name=_('Data de Criação')) diff --git a/sapl/crud/base.py b/sapl/crud/base.py index fd091ec52..0966c4bc4 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -863,6 +863,11 @@ class CrudDetailView(PermissionRequiredContainerCrudMixin, context[context_object_name] = self.object context.update(kwargs) + # Mensagem de atualidade (PNTP): data de última atualização da entidade, + # quando o modelo possuir o campo. Consumido pelo banner em base.html. + context['data_atualizacao_conteudo'] = getattr( + self.object, 'data_ultima_atualizacao', None) + return context @property diff --git a/sapl/parlamentares/migrations/0046_parlamentar_data_ultima_atualizacao.py b/sapl/parlamentares/migrations/0046_parlamentar_data_ultima_atualizacao.py new file mode 100644 index 000000000..c4ae53dee --- /dev/null +++ b/sapl/parlamentares/migrations/0046_parlamentar_data_ultima_atualizacao.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.28 on 2026-06-26 15:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0045_auto_20251201_1531'), + ] + + operations = [ + migrations.AddField( + model_name='parlamentar', + name='data_ultima_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data da Última Atualização'), + ), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index eecc2f314..6098835c4 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -193,6 +193,10 @@ class Parlamentar(models.Model): SEXO_CHOICE = ((FEMININO, _('Feminino')), (MASCULINO, _('Masculino'))) + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, auto_now=True, + verbose_name=_('Data da Última Atualização')) + nivel_instrucao = models.ForeignKey( NivelInstrucao, blank=True, diff --git a/sapl/protocoloadm/migrations/0046_auto_20260626_1220.py b/sapl/protocoloadm/migrations/0046_auto_20260626_1220.py new file mode 100644 index 000000000..313bcd98a --- /dev/null +++ b/sapl/protocoloadm/migrations/0046_auto_20260626_1220.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.28 on 2026-06-26 15:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('protocoloadm', '0045_auto_20240711_1405'), + ] + + operations = [ + migrations.AddField( + model_name='documentoadministrativo', + name='data_ultima_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data da Última Atualização'), + ), + migrations.AlterField( + model_name='documentoadministrativo', + name='ano', + field=models.PositiveSmallIntegerField(choices=[(2027, 2027), (2026, 2026), (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=[(2027, 2027), (2026, 2026), (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'), + ), + ] diff --git a/sapl/protocoloadm/models.py b/sapl/protocoloadm/models.py index 3cdf32735..1a169733a 100644 --- a/sapl/protocoloadm/models.py +++ b/sapl/protocoloadm/models.py @@ -158,6 +158,9 @@ class DocumentoAdministrativo(models.Model): tipo = models.ForeignKey( TipoDocumentoAdministrativo, on_delete=models.PROTECT, verbose_name=_('Tipo Documento')) + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, auto_now=True, + verbose_name=_('Data da Última Atualização')) numero = models.PositiveIntegerField(verbose_name=_('Número')) complemento = models.CharField(max_length=10, blank=True, diff --git a/sapl/templates/base.html b/sapl/templates/base.html index 67535ce7a..6b08b16b3 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -42,7 +42,8 @@ {% if not request|has_iframe %}
- {% trans 'Informações atualizadas em' %} {% now "d/m/Y" %} + {% trans 'Informações foram atualizadas em' %} + {% if data_atualizacao_conteudo %}{{ data_atualizacao_conteudo|date:"d/m/Y" }}{% else %}{% now "d/m/Y" %}{% endif %}
{% endif %} From fd3d093f17a636e7781b809a084f428d2d5b7f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moraes?= Date: Fri, 26 Jun 2026 12:24:04 -0300 Subject: [PATCH 9/9] =?UTF-8?q?fix(sessao):=20reverte=20r=C3=B3tulo=20do?= =?UTF-8?q?=20anexo=20e=20adiciona=20link=20de=20Vota=C3=A7=C3=B5es=20Nomi?= =?UTF-8?q?nais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Desfaz o rename de upload_anexo (volta a 'Anexo da Sessão' e remove a migração de verbose_name) e adiciona, na pesquisa pública de sessões, um link 'Votações Nominais' que aponta para o resumo da sessão. --- .../migrations/0070_auto_20260623_1932.py | 20 ------------------- sapl/sessao/models.py | 2 +- .../sessao/sessaoplenaria_filter.html | 3 ++- 3 files changed, 3 insertions(+), 22 deletions(-) delete mode 100644 sapl/sessao/migrations/0070_auto_20260623_1932.py diff --git a/sapl/sessao/migrations/0070_auto_20260623_1932.py b/sapl/sessao/migrations/0070_auto_20260623_1932.py deleted file mode 100644 index bb79bfa51..000000000 --- a/sapl/sessao/migrations/0070_auto_20260623_1932.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.2.28 on 2026-06-23 22:32 - -from django.db import migrations, models -import sapl.sessao.models -import sapl.utils - - -class Migration(migrations.Migration): - - dependencies = [ - ('sessao', '0069_auto_20220919_1705'), - ] - - operations = [ - migrations.AlterField( - model_name='sessaoplenaria', - name='upload_anexo', - field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.sessao.models.anexo_upload_path, verbose_name='Votações Nominais'), - ), - ] diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index 60a19f767..613068afe 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -209,7 +209,7 @@ class SessaoPlenaria(models.Model): null=True, storage=OverwriteStorage(), upload_to=anexo_upload_path, - verbose_name=_('Votações Nominais')) + verbose_name=_('Anexo da Sessão')) iniciada = models.BooleanField( null=True, blank=True, diff --git a/sapl/templates/sessao/sessaoplenaria_filter.html b/sapl/templates/sessao/sessaoplenaria_filter.html index d9179db76..7ea3f8b65 100644 --- a/sapl/templates/sessao/sessaoplenaria_filter.html +++ b/sapl/templates/sessao/sessaoplenaria_filter.html @@ -57,8 +57,9 @@ Pauta da Sessão
{% endif %} {% if s.upload_anexo %} - Votações Nominais
+ Anexo da Sessão
{% endif %} + Votações Nominais
{% endfor %}