From 799f0ec7f04261fa450806280d3bc410d9ddab3c Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Sun, 12 Apr 2026 23:57:17 -0300 Subject: [PATCH 01/26] =?UTF-8?q?feat:=20cria=20MesaDiretoraCrud=20e=20Com?= =?UTF-8?q?posicaoMesaCrud=20sem=20forms=20e=20regras=20de=20neg=C3=B3cio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0046_mesadiretora_legislatura.py | 37 ++++++++++++++ .../migrations/0047_auto_20260412_2256.py | 23 +++++++++ sapl/parlamentares/models.py | 17 ++++--- sapl/parlamentares/urls.py | 8 +++- sapl/parlamentares/views.py | 48 +++++++++++++++++-- sapl/templates/index.html | 2 +- sapl/templates/navbar.yaml | 2 +- ...form.html => composicaomesa_form_old.html} | 0 sapl/templates/parlamentares/layouts.yaml | 15 ++++++ ...ml => public_composicaomesa_form_old.html} | 0 sapl/templates/parlamentares/subnav_mesa.yaml | 5 ++ 11 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 sapl/parlamentares/migrations/0046_mesadiretora_legislatura.py create mode 100644 sapl/parlamentares/migrations/0047_auto_20260412_2256.py rename sapl/templates/parlamentares/{composicaomesa_form.html => composicaomesa_form_old.html} (100%) rename sapl/templates/parlamentares/{public_composicaomesa_form.html => public_composicaomesa_form_old.html} (100%) create mode 100644 sapl/templates/parlamentares/subnav_mesa.yaml diff --git a/sapl/parlamentares/migrations/0046_mesadiretora_legislatura.py b/sapl/parlamentares/migrations/0046_mesadiretora_legislatura.py new file mode 100644 index 000000000..052527523 --- /dev/null +++ b/sapl/parlamentares/migrations/0046_mesadiretora_legislatura.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.28 on 2026-04-13 01:48 + +from django.db import migrations, models +import django.db.models.deletion +from datetime import date + +def add_legislatura_to_mesa_diretora(apps, schema_editor): + MesaDiretora = apps.get_model('parlamentares', 'MesaDiretora') + SessaoLegislativa = apps.get_model('parlamentares', 'SessaoLegislativa') + + for mesa in MesaDiretora.objects.all(): + mesa.legislatura = mesa.sessao_legislativa.legislatura + data_inicio = mesa.sessao_legislativa.data_inicio + data_fim = mesa.sessao_legislativa.data_fim + if data_inicio.year == data_fim.year: + mesa.data_inicio = date(data_inicio.year, 1, 1) + mesa.data_fim = date(data_fim.year, 12, 31) + else: + mesa.data_inicio = data_inicio + mesa.data_fim = data_fim + mesa.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0045_auto_20251201_1531'), + ] + + operations = [ + migrations.AddField( + model_name='mesadiretora', + name='legislatura', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Legislatura', verbose_name='Legislatura'), + ), + migrations.RunPython(add_legislatura_to_mesa_diretora), + ] diff --git a/sapl/parlamentares/migrations/0047_auto_20260412_2256.py b/sapl/parlamentares/migrations/0047_auto_20260412_2256.py new file mode 100644 index 000000000..66eae56c1 --- /dev/null +++ b/sapl/parlamentares/migrations/0047_auto_20260412_2256.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.28 on 2026-04-13 01:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0046_mesadiretora_legislatura'), + ] + + operations = [ + migrations.AlterField( + model_name='mesadiretora', + name='legislatura', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Legislatura', verbose_name='Legislatura'), + ), + migrations.RemoveField( + model_name='mesadiretora', + name='sessao_legislativa', + ), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index eecc2f314..62c88e111 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -493,27 +493,30 @@ class CargoMesa(models.Model): class MesaDiretora(models.Model): data_inicio = models.DateField(verbose_name=_('Data Início'), null=True) data_fim = models.DateField(verbose_name=_('Data Fim'), null=True) - sessao_legislativa = models.ForeignKey(SessaoLegislativa, - on_delete=models.PROTECT) + legislatura = models.ForeignKey(Legislatura, + on_delete=models.PROTECT, + verbose_name=_('Legislatura')) descricao = models.TextField(verbose_name=_('Descrição'), blank=True) class Meta: verbose_name = _('Mesa Diretora') verbose_name_plural = _('Mesas Diretoras') - ordering = ('-data_inicio', '-sessao_legislativa') + ordering = ('-data_inicio', '-legislatura') def __str__(self): - return _('Mesa da %(sessao)s sessao da %(legislatura)s Legislatura') % { - 'sessao': self.sessao_legislativa, 'legislatura': self.sessao_legislativa.legislatura + return _('%(legislatura)s - %(data_inicio)s a %(data_fim)s') % { + 'legislatura': self.legislatura, + 'data_inicio': self.data_inicio, + 'data_fim': self.data_fim } - class ComposicaoMesa(models.Model): # TODO M2M ???? Ternary????? parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT) cargo = models.ForeignKey(CargoMesa, on_delete=models.PROTECT) mesa_diretora = models.ForeignKey( - MesaDiretora, on_delete=models.PROTECT, null=True) + MesaDiretora, on_delete=models.PROTECT, null=True, + related_name='composicaomesa_set') class Meta: verbose_name = _('Ocupação de cargo na Mesa') diff --git a/sapl/parlamentares/urls.py b/sapl/parlamentares/urls.py index d67a1f6b1..08442ee24 100644 --- a/sapl/parlamentares/urls.py +++ b/sapl/parlamentares/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import include, url -from sapl.parlamentares.views import (CargoMesaCrud, ColigacaoCrud, +from sapl.parlamentares.views import (CargoMesaCrud, ColigacaoCrud, ComposicaoMesaCrud, MesaDiretoraCrud, coligacao_legislatura, ComposicaoColigacaoCrud, DependenteCrud, FiliacaoCrud, FrenteCrud, FrenteList, @@ -104,6 +104,12 @@ urlpatterns = [ url(r'^sistema/mesa-diretora/cargo-mesa/', include(CargoMesaCrud.get_urls())), + url(r'^mesadiretora/', include( + MesaDiretoraCrud.get_urls() + ComposicaoMesaCrud.get_urls() + )), +] + +urlpatterns_mesa_old = [ url(r'^mesa-diretora/$', MesaDiretoraView.as_view(), name='mesa_diretora'), diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index a46ce2b84..9bc605eb3 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -1007,20 +1007,58 @@ def parlamentares_filiados(request, pk): return render(request, template_name, {'partido': partido, 'parlamentares': parlamentares_filiados}) +class MesaDiretoraCrud(Crud): + model = MesaDiretora + help_topic = 'mesa_diretora' + public = [RP_DETAIL, RP_LIST] + + class BaseMixin(Crud.BaseMixin): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + if 'subnav_template_name' not in context: + context['subnav_template_name'] = 'parlamentares/subnav_mesa.yaml' + return context + + class ListView(Crud.ListView): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['subnav_template_name'] = '' + return context + + class CreateView(Crud.CreateView): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['subnav_template_name'] = '' + return context + + +class ComposicaoMesaCrud(MasterDetailCrud): + model = ComposicaoMesa + parent_field = 'mesa_diretora' + help_topic = 'mesa_diretora' + public = [RP_LIST, RP_DETAIL] + + class BaseMixin(MasterDetailCrud.BaseMixin): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['subnav_template_name'] = 'parlamentares/subnav_mesa.yaml' + return context + + class MesaDiretoraView(FormView): - template_name = 'parlamentares/composicaomesa_form.html' - success_url = reverse_lazy('sapl.parlamentares:mesa_diretora') + template_name = 'parlamentares/composicaomesa_form_old.html' + success_url = ''#reverse_lazy('sapl.parlamentares:mesa_diretora') logger = logging.getLogger(__name__) def get_template_names(self): if self.request.user.has_perm('parlamentares.change_composicaomesa'): if 'iframe' not in self.request.GET: if not self.request.session.get('iframe'): - return 'parlamentares/composicaomesa_form.html' + return 'parlamentares/composicaomesa_form_old.html' elif self.request.GET['iframe'] == '0': - return 'parlamentares/composicaomesa_form.html' + return 'parlamentares/composicaomesa_form_old.html' - return 'parlamentares/public_composicaomesa_form.html' + return 'parlamentares/public_composicaomesa_form_old.html' # Essa função avisa quando se pode compor uma Mesa Legislativa def validation(self, request): diff --git a/sapl/templates/index.html b/sapl/templates/index.html index 3cb59bbc6..57b73cbcd 100644 --- a/sapl/templates/index.html +++ b/sapl/templates/index.html @@ -26,7 +26,7 @@ a dois anos.

- +
diff --git a/sapl/templates/navbar.yaml b/sapl/templates/navbar.yaml index e4dc39c10..1a5cb415b 100644 --- a/sapl/templates/navbar.yaml +++ b/sapl/templates/navbar.yaml @@ -6,7 +6,7 @@ - title: {% trans 'Institucional' %} children: - title: {% trans 'Mesa Diretora' %} - url: sapl.parlamentares:mesa_diretora + url: sapl.parlamentares:mesadiretora_list - title: {% trans 'Bancadas Parlamentares' %} url: sapl.sessao:bancada_list - title: {% trans 'Blocos Parlamentares' %} diff --git a/sapl/templates/parlamentares/composicaomesa_form.html b/sapl/templates/parlamentares/composicaomesa_form_old.html similarity index 100% rename from sapl/templates/parlamentares/composicaomesa_form.html rename to sapl/templates/parlamentares/composicaomesa_form_old.html diff --git a/sapl/templates/parlamentares/layouts.yaml b/sapl/templates/parlamentares/layouts.yaml index 9d2037477..3cb32ac9b 100644 --- a/sapl/templates/parlamentares/layouts.yaml +++ b/sapl/templates/parlamentares/layouts.yaml @@ -166,3 +166,18 @@ BlocoMembroList: - id - cargo:4 parlamentar - data_entrada data_saida + +MesaDiretora: + {% trans 'Período e Legislatura' %}: + - data_inicio data_fim legislatura:6 + - descricao + +MesaDiretoraDetail: + {% trans 'Período e Legislatura' %}: + - data_inicio data_fim legislatura:6 + - descricao + - composicaomesa_set + +ComposicaoMesa: + {% trans 'Composição da Mesa' %}: + - cargo:4 parlamentar \ No newline at end of file diff --git a/sapl/templates/parlamentares/public_composicaomesa_form.html b/sapl/templates/parlamentares/public_composicaomesa_form_old.html similarity index 100% rename from sapl/templates/parlamentares/public_composicaomesa_form.html rename to sapl/templates/parlamentares/public_composicaomesa_form_old.html diff --git a/sapl/templates/parlamentares/subnav_mesa.yaml b/sapl/templates/parlamentares/subnav_mesa.yaml new file mode 100644 index 000000000..32779de54 --- /dev/null +++ b/sapl/templates/parlamentares/subnav_mesa.yaml @@ -0,0 +1,5 @@ +{% load i18n common_tags %} +- title: {% trans 'Mesa Diretora' %} + url: sapl.parlamentares:mesadiretora_list +- title: {% trans 'Composição da Mesa' %} + url: sapl.parlamentares:composicaomesa_list \ No newline at end of file From 652e74fccf473fb58423f8857550ae8deffa25be Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Mon, 13 Apr 2026 10:39:26 -0300 Subject: [PATCH 02/26] =?UTF-8?q?feat:=20cria=20filterset=20para=20sele?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20legislatura?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/parlamentares/forms.py | 29 ++++++++++++++++++++++++++--- sapl/parlamentares/urls.py | 2 +- sapl/parlamentares/views.py | 19 +++++++++++++++++-- sapl/templates/crud/list.html | 2 ++ sapl/utils.py | 5 +++++ 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index 89ab424f5..c53fb2db1 100755 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -1,7 +1,7 @@ from datetime import timedelta import logging -from crispy_forms.layout import Fieldset, Layout +from crispy_forms.layout import Fieldset, Layout, Field from django import forms from django.contrib.auth import get_user_model from django.contrib.auth.models import Group, User @@ -19,10 +19,10 @@ from sapl.base.models import Autor, TipoAutor from sapl.crispy_layout_mixin import SaplFormHelper from sapl.crispy_layout_mixin import form_actions, to_row from sapl.rules import SAPL_GROUP_VOTANTE -from sapl.utils import FileFieldCheckMixin +from sapl.utils import FileFieldCheckMixin, SelectSubmitChangeWidget from .models import (Coligacao, ComposicaoColigacao, Filiacao, Frente, Legislatura, - Mandato, Parlamentar, Partido, Votante, Bloco, FrenteParlamentar, BlocoMembro) + Mandato, MesaDiretora, Parlamentar, Partido, Votante, Bloco, FrenteParlamentar, BlocoMembro) class CustomImageCropWidget(ImageCropWidget): @@ -752,3 +752,26 @@ class BlocoMembroForm(ModelForm): _("Parlamentar já é membro do bloco parlamentar.")) return cd + +class MesaDiretoraFilterSet(django_filters.FilterSet): + + legislatura = django_filters.ModelChoiceFilter( + label='', + queryset=Legislatura.objects.all(), + widget=SelectSubmitChangeWidget) + + class Meta: + model = MesaDiretora + fields = ['legislatura'] + + def __init__(self, *args, **kwargs): + super(MesaDiretoraFilterSet, self).__init__(*args, **kwargs) + + row0 = to_row([('legislatura', 5)]) + + self.form.helper = SaplFormHelper() + self.form.helper.form_method = 'GET' + self.form.helper.layout = Layout( + Fieldset(_('Escolha da Legislatura e da Sessão Legislativa'), + row0,) + ) diff --git a/sapl/parlamentares/urls.py b/sapl/parlamentares/urls.py index 08442ee24..c9ed82fe9 100644 --- a/sapl/parlamentares/urls.py +++ b/sapl/parlamentares/urls.py @@ -104,7 +104,7 @@ urlpatterns = [ url(r'^sistema/mesa-diretora/cargo-mesa/', include(CargoMesaCrud.get_urls())), - url(r'^mesadiretora/', include( + url(r'^mesa-diretora/', include( MesaDiretoraCrud.get_urls() + ComposicaoMesaCrud.get_urls() )), ] diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index 9bc605eb3..1a2eeabf7 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -35,7 +35,7 @@ from sapl.parlamentares.apps import AppConfig from sapl.rules import SAPL_GROUP_VOTANTE from sapl.utils import (parlamentares_ativos, show_results_filter_set, ratelimit_ip) -from .forms import (ColigacaoFilterSet, FiliacaoForm, FrenteForm, LegislaturaForm, MandatoForm, +from .forms import (ColigacaoFilterSet, FiliacaoForm, FrenteForm, LegislaturaForm, MandatoForm, MesaDiretoraFilterSet, ParlamentarCreateForm, ParlamentarForm, VotanteForm, ParlamentarFilterSet, PartidoFilterSet, VincularParlamentarForm, BlocoForm, FrenteParlamentarForm, BlocoMembroForm) @@ -1019,12 +1019,27 @@ class MesaDiretoraCrud(Crud): context['subnav_template_name'] = 'parlamentares/subnav_mesa.yaml' return context - class ListView(Crud.ListView): + class ListView(FilterView, Crud.ListView): + filterset_class = MesaDiretoraFilterSet + + def get_filterset_kwargs(self, filterset_class): + fk = super().get_filterset_kwargs(filterset_class) + if 'legislatura' not in self.request.GET: + legislatura = Legislatura.objects.filter( + data_inicio__lte=timezone.now()).order_by('-data_inicio').values_list('id', flat=True).first() + if legislatura: + fk['data'] = {'legislatura': legislatura} + return fk + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['subnav_template_name'] = '' + context['title'] = ' ' return context + def get(self, request, *args, **kwargs): + return FilterView.get(self, request, *args, **kwargs) + class CreateView(Crud.CreateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) diff --git a/sapl/templates/crud/list.html b/sapl/templates/crud/list.html index 44b394337..9e6d79cc9 100644 --- a/sapl/templates/crud/list.html +++ b/sapl/templates/crud/list.html @@ -7,6 +7,8 @@ {% block actions %} diff --git a/sapl/utils.py b/sapl/utils.py index ee97094aa..2df06ea02 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -262,6 +262,11 @@ def montar_helper_autor(self): ' class="btn btn-dark">Cancelar')])) +class SelectSubmitChangeWidget(forms.Select): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.attrs.update({'onchange': 'this.form.submit();'}) + class SaplGenericForeignKey(GenericForeignKey): def __init__( From 7acb5163570ce99424e40ad12032bc5ef0b198ee Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Mon, 13 Apr 2026 11:14:29 -0300 Subject: [PATCH 03/26] feat: add field titulo --- .../migrations/0048_auto_20260413_1049.py | 38 +++++++++++++++++++ sapl/parlamentares/models.py | 12 +++--- sapl/parlamentares/views.py | 7 ++++ sapl/templates/crud/filter.html | 1 + sapl/templates/parlamentares/layouts.yaml | 5 ++- 5 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 sapl/parlamentares/migrations/0048_auto_20260413_1049.py create mode 100644 sapl/templates/crud/filter.html diff --git a/sapl/parlamentares/migrations/0048_auto_20260413_1049.py b/sapl/parlamentares/migrations/0048_auto_20260413_1049.py new file mode 100644 index 000000000..c24c49b17 --- /dev/null +++ b/sapl/parlamentares/migrations/0048_auto_20260413_1049.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2.28 on 2026-04-13 13:49 + +from django.db import migrations, models +import django.db.models.deletion + +def preencher_titulo_mesa_diretora(apps, schema_editor): + MesaDiretora = apps.get_model('parlamentares', 'MesaDiretora') + for mesa in MesaDiretora.objects.all(): + ano_inicio = mesa.data_inicio.year if mesa.data_inicio else None + ano_fim = mesa.data_fim.year if mesa.data_fim else None + if ano_inicio and ano_fim: + mesa.titulo = f'Mesa Diretora{" Biênio" if ano_fim - ano_inicio == 1 else ""} {ano_inicio}/{ano_fim}' + mesa.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0047_auto_20260412_2256'), + ] + + operations = [ + migrations.AlterModelOptions( + name='mesadiretora', + options={'ordering': ('-data_inicio', '-legislatura'), 'verbose_name': 'Mesa Diretora', 'verbose_name_plural': 'Mesas Diretoras'}, + ), + migrations.AddField( + model_name='mesadiretora', + name='titulo', + field=models.CharField(default='', max_length=50, verbose_name='Título da Mesa Diretora'), + ), + migrations.AlterField( + model_name='composicaomesa', + name='mesa_diretora', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='composicaomesa_set', to='parlamentares.MesaDiretora'), + ), + migrations.RunPython(preencher_titulo_mesa_diretora), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 62c88e111..870fe9347 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -491,6 +491,7 @@ class CargoMesa(models.Model): class MesaDiretora(models.Model): + titulo = models.CharField(max_length=50, default='', verbose_name=_('Título da Mesa Diretora')) data_inicio = models.DateField(verbose_name=_('Data Início'), null=True) data_fim = models.DateField(verbose_name=_('Data Fim'), null=True) legislatura = models.ForeignKey(Legislatura, @@ -504,11 +505,12 @@ class MesaDiretora(models.Model): ordering = ('-data_inicio', '-legislatura') def __str__(self): - return _('%(legislatura)s - %(data_inicio)s a %(data_fim)s') % { - 'legislatura': self.legislatura, - 'data_inicio': self.data_inicio, - 'data_fim': self.data_fim - } + return self.titulo or _('%(legislatura)s - %(data_inicio)s a %(data_fim)s') % { + 'legislatura': self.legislatura, + 'data_inicio': self.data_inicio, + 'data_fim': self.data_fim + } + class ComposicaoMesa(models.Model): # TODO M2M ???? Ternary????? diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index 1a2eeabf7..2b11dd9bf 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -1021,6 +1021,7 @@ class MesaDiretoraCrud(Crud): class ListView(FilterView, Crud.ListView): filterset_class = MesaDiretoraFilterSet + paginate_by = None def get_filterset_kwargs(self, filterset_class): fk = super().get_filterset_kwargs(filterset_class) @@ -1046,6 +1047,12 @@ class MesaDiretoraCrud(Crud): context['subnav_template_name'] = '' return context + class DetailView(Crud.DetailView): + layout_key = 'MesaDiretoraDetail' + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + return context + class ComposicaoMesaCrud(MasterDetailCrud): model = ComposicaoMesa diff --git a/sapl/templates/crud/filter.html b/sapl/templates/crud/filter.html new file mode 100644 index 000000000..ddfadab08 --- /dev/null +++ b/sapl/templates/crud/filter.html @@ -0,0 +1 @@ +{% extends "crud/list.html" %} \ No newline at end of file diff --git a/sapl/templates/parlamentares/layouts.yaml b/sapl/templates/parlamentares/layouts.yaml index 3cb32ac9b..6ff9b6351 100644 --- a/sapl/templates/parlamentares/layouts.yaml +++ b/sapl/templates/parlamentares/layouts.yaml @@ -169,14 +169,17 @@ BlocoMembroList: MesaDiretora: {% trans 'Período e Legislatura' %}: + - titulo - data_inicio data_fim legislatura:6 + {% trans 'Descrição' %}: - descricao MesaDiretoraDetail: {% trans 'Período e Legislatura' %}: + - titulo - data_inicio data_fim legislatura:6 + {% trans 'Descrição' %}: - descricao - - composicaomesa_set ComposicaoMesa: {% trans 'Composição da Mesa' %}: From 2b55d6dd4960f94af634026105613c970c47250e Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Mon, 13 Apr 2026 11:50:00 -0300 Subject: [PATCH 04/26] =?UTF-8?q?feat:=20impl=20tabs=20na=20listagem=20de?= =?UTF-8?q?=20mesa=20diretora=20perfil=20p=C3=BAblico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/templates/crud/filter.html | 1 - .../parlamentares/mesadiretora_filter.html | 63 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) delete mode 100644 sapl/templates/crud/filter.html create mode 100644 sapl/templates/parlamentares/mesadiretora_filter.html diff --git a/sapl/templates/crud/filter.html b/sapl/templates/crud/filter.html deleted file mode 100644 index ddfadab08..000000000 --- a/sapl/templates/crud/filter.html +++ /dev/null @@ -1 +0,0 @@ -{% extends "crud/list.html" %} \ No newline at end of file diff --git a/sapl/templates/parlamentares/mesadiretora_filter.html b/sapl/templates/parlamentares/mesadiretora_filter.html new file mode 100644 index 000000000..6f28b05e8 --- /dev/null +++ b/sapl/templates/parlamentares/mesadiretora_filter.html @@ -0,0 +1,63 @@ +{% extends "crud/list.html" %} +{% load i18n common_tags crispy_forms_tags cropping %} + +{% block container_table_list %} + {% if perms.add_mesadiretora %} + {{ block.super }} + {% else %} +
+ +
+ {% for md in object_list %} + {% if forloop.first or request.GET.mesadiretora == md.id|stringformat:"s" %} +
+ {% else %} +
+ {% endif %} + + + + + + + + + + + {% for p in md.composicaomesa_set.all %} + + {% if p.parlamentar.fotografia %} + + {% else %} + + {% endif %} + + + + + {% endfor %} + + +
+ {% endfor %} +
+ {% endif %} + +{% endblock container_table_list %} From b32959c3b779f11e9790c6e3dc0b476c36fbca8f Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Mon, 13 Apr 2026 13:32:48 -0300 Subject: [PATCH 05/26] ajustes no layout publico de mesa --- sapl/parlamentares/models.py | 2 +- .../parlamentares/mesadiretora_filter.html | 25 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 870fe9347..b8544c8fe 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -523,7 +523,7 @@ class ComposicaoMesa(models.Model): class Meta: verbose_name = _('Ocupação de cargo na Mesa') verbose_name_plural = _('Ocupações de cargo na Mesa') - ordering = ('cargo', 'parlamentar') + ordering = ('id', ) def __str__(self): return _('%(parlamentar)s - %(cargo)s') % { diff --git a/sapl/templates/parlamentares/mesadiretora_filter.html b/sapl/templates/parlamentares/mesadiretora_filter.html index 6f28b05e8..d44256f6e 100644 --- a/sapl/templates/parlamentares/mesadiretora_filter.html +++ b/sapl/templates/parlamentares/mesadiretora_filter.html @@ -19,17 +19,18 @@ {% endif %} {% endfor %} -
+
{% for md in object_list %} {% if forloop.first or request.GET.mesadiretora == md.id|stringformat:"s" %}
{% else %}
{% endif %} - + (De {{md.data_inicio }} a {{ md.data_fim }}) + + - @@ -38,17 +39,15 @@ {% for p in md.composicaomesa_set.all %} - {% if p.parlamentar.fotografia %} - - {% else %} - - {% endif %} - + {% endif %} + + {{p.parlamentar.nome_parlamentar}} + + From 106847a244c5d151d0efb65e1ef759802715dd74 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Tue, 14 Apr 2026 10:34:14 -0300 Subject: [PATCH 06/26] ajustes de scss para telas mobiles --- .../src/__global/scss/layouts/_globals.scss | 1 + .../scss/libs/bootstrap/_nav_tabs.scss | 89 +++++++++ frontend/src/__global/scss/libs/libs.scss | 1 + sapl/parlamentares/forms.py | 2 +- .../parlamentares/mesadiretora_filter.html | 182 +++++++++++++----- 5 files changed, 225 insertions(+), 50 deletions(-) create mode 100644 frontend/src/__global/scss/libs/bootstrap/_nav_tabs.scss diff --git a/frontend/src/__global/scss/layouts/_globals.scss b/frontend/src/__global/scss/layouts/_globals.scss index 3cb9c6f82..86515b3be 100644 --- a/frontend/src/__global/scss/layouts/_globals.scss +++ b/frontend/src/__global/scss/layouts/_globals.scss @@ -183,6 +183,7 @@ small { hyphens: auto;*/ } + @media print { a[href]:after { content: none !important; diff --git a/frontend/src/__global/scss/libs/bootstrap/_nav_tabs.scss b/frontend/src/__global/scss/libs/bootstrap/_nav_tabs.scss new file mode 100644 index 000000000..fc0823637 --- /dev/null +++ b/frontend/src/__global/scss/libs/bootstrap/_nav_tabs.scss @@ -0,0 +1,89 @@ +@import "~bootstrap/scss/variables"; + +// Estilização do tab-content conectado ao nav-tabs (substitui inline style) +.nav-tabs + .tab-content { + border: 1px solid $nav-tabs-border-color; + border-top: 0; + border-radius: 0 0 $border-radius $border-radius; +} + +@media (max-width: 992px) { + .nav-tabs { + position: relative; + flex-direction: column; + border: 1px solid $nav-tabs-border-color; + border-radius: $border-radius; // Totalmente arredondado — parece um botão/select + background-color: $white; + overflow: hidden; // Recorta filhos nas bordas arredondadas + + // Seta indicando dropdown + &::after { + content: "▾"; + position: absolute; + right: 0.75rem; + top: 0.6rem; + font-size: 1rem; + color: $secondary; + pointer-events: none; + transition: transform 0.2s ease; + } + + // Oculta todos os itens por padrão + .nav-item { + display: none; + width: 100%; + + .nav-link { + border: none; + border-bottom: 1px solid $nav-tabs-border-color; + border-radius: 0; + width: 100%; + text-align: left; + padding-right: 2rem; + margin-bottom: 0; + + &.active { + background-color: $nav-tabs-link-active-bg; + color: $nav-tabs-link-active-color; + border-color: transparent; + } + + &:hover:not(.active) { + background-color: $light; + } + } + + &:last-child .nav-link { + border-bottom: none; + } + } + + // CSS nativo: exibe somente o item ativo (browsers com suporte a :has) + .nav-item:has(.nav-link.active) { + display: block; + } + + // Estado expandido: via :focus-within (nativo) ou .nav-tabs--open (fallback JS) + &:focus-within, + &.nav-tabs--open { + border-radius: $border-radius $border-radius 0 0; // Arredonda apenas topo quando aberto + overflow: visible; + z-index: $zindex-dropdown; + + &::after { + transform: rotate(180deg); + } + + .nav-item { + display: block; + } + } + } + + // Em mobile, tab-content é visualmente independente do nav-tabs (que vira um select) + .nav-tabs + .tab-content { + border-top: 1px solid $nav-tabs-border-color; + border-radius: $border-radius; // Rounding completo — desconectado do nav-tabs + margin-top: 0.5rem; + } +} diff --git a/frontend/src/__global/scss/libs/libs.scss b/frontend/src/__global/scss/libs/libs.scss index 76a4e7c73..4f3233661 100644 --- a/frontend/src/__global/scss/libs/libs.scss +++ b/frontend/src/__global/scss/libs/libs.scss @@ -1,2 +1,3 @@ @import "./bootstrap/nav_navbar"; +@import "./bootstrap/nav_tabs"; @import "./bootstrap/table"; diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index c53fb2db1..9ab426bed 100755 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -772,6 +772,6 @@ class MesaDiretoraFilterSet(django_filters.FilterSet): self.form.helper = SaplFormHelper() self.form.helper.form_method = 'GET' self.form.helper.layout = Layout( - Fieldset(_('Escolha da Legislatura e da Sessão Legislativa'), + Fieldset(_('Escolha da Legislatura'), row0,) ) diff --git a/sapl/templates/parlamentares/mesadiretora_filter.html b/sapl/templates/parlamentares/mesadiretora_filter.html index d44256f6e..9da3d275c 100644 --- a/sapl/templates/parlamentares/mesadiretora_filter.html +++ b/sapl/templates/parlamentares/mesadiretora_filter.html @@ -5,58 +5,142 @@ {% if perms.add_mesadiretora %} {{ block.super }} {% else %} -
- -
- {% for md in object_list %} - {% if forloop.first or request.GET.mesadiretora == md.id|stringformat:"s" %} -
- {% else %} -
- {% endif %} - (De {{md.data_inicio }} a {{ md.data_fim }}) - -
- - - - - - - - - {% for p in md.composicaomesa_set.all %} +
+
+ + +
+ {% for md in object_list %} + {% if forloop.first or request.GET.mesadiretora == md.id|stringformat:"s" %} +
+ {% else %} +
+ {% endif %} + (De {{md.data_inicio }} a {{ md.data_fim }}) + +
+ - - - + + + - {% endfor %} - - -
- {% endfor %} + + + {% for p in md.composicaomesa_set.all %} + + +
+ {% if p.parlamentar.fotografia %} + + {% endif %} + + {{p.parlamentar.nome_parlamentar}} +
+ + {{p.parlamentar.filiacao_atual}} + {{p.cargo}} + + {% endfor %} + + +
+ {% endfor %} +
+
{% endif %} {% endblock container_table_list %} + +{% block extra_js %} + +{% endblock extra_js %} From 08c5eb6a690d0135aeab2a399ded399e685bac06 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Tue, 14 Apr 2026 15:18:13 -0300 Subject: [PATCH 07/26] altera link para button nas tabs de mesa diretora --- .../parlamentares/mesadiretora_filter.html | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/sapl/templates/parlamentares/mesadiretora_filter.html b/sapl/templates/parlamentares/mesadiretora_filter.html index 9da3d275c..90929be4c 100644 --- a/sapl/templates/parlamentares/mesadiretora_filter.html +++ b/sapl/templates/parlamentares/mesadiretora_filter.html @@ -10,26 +10,28 @@
{% for md in object_list %} - {% if forloop.first or request.GET.mesadiretora == md.id|stringformat:"s" %} -
+ {% if forloop.first or request.GET.mesa == md.id|stringformat:"s" %} +
{% else %} -
+
{% endif %} (De {{md.data_inicio }} a {{ md.data_fim }}) - + + + From 2e894ca9c11f2133e6f3596c8e0c96e881756956 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Tue, 14 Apr 2026 17:36:17 -0300 Subject: [PATCH 08/26] =?UTF-8?q?add=20forms=20com=20regras=20de=20neg?= =?UTF-8?q?=C3=B3cio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/parlamentares/forms.py | 75 ++++++++++++++++++- sapl/parlamentares/models.py | 7 +- sapl/parlamentares/views.py | 24 +++++- .../parlamentares/mesadiretora_filter.html | 4 + 4 files changed, 104 insertions(+), 6 deletions(-) diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index 9ab426bed..f5bef2123 100755 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -21,7 +21,7 @@ from sapl.crispy_layout_mixin import form_actions, to_row from sapl.rules import SAPL_GROUP_VOTANTE from sapl.utils import FileFieldCheckMixin, SelectSubmitChangeWidget -from .models import (Coligacao, ComposicaoColigacao, Filiacao, Frente, Legislatura, +from .models import (Coligacao, ComposicaoColigacao, ComposicaoMesa, Filiacao, Frente, Legislatura, Mandato, MesaDiretora, Parlamentar, Partido, Votante, Bloco, FrenteParlamentar, BlocoMembro) @@ -775,3 +775,76 @@ class MesaDiretoraFilterSet(django_filters.FilterSet): Fieldset(_('Escolha da Legislatura'), row0,) ) + + +class MesaDiretoraForm(ModelForm): + + class Meta: + model = MesaDiretora + fields = '__all__' + + def clean(self): + super(MesaDiretoraForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + + data = self.cleaned_data + legislatura = data['legislatura'] + data_inicio = data['data_inicio'] + data_fim = data['data_fim'] + + if data_inicio >= data_fim: + raise ValidationError(_('A data de início deve ser anterior à data de fim.')) + + # Verifica se há intersecção de datas com outra mesa diretora da mesma legislatura + intersecao_mesadiretora = MesaDiretora.objects.filter( + legislatura=legislatura, + data_inicio__lte=data_fim, + data_fim__gte=data_inicio + ).exclude(pk=self.instance.pk).exists() + if intersecao_mesadiretora: + raise ValidationError(_('As datas da mesa diretora se sobrepõem com outra mesa diretora existente.')) + + # Verifica se as datas da mesa diretora estão dentro do intervalo da legislatura + if data_inicio < legislatura.data_inicio or data_fim > legislatura.data_fim: + raise ValidationError(_('As datas da mesa diretora devem estar dentro do período da legislatura.')) + return data + +class ComposicaoMesaForm(ModelForm): + + class Meta: + model = ComposicaoMesa + fields = '__all__' + widgets = { + 'mesa_diretora': forms.HiddenInput() + } + + def __init__(self, *args, **kwargs): + super(ComposicaoMesaForm, self).__init__(*args, **kwargs) + self.fields['parlamentar'].queryset = self.fields['parlamentar'].queryset.filter( + mandato__legislatura=self.initial['mesa_diretora'].legislatura) + + def clean(self): + super(ComposicaoMesaForm, self).clean() + if not self.is_valid(): + return self.cleaned_data + + data = self.cleaned_data + mesa_diretora = data['mesa_diretora'] + cargo = data['cargo'] + + # Verifica se Parlamentar já ocupa algum cargo + parlamentar = data['parlamentar'] + if ComposicaoMesa.objects.filter( + parlamentar=parlamentar, mesa_diretora=mesa_diretora + ).exclude(pk=self.instance.pk).exists(): + raise ValidationError(_('Parlamentar já ocupa um cargo nesta mesa diretora.')) + + if cargo.unico: + composicao_mesa = ComposicaoMesa.objects.filter( + mesa_diretora=mesa_diretora, + cargo=cargo + ).exclude(pk=self.instance.pk) + if composicao_mesa.exists(): + raise ValidationError(_('Cargo único já ocupado por outro parlamentar.')) + return data diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index b8544c8fe..a8cf1221f 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -513,12 +513,11 @@ class MesaDiretora(models.Model): class ComposicaoMesa(models.Model): - # TODO M2M ???? Ternary????? - parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT) - cargo = models.ForeignKey(CargoMesa, on_delete=models.PROTECT) + parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT, verbose_name=_('Parlamentar')) + cargo = models.ForeignKey(CargoMesa, on_delete=models.PROTECT, verbose_name=_('Cargo')) mesa_diretora = models.ForeignKey( MesaDiretora, on_delete=models.PROTECT, null=True, - related_name='composicaomesa_set') + related_name='composicaomesa_set', verbose_name=_('Mesa Diretora')) class Meta: verbose_name = _('Ocupação de cargo na Mesa') diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index 2b11dd9bf..6c6fb0760 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -35,7 +35,7 @@ from sapl.parlamentares.apps import AppConfig from sapl.rules import SAPL_GROUP_VOTANTE from sapl.utils import (parlamentares_ativos, show_results_filter_set, ratelimit_ip) -from .forms import (ColigacaoFilterSet, FiliacaoForm, FrenteForm, LegislaturaForm, MandatoForm, MesaDiretoraFilterSet, +from .forms import (ColigacaoFilterSet, ComposicaoMesaForm, FiliacaoForm, FrenteForm, LegislaturaForm, MandatoForm, MesaDiretoraFilterSet, MesaDiretoraForm, ParlamentarCreateForm, ParlamentarForm, VotanteForm, ParlamentarFilterSet, PartidoFilterSet, VincularParlamentarForm, BlocoForm, FrenteParlamentarForm, BlocoMembroForm) @@ -1041,7 +1041,12 @@ class MesaDiretoraCrud(Crud): def get(self, request, *args, **kwargs): return FilterView.get(self, request, *args, **kwargs) + class UpdateView(Crud.UpdateView): + form_class = MesaDiretoraForm + class CreateView(Crud.CreateView): + form_class = MesaDiretoraForm + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['subnav_template_name'] = '' @@ -1066,6 +1071,23 @@ class ComposicaoMesaCrud(MasterDetailCrud): context['subnav_template_name'] = 'parlamentares/subnav_mesa.yaml' return context + class UpdateView(MasterDetailCrud.UpdateView): + form_class = ComposicaoMesaForm + + def get_initial(self): + initial = super().get_initial() + initial['mesa_diretora'] = self.object.mesa_diretora + return initial + + class CreateView(MasterDetailCrud.CreateView): + form_class = ComposicaoMesaForm + + def get_initial(self): + initial = super().get_initial() + initial['mesa_diretora'] = MesaDiretora.objects.get(pk=self.kwargs['pk']) + return initial + + class MesaDiretoraView(FormView): template_name = 'parlamentares/composicaomesa_form_old.html' diff --git a/sapl/templates/parlamentares/mesadiretora_filter.html b/sapl/templates/parlamentares/mesadiretora_filter.html index 90929be4c..1ab1b94ef 100644 --- a/sapl/templates/parlamentares/mesadiretora_filter.html +++ b/sapl/templates/parlamentares/mesadiretora_filter.html @@ -14,6 +14,10 @@ + {% elif forloop.first%} + {% else %} - - - - - - - - - - {% for p in composicao_mesa %} - - {% if p.parlamentar.fotografia %} - - {% endif %} - - - - - {% endfor %} - - - - -{% endblock detail_content %} - -{% block extra_js %} - - - -{% endblock %} diff --git a/sapl/utils.py b/sapl/utils.py index 2df06ea02..19690d0e9 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -267,6 +267,7 @@ class SelectSubmitChangeWidget(forms.Select): super().__init__(*args, **kwargs) self.attrs.update({'onchange': 'this.form.submit();'}) + class SaplGenericForeignKey(GenericForeignKey): def __init__( From cfdfd695ce582f7dfa2653276756e287302597c5 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 17:16:39 -0300 Subject: [PATCH 12/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083470632 --- sapl/parlamentares/migrations/0048_auto_20260413_1049.py | 2 +- sapl/parlamentares/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sapl/parlamentares/migrations/0048_auto_20260413_1049.py b/sapl/parlamentares/migrations/0048_auto_20260413_1049.py index c24c49b17..4afbb3fe9 100644 --- a/sapl/parlamentares/migrations/0048_auto_20260413_1049.py +++ b/sapl/parlamentares/migrations/0048_auto_20260413_1049.py @@ -27,7 +27,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='mesadiretora', name='titulo', - field=models.CharField(default='', max_length=50, verbose_name='Título da Mesa Diretora'), + field=models.CharField(default='', max_length=100, verbose_name='Título da Mesa Diretora'), ), migrations.AlterField( model_name='composicaomesa', diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index a8cf1221f..a4422248d 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -491,7 +491,7 @@ class CargoMesa(models.Model): class MesaDiretora(models.Model): - titulo = models.CharField(max_length=50, default='', verbose_name=_('Título da Mesa Diretora')) + titulo = models.CharField(max_length=100, default='', verbose_name=_('Título da Mesa Diretora')) data_inicio = models.DateField(verbose_name=_('Data Início'), null=True) data_fim = models.DateField(verbose_name=_('Data Fim'), null=True) legislatura = models.ForeignKey(Legislatura, From 65e50bce8dc4d8c132fea9d0c65be246624f6874 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 17:20:26 -0300 Subject: [PATCH 13/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083471759 --- sapl/parlamentares/migrations/0048_auto_20260413_1049.py | 2 +- sapl/parlamentares/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sapl/parlamentares/migrations/0048_auto_20260413_1049.py b/sapl/parlamentares/migrations/0048_auto_20260413_1049.py index 4afbb3fe9..ee0d1f3a0 100644 --- a/sapl/parlamentares/migrations/0048_auto_20260413_1049.py +++ b/sapl/parlamentares/migrations/0048_auto_20260413_1049.py @@ -22,7 +22,7 @@ class Migration(migrations.Migration): operations = [ migrations.AlterModelOptions( name='mesadiretora', - options={'ordering': ('-data_inicio', '-legislatura'), 'verbose_name': 'Mesa Diretora', 'verbose_name_plural': 'Mesas Diretoras'}, + options={'ordering': ('-legislatura', '-data_inicio'), 'verbose_name': 'Mesa Diretora', 'verbose_name_plural': 'Mesas Diretoras'}, ), migrations.AddField( model_name='mesadiretora', diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index a4422248d..957d06ee3 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -502,7 +502,7 @@ class MesaDiretora(models.Model): class Meta: verbose_name = _('Mesa Diretora') verbose_name_plural = _('Mesas Diretoras') - ordering = ('-data_inicio', '-legislatura') + ordering = ('-legislatura', '-data_inicio') def __str__(self): return self.titulo or _('%(legislatura)s - %(data_inicio)s a %(data_fim)s') % { From 76dec7bfe91d7d4edea38ba36236d69336be0a2d Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 17:31:07 -0300 Subject: [PATCH 14/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083757964 --- sapl/parlamentares/models.py | 2 +- sapl/templates/parlamentares/mesadiretora_filter.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 957d06ee3..759835783 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -522,7 +522,7 @@ class ComposicaoMesa(models.Model): class Meta: verbose_name = _('Ocupação de cargo na Mesa') verbose_name_plural = _('Ocupações de cargo na Mesa') - ordering = ('id', ) + ordering = ('cargo', 'parlamentar') def __str__(self): return _('%(parlamentar)s - %(cargo)s') % { diff --git a/sapl/templates/parlamentares/mesadiretora_filter.html b/sapl/templates/parlamentares/mesadiretora_filter.html index 1ab1b94ef..a07c5262f 100644 --- a/sapl/templates/parlamentares/mesadiretora_filter.html +++ b/sapl/templates/parlamentares/mesadiretora_filter.html @@ -2,7 +2,7 @@ {% load i18n common_tags crispy_forms_tags cropping %} {% block container_table_list %} - {% if perms.add_mesadiretora %} + {% if perms.parlamentares.add_mesadiretora %} {{ block.super }} {% else %}
From 7a14c1ec1c2a1acf8cefd3ae85cc675dce833ee2 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 18:25:32 -0300 Subject: [PATCH 15/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093425814 --- .../0046_mesadiretora_legislatura.py | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/sapl/parlamentares/migrations/0046_mesadiretora_legislatura.py b/sapl/parlamentares/migrations/0046_mesadiretora_legislatura.py index 052527523..d7e6d59d1 100644 --- a/sapl/parlamentares/migrations/0046_mesadiretora_legislatura.py +++ b/sapl/parlamentares/migrations/0046_mesadiretora_legislatura.py @@ -5,20 +5,17 @@ import django.db.models.deletion from datetime import date def add_legislatura_to_mesa_diretora(apps, schema_editor): - MesaDiretora = apps.get_model('parlamentares', 'MesaDiretora') - SessaoLegislativa = apps.get_model('parlamentares', 'SessaoLegislativa') - - for mesa in MesaDiretora.objects.all(): - mesa.legislatura = mesa.sessao_legislativa.legislatura - data_inicio = mesa.sessao_legislativa.data_inicio - data_fim = mesa.sessao_legislativa.data_fim - if data_inicio.year == data_fim.year: - mesa.data_inicio = date(data_inicio.year, 1, 1) - mesa.data_fim = date(data_fim.year, 12, 31) - else: - mesa.data_inicio = data_inicio - mesa.data_fim = data_fim - mesa.save() + schema_editor.execute(""" + UPDATE parlamentares_mesadiretora md + SET + legislatura_id = sl.legislatura_id, + data_inicio = sl.data_inicio, + data_fim = sl.data_fim + FROM + parlamentares_sessaolegislativa sl + WHERE + sl.id = md.sessao_legislativa_id + """) class Migration(migrations.Migration): From 507cc4ad0e77dab680276a85c935a724e76a9d02 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 18:36:24 -0300 Subject: [PATCH 16/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083452572 --- .../migrations/0048_auto_20260413_1049.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/sapl/parlamentares/migrations/0048_auto_20260413_1049.py b/sapl/parlamentares/migrations/0048_auto_20260413_1049.py index ee0d1f3a0..91910292c 100644 --- a/sapl/parlamentares/migrations/0048_auto_20260413_1049.py +++ b/sapl/parlamentares/migrations/0048_auto_20260413_1049.py @@ -4,13 +4,17 @@ from django.db import migrations, models import django.db.models.deletion def preencher_titulo_mesa_diretora(apps, schema_editor): - MesaDiretora = apps.get_model('parlamentares', 'MesaDiretora') - for mesa in MesaDiretora.objects.all(): - ano_inicio = mesa.data_inicio.year if mesa.data_inicio else None - ano_fim = mesa.data_fim.year if mesa.data_fim else None - if ano_inicio and ano_fim: - mesa.titulo = f'Mesa Diretora{" Biênio" if ano_fim - ano_inicio == 1 else ""} {ano_inicio}/{ano_fim}' - mesa.save() + schema_editor.execute(""" + UPDATE parlamentares_mesadiretora + SET titulo = 'Mesa Diretora' || + CASE WHEN EXTRACT(YEAR FROM data_fim)::integer - EXTRACT(YEAR FROM data_inicio)::integer = 1 + THEN ' Biênio' + ELSE '' + END || ' ' || + EXTRACT(YEAR FROM data_inicio)::integer::text || '/' || + EXTRACT(YEAR FROM data_fim)::integer::text + WHERE data_inicio IS NOT NULL AND data_fim IS NOT NULL + """) class Migration(migrations.Migration): From c9e55dcd57fbdec1e70854ca0bfed10a8aa32391 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 19:13:01 -0300 Subject: [PATCH 17/26] fix: https://github.com/interlegis/sapl/pull/3829/changes/BASE..958e83fd2a18b5fe8d296b8bc891dc03de0f8595#r3093413994 --- sapl/parlamentares/forms.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index 2775be5a1..7da23b763 100755 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -785,17 +785,26 @@ class MesaDiretoraForm(ModelForm): def clean(self): super(MesaDiretoraForm, self).clean() - if not self.is_valid(): - return self.cleaned_data data = self.cleaned_data - legislatura = data['legislatura'] - data_inicio = data['data_inicio'] - data_fim = data['data_fim'] + + legislatura = data.get('legislatura', None) + if not legislatura: + raise ValidationError(_('Legislatura é obrigatória.')) + + data_inicio = data.get('data_inicio', None) + data_fim = data.get('data_fim', None) + + if not data_inicio or not data_fim: + raise ValidationError(_('As datas de início e fim da mesa diretora são obrigatórias.')) if data_inicio >= data_fim: raise ValidationError(_('A data de início deve ser anterior à data de fim.')) + # Verifica se as datas da mesa diretora estão dentro do intervalo da legislatura + if data_inicio < legislatura.data_inicio or data_fim > legislatura.data_fim: + raise ValidationError(_('As datas da mesa diretora devem estar dentro do período da legislatura.')) + # Verifica se há intersecção de datas com outra mesa diretora da mesma legislatura intersecao_mesadiretora = MesaDiretora.objects.filter( legislatura=legislatura, @@ -805,9 +814,6 @@ class MesaDiretoraForm(ModelForm): if intersecao_mesadiretora: raise ValidationError(_('As datas da mesa diretora se sobrepõem com outra mesa diretora existente.')) - # Verifica se as datas da mesa diretora estão dentro do intervalo da legislatura - if data_inicio < legislatura.data_inicio or data_fim > legislatura.data_fim: - raise ValidationError(_('As datas da mesa diretora devem estar dentro do período da legislatura.')) return data class ComposicaoMesaForm(ModelForm): From 3d629556a772418955cabc30d838a73bda632c6b Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 19:19:47 -0300 Subject: [PATCH 18/26] fix: reviews https://github.com/interlegis/sapl/pull/3829#discussion_r3083631421 https://github.com/interlegis/sapl/pull/3829#discussion_r3083748499 --- .../migrations/0049_auto_20260417_1917.py | 39 +++++++++++++++++++ sapl/parlamentares/models.py | 6 +-- 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 sapl/parlamentares/migrations/0049_auto_20260417_1917.py diff --git a/sapl/parlamentares/migrations/0049_auto_20260417_1917.py b/sapl/parlamentares/migrations/0049_auto_20260417_1917.py new file mode 100644 index 000000000..401e07433 --- /dev/null +++ b/sapl/parlamentares/migrations/0049_auto_20260417_1917.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.28 on 2026-04-17 22:17 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0048_auto_20260413_1049'), + ] + + operations = [ + migrations.AlterField( + model_name='composicaomesa', + name='cargo', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.CargoMesa', verbose_name='Cargo'), + ), + migrations.AlterField( + model_name='composicaomesa', + name='mesa_diretora', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='composicaomesa_set', to='parlamentares.MesaDiretora', verbose_name='Mesa Diretora'), + ), + migrations.AlterField( + model_name='composicaomesa', + name='parlamentar', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Parlamentar', verbose_name='Parlamentar'), + ), + migrations.AlterField( + model_name='mesadiretora', + name='data_fim', + field=models.DateField(verbose_name='Data Fim'), + ), + migrations.AlterField( + model_name='mesadiretora', + name='data_inicio', + field=models.DateField(verbose_name='Data Início'), + ), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 759835783..260757768 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -492,8 +492,8 @@ class CargoMesa(models.Model): class MesaDiretora(models.Model): titulo = models.CharField(max_length=100, default='', verbose_name=_('Título da Mesa Diretora')) - data_inicio = models.DateField(verbose_name=_('Data Início'), null=True) - data_fim = models.DateField(verbose_name=_('Data Fim'), null=True) + data_inicio = models.DateField(verbose_name=_('Data Início')) + data_fim = models.DateField(verbose_name=_('Data Fim')) legislatura = models.ForeignKey(Legislatura, on_delete=models.PROTECT, verbose_name=_('Legislatura')) @@ -516,7 +516,7 @@ class ComposicaoMesa(models.Model): parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT, verbose_name=_('Parlamentar')) cargo = models.ForeignKey(CargoMesa, on_delete=models.PROTECT, verbose_name=_('Cargo')) mesa_diretora = models.ForeignKey( - MesaDiretora, on_delete=models.PROTECT, null=True, + MesaDiretora, on_delete=models.PROTECT, related_name='composicaomesa_set', verbose_name=_('Mesa Diretora')) class Meta: From a6ee8d33c750229a0ae723aa80c45f943944d5da Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 19:48:46 -0300 Subject: [PATCH 19/26] fix: ajuste em subnav_mesa.yaml --- sapl/templates/parlamentares/subnav_mesa.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sapl/templates/parlamentares/subnav_mesa.yaml b/sapl/templates/parlamentares/subnav_mesa.yaml index 32779de54..5391023db 100644 --- a/sapl/templates/parlamentares/subnav_mesa.yaml +++ b/sapl/templates/parlamentares/subnav_mesa.yaml @@ -1,5 +1,5 @@ {% load i18n common_tags %} - title: {% trans 'Mesa Diretora' %} - url: sapl.parlamentares:mesadiretora_list + url: sapl.parlamentares:mesadiretora_detail - title: {% trans 'Composição da Mesa' %} url: sapl.parlamentares:composicaomesa_list \ No newline at end of file From 29c9dc09e49884ac141d46763cbb63db9146da42 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 20:00:40 -0300 Subject: [PATCH 20/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093455410 --- sapl/parlamentares/views.py | 7 ------- sapl/templates/parlamentares/layouts.yaml | 7 ------- 2 files changed, 14 deletions(-) diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index f6f4ba76f..4fac0701a 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -1052,13 +1052,6 @@ class MesaDiretoraCrud(Crud): context['subnav_template_name'] = '' return context - class DetailView(Crud.DetailView): - layout_key = 'MesaDiretoraDetail' - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - return context - - class ComposicaoMesaCrud(MasterDetailCrud): model = ComposicaoMesa parent_field = 'mesa_diretora' diff --git a/sapl/templates/parlamentares/layouts.yaml b/sapl/templates/parlamentares/layouts.yaml index 6ff9b6351..45a0efe3a 100644 --- a/sapl/templates/parlamentares/layouts.yaml +++ b/sapl/templates/parlamentares/layouts.yaml @@ -174,13 +174,6 @@ MesaDiretora: {% trans 'Descrição' %}: - descricao -MesaDiretoraDetail: - {% trans 'Período e Legislatura' %}: - - titulo - - data_inicio data_fim legislatura:6 - {% trans 'Descrição' %}: - - descricao - ComposicaoMesa: {% trans 'Composição da Mesa' %}: - cargo:4 parlamentar \ No newline at end of file From f36970c6211d2e4d9f0e7500ee48e35aeff5f312 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 20:32:57 -0300 Subject: [PATCH 21/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093434428 --- sapl/parlamentares/models.py | 4 +++- sapl/parlamentares/views.py | 6 ++++++ .../parlamentares/mesadiretora_filter.html | 14 +++++++------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 260757768..74351957e 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -298,7 +298,9 @@ class Parlamentar(models.Model): @property def filiacao_atual(self): - ultima_filiacao = self.filiacao_set.order_by('-data').first() + # este método conta com a ordenação default do model Filiacao para trazer a última filiação primeiro + # se order_by for adicionado aqui, o prefetch_related que inclui filiacao_set não irá pré-carregar como esperado + ultima_filiacao = self.filiacao_set.first() if ultima_filiacao and not ultima_filiacao.data_desfiliacao: return ultima_filiacao.partido.sigla else: diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index 4fac0701a..e98c811b3 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -1032,6 +1032,12 @@ class MesaDiretoraCrud(Crud): fk['data'] = {'legislatura': legislatura} return fk + def get_queryset(self): + return super().get_queryset().prefetch_related( + 'composicaomesa_set__parlamentar__filiacao_set__partido', + 'composicaomesa_set__cargo' + ) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['subnav_template_name'] = '' diff --git a/sapl/templates/parlamentares/mesadiretora_filter.html b/sapl/templates/parlamentares/mesadiretora_filter.html index a07c5262f..ecbcfcb81 100644 --- a/sapl/templates/parlamentares/mesadiretora_filter.html +++ b/sapl/templates/parlamentares/mesadiretora_filter.html @@ -45,19 +45,19 @@ - {% for p in md.composicaomesa_set.all %} + {% for composicao in md.composicaomesa_set.all %}
- {% if p.parlamentar.fotografia %} - + {% if composicao.parlamentar.fotografia %} + {% endif %} - - {{p.parlamentar.nome_parlamentar}} + + {{composicao.parlamentar.nome_parlamentar}}
- {{p.parlamentar.filiacao_atual}} - {{p.cargo}} + {{composicao.parlamentar.filiacao_atual}} + {{composicao.cargo}} {% endfor %} From 5faefcbec1af8686651bb845a96f4016d656e1cc Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 20:56:56 -0300 Subject: [PATCH 22/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093327707 --- sapl/parlamentares/forms.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index 7da23b763..e794cb61d 100755 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -820,38 +820,38 @@ class ComposicaoMesaForm(ModelForm): class Meta: model = ComposicaoMesa - fields = '__all__' - widgets = { - 'mesa_diretora': forms.HiddenInput() - } + fields = ( + 'parlamentar', + 'cargo' + ) def __init__(self, *args, **kwargs): super(ComposicaoMesaForm, self).__init__(*args, **kwargs) - mesa_diretora = self.initial.get('mesa_diretora', None) - - if mesa_diretora: - self.fields['parlamentar'].queryset = self.fields['parlamentar'].queryset.filter( - mandato__legislatura=mesa_diretora.legislatura) + self.fields['parlamentar'].queryset = self.fields['parlamentar'].queryset.filter( + mandato__legislatura=self.initial.get('mesa_diretora').legislatura) def clean(self): super(ComposicaoMesaForm, self).clean() - if not self.is_valid(): - return self.cleaned_data data = self.cleaned_data - mesa_diretora = data['mesa_diretora'] - cargo = data['cargo'] + cargo = data.get('cargo', None) + if not cargo: + raise ValidationError(_('Cargo é obrigatório.')) # Verifica se Parlamentar já ocupa algum cargo - parlamentar = data['parlamentar'] + parlamentar = data.get('parlamentar', None) + if not parlamentar: + raise ValidationError(_('Parlamentar é obrigatório.')) + if ComposicaoMesa.objects.filter( - parlamentar=parlamentar, mesa_diretora=mesa_diretora + mesa_diretora=self.initial.get('mesa_diretora'), + parlamentar=parlamentar, ).exclude(pk=self.instance.pk).exists(): raise ValidationError(_('Parlamentar já ocupa um cargo nesta mesa diretora.')) if cargo.unico: composicao_mesa = ComposicaoMesa.objects.filter( - mesa_diretora=mesa_diretora, + mesa_diretora=self.initial.get('mesa_diretora'), cargo=cargo ).exclude(pk=self.instance.pk) if composicao_mesa.exists(): From fcb2de67317fea8c38c14851b6493ecb3816d6b6 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 21:02:26 -0300 Subject: [PATCH 23/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093438428 --- sapl/templates/crud/list.html | 15 ++++++++------- .../parlamentares/mesadiretora_filter.html | 8 ++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/sapl/templates/crud/list.html b/sapl/templates/crud/list.html index 9e6d79cc9..02a943205 100644 --- a/sapl/templates/crud/list.html +++ b/sapl/templates/crud/list.html @@ -4,13 +4,14 @@ {% block base_content %}
- + {% block actions_search %} + + {% endblock actions_search %} + {% block actions %}
{% if view.create_url %} diff --git a/sapl/templates/parlamentares/mesadiretora_filter.html b/sapl/templates/parlamentares/mesadiretora_filter.html index ecbcfcb81..11dfaf0f2 100644 --- a/sapl/templates/parlamentares/mesadiretora_filter.html +++ b/sapl/templates/parlamentares/mesadiretora_filter.html @@ -1,6 +1,14 @@ {% extends "crud/list.html" %} {% load i18n common_tags crispy_forms_tags cropping %} +{% block actions_search %} + +{% endblock actions_search %} + {% block container_table_list %} {% if perms.parlamentares.add_mesadiretora %} {{ block.super }} From f777e0289849a40c1c7e842b4ebc6e31d360e5a0 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 21:54:35 -0300 Subject: [PATCH 24/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093737607 --- sapl/parlamentares/models.py | 4 +++- sapl/parlamentares/views.py | 21 +++++++++++++------ .../parlamentares/mesadiretora_filter.html | 10 +++------ 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 74351957e..1e3266074 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -498,7 +498,9 @@ class MesaDiretora(models.Model): data_fim = models.DateField(verbose_name=_('Data Fim')) legislatura = models.ForeignKey(Legislatura, on_delete=models.PROTECT, - verbose_name=_('Legislatura')) + verbose_name=_('Legislatura'), + related_name='mesadiretora_set' + ) descricao = models.TextField(verbose_name=_('Descrição'), blank=True) class Meta: diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index e98c811b3..ac9ef2620 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -9,7 +9,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.db.models import F, Q from django.db.models.aggregates import Count -from django.http import JsonResponse +from django.http import Http404, JsonResponse from django.http.response import HttpResponseRedirect from django.shortcuts import render from django.templatetags.static import static @@ -1023,13 +1023,22 @@ class MesaDiretoraCrud(Crud): filterset_class = MesaDiretoraFilterSet paginate_by = None + def get_id_legislatura_atual(self): + return Legislatura.objects.filter( + data_inicio__lte=timezone.now() + ).order_by('-data_inicio').values_list('id', flat=True).first() + def get_filterset_kwargs(self, filterset_class): fk = super().get_filterset_kwargs(filterset_class) - if 'legislatura' not in self.request.GET: - legislatura = Legislatura.objects.filter( - data_inicio__lte=timezone.now()).order_by('-data_inicio').values_list('id', flat=True).first() - if legislatura: - fk['data'] = {'legislatura': legislatura} + if 'legislatura' not in self.request.GET and not 'mesa' in self.request.GET: + fk['data'] = {'legislatura': self.get_id_legislatura_atual()} + elif 'legislatura' not in self.request.GET and 'mesa' in self.request.GET: + legislatura_da_mesa = Legislatura.objects.filter( + mesadiretora_set__id=self.request.GET['mesa'] + ).values_list('id', flat=True).first() + if not legislatura_da_mesa: + raise Http404("MesaDiretora {} não encontrada.".format(self.request.GET['mesa'])) + fk['data'] = {'legislatura': legislatura_da_mesa} return fk def get_queryset(self): diff --git a/sapl/templates/parlamentares/mesadiretora_filter.html b/sapl/templates/parlamentares/mesadiretora_filter.html index 11dfaf0f2..a1ec31a73 100644 --- a/sapl/templates/parlamentares/mesadiretora_filter.html +++ b/sapl/templates/parlamentares/mesadiretora_filter.html @@ -18,11 +18,7 @@
{% for md in object_list %} - {% if forloop.first or request.GET.mesa == md.id|stringformat:"s" %} + {% if request.GET.mesa == md.id|stringformat:"s" or not request.GET.mesa and forloop.first %}
{% else %}
{% endif %} (De {{md.data_inicio }} a {{ md.data_fim }}) - + From 98e687f4af87a42fde3d897e0c79236d3673ab42 Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 22:30:08 -0300 Subject: [PATCH 25/26] fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093543652 --- sapl/parlamentares/forms.py | 61 +----- sapl/parlamentares/models.py | 38 ++++ sapl/parlamentares/tests/test_mesadiretora.py | 181 ++++++++++++++++-- 3 files changed, 208 insertions(+), 72 deletions(-) diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index e794cb61d..7acb4a984 100755 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -783,38 +783,6 @@ class MesaDiretoraForm(ModelForm): model = MesaDiretora fields = '__all__' - def clean(self): - super(MesaDiretoraForm, self).clean() - - data = self.cleaned_data - - legislatura = data.get('legislatura', None) - if not legislatura: - raise ValidationError(_('Legislatura é obrigatória.')) - - data_inicio = data.get('data_inicio', None) - data_fim = data.get('data_fim', None) - - if not data_inicio or not data_fim: - raise ValidationError(_('As datas de início e fim da mesa diretora são obrigatórias.')) - - if data_inicio >= data_fim: - raise ValidationError(_('A data de início deve ser anterior à data de fim.')) - - # Verifica se as datas da mesa diretora estão dentro do intervalo da legislatura - if data_inicio < legislatura.data_inicio or data_fim > legislatura.data_fim: - raise ValidationError(_('As datas da mesa diretora devem estar dentro do período da legislatura.')) - - # Verifica se há intersecção de datas com outra mesa diretora da mesma legislatura - intersecao_mesadiretora = MesaDiretora.objects.filter( - legislatura=legislatura, - data_inicio__lte=data_fim, - data_fim__gte=data_inicio - ).exclude(pk=self.instance.pk).exists() - if intersecao_mesadiretora: - raise ValidationError(_('As datas da mesa diretora se sobrepõem com outra mesa diretora existente.')) - - return data class ComposicaoMesaForm(ModelForm): @@ -827,33 +795,6 @@ class ComposicaoMesaForm(ModelForm): def __init__(self, *args, **kwargs): super(ComposicaoMesaForm, self).__init__(*args, **kwargs) + self.instance.mesa_diretora = self.initial.get('mesa_diretora') self.fields['parlamentar'].queryset = self.fields['parlamentar'].queryset.filter( mandato__legislatura=self.initial.get('mesa_diretora').legislatura) - - def clean(self): - super(ComposicaoMesaForm, self).clean() - - data = self.cleaned_data - cargo = data.get('cargo', None) - if not cargo: - raise ValidationError(_('Cargo é obrigatório.')) - - # Verifica se Parlamentar já ocupa algum cargo - parlamentar = data.get('parlamentar', None) - if not parlamentar: - raise ValidationError(_('Parlamentar é obrigatório.')) - - if ComposicaoMesa.objects.filter( - mesa_diretora=self.initial.get('mesa_diretora'), - parlamentar=parlamentar, - ).exclude(pk=self.instance.pk).exists(): - raise ValidationError(_('Parlamentar já ocupa um cargo nesta mesa diretora.')) - - if cargo.unico: - composicao_mesa = ComposicaoMesa.objects.filter( - mesa_diretora=self.initial.get('mesa_diretora'), - cargo=cargo - ).exclude(pk=self.instance.pk) - if composicao_mesa.exists(): - raise ValidationError(_('Cargo único já ocupado por outro parlamentar.')) - return data diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index 1e3266074..0ca2c8356 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -1,4 +1,5 @@ +from django.core.exceptions import ValidationError from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -515,6 +516,25 @@ class MesaDiretora(models.Model): 'data_fim': self.data_fim } + def clean(self): + if self.data_inicio and self.data_fim: + if self.data_inicio >= self.data_fim: + raise ValidationError( + _('A data de início deve ser anterior à data de fim.')) + + if self.legislatura_id: + if self.data_inicio < self.legislatura.data_inicio or self.data_fim > self.legislatura.data_fim: + raise ValidationError( + _('As datas da mesa diretora devem estar dentro do período da legislatura.')) + + if MesaDiretora.objects.filter( + legislatura=self.legislatura, + data_inicio__lte=self.data_fim, + data_fim__gte=self.data_inicio + ).exclude(pk=self.pk).exists(): + raise ValidationError( + _('As datas da mesa diretora se sobrepõem com outra mesa diretora existente.')) + class ComposicaoMesa(models.Model): parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT, verbose_name=_('Parlamentar')) @@ -533,6 +553,24 @@ class ComposicaoMesa(models.Model): 'parlamentar': self.parlamentar, 'cargo': self.cargo } + def clean(self): + if self.parlamentar_id and self.mesa_diretora_id: + if ComposicaoMesa.objects.filter( + mesa_diretora=self.mesa_diretora, + parlamentar=self.parlamentar, + ).exclude(pk=self.pk).exists(): + raise ValidationError( + _('Parlamentar já ocupa um cargo nesta mesa diretora.')) + + if self.cargo_id and self.mesa_diretora_id: + if self.cargo.unico: + if ComposicaoMesa.objects.filter( + mesa_diretora=self.mesa_diretora, + cargo=self.cargo + ).exclude(pk=self.pk).exists(): + raise ValidationError( + _('Cargo único já ocupado por outro parlamentar.')) + class Frente(models.Model): ''' diff --git a/sapl/parlamentares/tests/test_mesadiretora.py b/sapl/parlamentares/tests/test_mesadiretora.py index ff663488d..053cef26c 100644 --- a/sapl/parlamentares/tests/test_mesadiretora.py +++ b/sapl/parlamentares/tests/test_mesadiretora.py @@ -1,4 +1,7 @@ import pytest +from datetime import date + +from django.core.exceptions import ValidationError from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from model_bakery import baker @@ -7,6 +10,159 @@ from sapl.parlamentares.forms import ComposicaoMesaForm, MesaDiretoraForm from sapl.parlamentares.models import ComposicaoMesa, MesaDiretora +# ===================================================================== +# Testes de validação a nível de Model — MesaDiretora +# ===================================================================== + +@pytest.mark.django_db(transaction=False) +def test_mesadiretora_model_clean_data_inicio_maior_que_data_fim(): + legislatura = baker.make( + 'parlamentares.Legislatura', + data_inicio=date(2021, 1, 1), + data_fim=date(2024, 12, 31) + ) + mesa = MesaDiretora( + titulo='Mesa', + data_inicio=date(2022, 1, 1), + data_fim=date(2021, 12, 31), + legislatura=legislatura + ) + with pytest.raises(ValidationError, match='A data de início deve ser anterior à data de fim.'): + mesa.clean() + + +@pytest.mark.django_db(transaction=False) +def test_mesadiretora_model_clean_data_fora_da_legislatura(): + legislatura = baker.make( + 'parlamentares.Legislatura', + data_inicio=date(2021, 1, 1), + data_fim=date(2024, 12, 31) + ) + mesa = MesaDiretora( + titulo='Mesa', + data_inicio=date(2020, 1, 1), + data_fim=date(2021, 12, 31), + legislatura=legislatura + ) + with pytest.raises(ValidationError, match='As datas da mesa diretora devem estar dentro do período da legislatura.'): + mesa.clean() + + +@pytest.mark.django_db(transaction=False) +def test_mesadiretora_model_clean_intersecao(): + legislatura = baker.make( + 'parlamentares.Legislatura', + data_inicio=date(2021, 1, 1), + data_fim=date(2024, 12, 31) + ) + baker.make( + 'parlamentares.MesaDiretora', + legislatura=legislatura, + titulo='Mesa Existente', + data_inicio=date(2021, 1, 1), + data_fim=date(2022, 12, 31) + ) + mesa = MesaDiretora( + titulo='Nova Mesa', + data_inicio=date(2022, 1, 1), + data_fim=date(2023, 12, 31), + legislatura=legislatura + ) + with pytest.raises(ValidationError, match='As datas da mesa diretora se sobrepõem com outra mesa diretora existente.'): + mesa.clean() + + +@pytest.mark.django_db(transaction=False) +def test_mesadiretora_model_clean_valido(): + legislatura = baker.make( + 'parlamentares.Legislatura', + data_inicio=date(2021, 1, 1), + data_fim=date(2024, 12, 31) + ) + mesa = MesaDiretora( + titulo='Mesa', + data_inicio=date(2021, 1, 1), + data_fim=date(2022, 12, 31), + legislatura=legislatura + ) + mesa.clean() # não deve lançar exceção + + +@pytest.mark.django_db(transaction=False) +def test_mesadiretora_model_full_clean_sem_data_inicio(): + legislatura = baker.make( + 'parlamentares.Legislatura', + data_inicio=date(2021, 1, 1), + data_fim=date(2024, 12, 31) + ) + mesa = MesaDiretora( + titulo='Mesa', + data_fim=date(2022, 12, 31), + legislatura=legislatura + ) + with pytest.raises(ValidationError) as exc_info: + mesa.full_clean() + + assert 'data_inicio' in exc_info.value.message_dict + assert exc_info.value.message_dict['data_inicio'] == [_('Este campo não pode ser nulo.')] + + +# ===================================================================== +# Testes de validação a nível de Model — ComposicaoMesa +# ===================================================================== + +@pytest.mark.django_db(transaction=False) +def test_composicaomesa_model_clean_parlamentar_duplicado(): + parlamentar = baker.make('parlamentares.Parlamentar') + cargo1 = baker.make('parlamentares.CargoMesa') + cargo2 = baker.make('parlamentares.CargoMesa') + mesa_diretora = baker.make('parlamentares.MesaDiretora') + + ComposicaoMesa.objects.create( + parlamentar=parlamentar, cargo=cargo1, mesa_diretora=mesa_diretora + ) + + composicao = ComposicaoMesa( + parlamentar=parlamentar, cargo=cargo2, mesa_diretora=mesa_diretora + ) + with pytest.raises(ValidationError, match='Parlamentar já ocupa um cargo nesta mesa diretora.'): + composicao.clean() + + +@pytest.mark.django_db(transaction=False) +def test_composicaomesa_model_clean_cargo_unico(): + parlamentar1 = baker.make('parlamentares.Parlamentar') + parlamentar2 = baker.make('parlamentares.Parlamentar') + cargo = baker.make('parlamentares.CargoMesa', unico=True) + mesa_diretora = baker.make('parlamentares.MesaDiretora') + + ComposicaoMesa.objects.create( + parlamentar=parlamentar1, cargo=cargo, mesa_diretora=mesa_diretora + ) + + composicao = ComposicaoMesa( + parlamentar=parlamentar2, cargo=cargo, mesa_diretora=mesa_diretora + ) + with pytest.raises(ValidationError, match='Cargo único já ocupado por outro parlamentar.'): + composicao.clean() + + +@pytest.mark.django_db(transaction=False) +def test_composicaomesa_model_clean_valido(): + parlamentar = baker.make('parlamentares.Parlamentar') + cargo = baker.make('parlamentares.CargoMesa') + mesa_diretora = baker.make('parlamentares.MesaDiretora') + + composicao = ComposicaoMesa( + parlamentar=parlamentar, cargo=cargo, mesa_diretora=mesa_diretora + ) + composicao.clean() # não deve lançar exceção + + +# ===================================================================== +# Testes de validação via Form — MesaDiretora +# ===================================================================== + def test_mesadiretora_form_invalido(): form = MesaDiretoraForm(data={}) @@ -64,7 +220,7 @@ def test_mesadiretora_form_intersecao(): data_inicio='2021-01-01', data_fim='2024-12-31' ) - mesa_existente = baker.make( + baker.make( 'parlamentares.MesaDiretora', legislatura=legislatura, titulo='Mesa Diretora 2021-2022', @@ -102,8 +258,14 @@ def test_mesadiretora_form_data_fora_da_legislatura(): assert errors['__all__'] == [_('As datas da mesa diretora devem estar dentro do período da legislatura.')] -def test_composicaomesa_form(): - form = ComposicaoMesaForm(data={}) +# ===================================================================== +# Testes de validação via Form — ComposicaoMesa +# ===================================================================== + +@pytest.mark.django_db(transaction=False) +def test_composicaomesa_form_invalido(): + mesa_diretora = baker.make('parlamentares.MesaDiretora') + form = ComposicaoMesaForm(data={}, initial={'mesa_diretora': mesa_diretora}) assert not form.is_valid() @@ -111,8 +273,6 @@ def test_composicaomesa_form(): assert errors['parlamentar'] == [_('Este campo é obrigatório.')] assert errors['cargo'] == [_('Este campo é obrigatório.')] - assert errors['mesa_diretora'] == [_('Este campo é obrigatório.')] - @pytest.mark.django_db(transaction=False) @@ -128,7 +288,6 @@ def test_composicaomesa_form_valido(): form = ComposicaoMesaForm(data={ 'parlamentar': parlamentar.id, 'cargo': cargo.id, - 'mesa_diretora': mesa_diretora.id, }, initial={ 'mesa_diretora': mesa_diretora, }) @@ -146,7 +305,6 @@ def test_composicaomesa_form_parlamentar_ocupando_cargo_na_mesma_mesa(): parlamentar=parlamentar, legislatura=mesa_diretora.legislatura) - # Cria uma composição de mesa existente com o mesmo parlamentar e mesa ComposicaoMesa.objects.create( parlamentar=parlamentar, cargo=cargo1, @@ -156,7 +314,6 @@ def test_composicaomesa_form_parlamentar_ocupando_cargo_na_mesma_mesa(): form = ComposicaoMesaForm(data={ 'parlamentar': parlamentar.id, 'cargo': cargo2.id, - 'mesa_diretora': mesa_diretora.id, }, initial={ 'mesa_diretora': mesa_diretora, }) @@ -178,7 +335,6 @@ def test_composicaomesa_form_parlamentar_cargo_unico_mesma_mesa(): mandato1 = baker.make('parlamentares.Mandato', parlamentar=parlamentar1, legislatura=mesa_diretora.legislatura) mandato2 = baker.make('parlamentares.Mandato', parlamentar=parlamentar2, legislatura=mesa_diretora.legislatura) - # Cria uma composição de mesa existente com o cargo único ComposicaoMesa.objects.create( parlamentar=parlamentar1, cargo=cargo, @@ -188,7 +344,6 @@ def test_composicaomesa_form_parlamentar_cargo_unico_mesma_mesa(): form = ComposicaoMesaForm(data={ 'parlamentar': parlamentar2.id, 'cargo': cargo.id, - 'mesa_diretora': mesa_diretora.id, }, initial={ 'mesa_diretora': mesa_diretora, }) @@ -198,6 +353,10 @@ def test_composicaomesa_form_parlamentar_cargo_unico_mesma_mesa(): assert errors['__all__'] == [_('Cargo único já ocupado por outro parlamentar.')] +# ===================================================================== +# Testes de integração via View — ComposicaoMesa +# ===================================================================== + @pytest.mark.django_db(transaction=False) def test_composicaomesa_form_view_create(admin_client): parlamentar = baker.make('parlamentares.Parlamentar') @@ -211,7 +370,6 @@ def test_composicaomesa_form_view_create(admin_client): response = admin_client.post(reverse('sapl.parlamentares:composicaomesa_create', kwargs={'pk': mesa_diretora.id}), data={ 'parlamentar': parlamentar.id, 'cargo': cargo.id, - 'mesa_diretora': mesa_diretora.id, }) assert ComposicaoMesa.objects.filter(parlamentar=parlamentar, cargo=cargo, mesa_diretora=mesa_diretora).exists() @@ -237,7 +395,6 @@ def test_composicaomesa_form_view_update(admin_client): response = admin_client.post(reverse('sapl.parlamentares:composicaomesa_update', kwargs={'pk': composicao.id}), data={ 'parlamentar': parlamentar.id, 'cargo': new_cargo.id, - 'mesa_diretora': mesa_diretora.id, }) composicao.refresh_from_db() From 1b144e48b058b31cd79041e5d7209993c9a1e43c Mon Sep 17 00:00:00 2001 From: LeandroJatai Date: Fri, 17 Apr 2026 22:35:56 -0300 Subject: [PATCH 26/26] fix: ajuste de related_name --- .../migrations/0050_auto_20260417_2235.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 sapl/parlamentares/migrations/0050_auto_20260417_2235.py diff --git a/sapl/parlamentares/migrations/0050_auto_20260417_2235.py b/sapl/parlamentares/migrations/0050_auto_20260417_2235.py new file mode 100644 index 000000000..99ba7bfc9 --- /dev/null +++ b/sapl/parlamentares/migrations/0050_auto_20260417_2235.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.28 on 2026-04-18 01:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0049_auto_20260417_1917'), + ] + + operations = [ + migrations.AlterField( + model_name='mesadiretora', + name='legislatura', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='mesadiretora_set', to='parlamentares.Legislatura', verbose_name='Legislatura'), + ), + ]