Browse Source

Merge fa3d7aa40a into 491838c14e

pull/3822/merge
LeandroJataí 1 day ago
committed by GitHub
parent
commit
df0d91f94c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 56
      sapl/api/views_materia.py
  2. 45
      sapl/materia/forms.py
  3. 106
      sapl/materia/models.py
  4. 385
      sapl/materia/tests/test_materia_proximo_numero.py
  5. 51
      sapl/materia/views.py

56
sapl/api/views_materia.py

@ -1,7 +1,9 @@
from django.apps.registry import apps
from django.db import IntegrityError, transaction
from django.db.models import Q
from rest_framework.decorators import action
from rest_framework.status import HTTP_201_CREATED, HTTP_409_CONFLICT
from rest_framework.response import Response
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
@ -90,6 +92,60 @@ class _MateriaLegislativaViewSet:
class Meta:
ordering = ['-ano', 'tipo', 'numero']
_MAX_RETRIES_NUMERO = 3
def create(self, request, *args, **kwargs):
data = dict(request.data)
tipo = data.get('tipo', None)
numero = data.get('numero', None)
ano = data.get('ano', None)
if tipo and not numero:
# Número não fornecido pelo cliente: auto-gerar próximo disponível.
# select_for_update() em get_proximo_numero previne race conditions.
# Retry como camada extra de segurança contra IntegrityError residual.
for tentativa in range(self._MAX_RETRIES_NUMERO):
try:
with transaction.atomic():
numero_gerado, ano_gerado = MateriaLegislativa.get_proximo_numero(
tipo=tipo, ano=ano
)
data['numero'] = numero_gerado
data['ano'] = ano_gerado
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=HTTP_201_CREATED, headers=headers)
except IntegrityError:
if tentativa == self._MAX_RETRIES_NUMERO - 1:
return Response(
{'detail': 'Não foi possível gerar um número único após '
'%d tentativas. Tente novamente.' % self._MAX_RETRIES_NUMERO},
status=HTTP_409_CONFLICT
)
continue
# Número fornecido pelo cliente (ou tipo ausente):
# respeitar os dados enviados. Se houver conflito de unicidade,
# retornar erro explícito em vez de sobrescrever silenciosamente.
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
try:
with transaction.atomic():
self.perform_create(serializer)
except IntegrityError:
return Response(
{'numero': [
'O número %s já está em uso para este tipo/ano. '
'Remova o campo "numero" para auto-gerar o próximo disponível.' % numero
]},
status=HTTP_409_CONFLICT
)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=HTTP_201_CREATED, headers=headers)
@action(detail=True, methods=['GET'])
def ultima_tramitacao(self, request, *args, **kwargs):

45
sapl/materia/forms.py

@ -2458,47 +2458,12 @@ class ConfirmarProposicaoForm(ProposicaoForm):
if self.instance.tipo.content_type.model_class(
) == TipoMateriaLegislativa:
numeracao = None
try:
self.logger.debug(
"Tentando obter modelo de sequência de numeração.")
numeracao = BaseAppConfig.objects.last(
).sequencia_numeracao_protocolo
except AttributeError as e:
self.logger.error("Erro ao obter modelo. " + str(e))
pass
tipo = self.instance.tipo.tipo_conteudo_related
if tipo.sequencia_numeracao:
numeracao = tipo.sequencia_numeracao
ano = timezone.now().year
if numeracao == 'A':
numero = MateriaLegislativa.objects.filter(
ano=ano, tipo=tipo).aggregate(Max('numero'))
elif numeracao == 'L':
legislatura = Legislatura.objects.filter(
data_inicio__year__lte=ano,
data_fim__year__gte=ano).first()
data_inicio = legislatura.data_inicio
data_fim = legislatura.data_fim
numero = MateriaLegislativa.objects.filter(
data_apresentacao__gte=data_inicio,
data_apresentacao__lte=data_fim,
tipo=tipo).aggregate(
Max('numero'))
elif numeracao == 'U':
numero = MateriaLegislativa.objects.filter(
tipo=tipo).aggregate(Max('numero'))
if numeracao is None:
numero['numero__max'] = 0
if cd['numero_materia_futuro'] and not MateriaLegislativa.objects.filter(tipo=tipo,
ano=ano,
numero=cd['numero_materia_futuro']):
max_numero = cd['numero_materia_futuro']
else:
max_numero = numero['numero__max'] + \
1 if numero['numero__max'] else 1
max_numero, ano = MateriaLegislativa.get_proximo_numero(
tipo=tipo,
ano=None,
numero_candidato=cd.get('numero_materia_futuro', None)
)
# dados básicos
materia = MateriaLegislativa()

106
sapl/materia/models.py

@ -3,15 +3,19 @@ from datetime import datetime
from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Max
from django.db.models.functions import Concat
from django.template import defaultfilters
from django.utils import formats, timezone
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
from sapl.base.models import SEQUENCIA_NUMERACAO_PROTOCOLO, Autor
from sapl.base.models import SEQUENCIA_NUMERACAO_PROTOCOLO, Autor, AppConfig as BaseAppConfig
from sapl.comissoes.models import Comissao, Reuniao
from sapl.parlamentares.models import Legislatura
from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
TextoArticulado)
from sapl.parlamentares.models import Parlamentar
@ -382,6 +386,106 @@ class MateriaLegislativa(models.Model):
using=using,
update_fields=update_fields)
@staticmethod
def get_proximo_numero(tipo, ano=None, numero_candidato=None):
"""
Retorna o próximo número disponível para uma MateriaLegislativa
baseado no tipo e nas configurações de numeração.
IMPORTANTE: Este método utiliza select_for_update() e DEVE ser
chamado dentro de uma transação (transaction.atomic) para garantir
proteção contra race conditions em acessos concorrentes.
Args:
tipo: TipoMateriaLegislativa ou int/str - o tipo da matéria
ano: int - o ano da matéria (default: ano atual)
numero_candidato: int - número candidato/desejado (opcional).
Se fornecido e disponível, será retornado. Caso contrário,
retorna o próximo sequencial.
Returns:
tuple[int, int]: Uma tupla contendo (numero, ano) da matéria.
"""
if ano is None:
ano = timezone.now().year
# Obtém a configuração de numeração
numeracao = None
try:
numeracao = BaseAppConfig.objects.last(
).sequencia_numeracao_protocolo
except AttributeError:
pass
if not isinstance(tipo, TipoMateriaLegislativa):
if tipo is None:
raise ValidationError(_("O tipo é obrigatório."))
try:
tipo_id = int(tipo)
except (ValueError, TypeError):
raise ValidationError(_("Tipo inválido: '%s'") % tipo)
try:
tipo = TipoMateriaLegislativa.objects.get(pk=tipo_id)
except TipoMateriaLegislativa.DoesNotExist:
raise TipoMateriaLegislativa.DoesNotExist(
_("TipoMateriaLegislativa with pk '%s' does not exist.") % tipo_id
)
# Lock na linha do TipoMateriaLegislativa para serializar
# gerações concorrentes de número do mesmo tipo.
# Requer que o chamador esteja dentro de transaction.atomic().
TipoMateriaLegislativa.objects.select_for_update().get(pk=tipo.pk)
# O tipo pode sobrescrever a configuração global
if tipo.sequencia_numeracao:
numeracao = tipo.sequencia_numeracao
# Calcula o próximo número baseado no tipo de numeração
materias_select_for_update = MateriaLegislativa.objects.select_for_update()
if numeracao == 'A': # Por ano
numero = materias_select_for_update.filter(
ano=ano, tipo=tipo).aggregate(Max('numero'))
elif numeracao == 'L': # Por legislatura
legislatura = Legislatura.objects.filter(
data_inicio__year__lte=ano,
data_fim__year__gte=ano).first()
if legislatura:
data_inicio = legislatura.data_inicio
data_fim = legislatura.data_fim
numero = materias_select_for_update.filter(
data_apresentacao__gte=data_inicio,
data_apresentacao__lte=data_fim,
tipo=tipo).aggregate(Max('numero'))
else:
numero = {'numero__max': 0}
elif numeracao == 'U': # Único/Universal
numero = materias_select_for_update.filter(
tipo=tipo).aggregate(Max('numero'))
else:
numero = {'numero__max': 0}
# Converte o número candidato para inteiro, se possível
numero_candidato_int = None
if numero_candidato is not None:
try:
numero_candidato_int = int(numero_candidato)
except (TypeError, ValueError):
numero_candidato_int = None
# Verifica se o número candidato está disponível
if numero_candidato_int is not None and not materias_select_for_update.filter(
tipo=tipo,
ano=ano,
numero=numero_candidato_int).exists():
return numero_candidato_int, ano
# Retorna o próximo número sequencial
max_numero = numero['numero__max']
return ((max_numero + 1) if max_numero else 1), ano
class Autoria(models.Model):
autor = models.ForeignKey(Autor,

385
sapl/materia/tests/test_materia_proximo_numero.py

@ -0,0 +1,385 @@
from datetime import date
import pytest
from django.db import IntegrityError, transaction
from model_bakery import baker
from rest_framework.test import APIClient
from sapl.base.models import AppConfig as BaseAppConfig
from sapl.materia.models import (MateriaLegislativa, RegimeTramitacao,
TipoMateriaLegislativa)
from sapl.parlamentares.models import Legislatura
# ---------------------------------------------------------------------------
# Helpers / fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def tipo_materia(db):
"""TipoMateriaLegislativa sem sequencia_numeracao própria (usa global)."""
return baker.make(TipoMateriaLegislativa, descricao='Projeto de Lei',
sigla='PL', sequencia_numeracao='')
@pytest.fixture
def tipo_materia_anual(db):
return baker.make(TipoMateriaLegislativa, descricao='Requerimento',
sigla='REQ', sequencia_numeracao='A')
@pytest.fixture
def tipo_materia_unico(db):
return baker.make(TipoMateriaLegislativa, descricao='Indicação',
sigla='IND', sequencia_numeracao='U')
@pytest.fixture
def tipo_materia_legislatura(db):
return baker.make(TipoMateriaLegislativa, descricao='PEC',
sigla='PEC', sequencia_numeracao='L')
@pytest.fixture
def regime(db):
return baker.make(RegimeTramitacao, descricao='Normal')
@pytest.fixture
def app_config(db):
return BaseAppConfig.objects.create(sequencia_numeracao_protocolo='A')
def _criar_materia(tipo, numero, ano, regime, **kwargs):
"""Atalho para criar MateriaLegislativa com campos obrigatórios."""
defaults = dict(
tipo=tipo,
numero=numero,
ano=ano,
data_apresentacao=date(ano, 1, 1),
regime_tramitacao=regime,
ementa='Ementa de teste',
)
defaults.update(kwargs)
return MateriaLegislativa.objects.create(**defaults)
# ===========================================================================
# Testes de get_proximo_numero – numeração por ano (padrão)
# ===========================================================================
@pytest.mark.django_db(transaction=False)
def test_proximo_numero_primeiro_do_ano(tipo_materia_anual, regime, app_config):
"""Sem matérias existentes, o primeiro número gerado deve ser 1."""
with transaction.atomic():
numero, ano = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_anual, ano=2024)
assert numero == 1
assert ano == 2024
@pytest.mark.django_db(transaction=False)
def test_proximo_numero_sequencial(tipo_materia_anual, regime, app_config):
"""Com matérias existentes 1, 2, 3 → próximo deve ser 4."""
for n in (1, 2, 3):
_criar_materia(tipo_materia_anual, n, 2024, regime)
with transaction.atomic():
numero, ano = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_anual, ano=2024)
assert numero == 4
assert ano == 2024
@pytest.mark.django_db(transaction=False)
def test_proximo_numero_ignora_outro_ano(tipo_materia_anual, regime, app_config):
"""Matérias em anos diferentes não influenciam a sequência."""
_criar_materia(tipo_materia_anual, 10, 2023, regime,
data_apresentacao=date(2023, 6, 1))
_criar_materia(tipo_materia_anual, 1, 2024, regime)
with transaction.atomic():
numero, ano = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_anual, ano=2024)
assert numero == 2
@pytest.mark.django_db(transaction=False)
def test_proximo_numero_ignora_outro_tipo(tipo_materia_anual, regime, app_config):
"""Matérias de outro tipo não influenciam a sequência."""
outro_tipo = baker.make(TipoMateriaLegislativa, descricao='Moção',
sigla='MOC', sequencia_numeracao='A')
_criar_materia(outro_tipo, 50, 2024, regime)
_criar_materia(tipo_materia_anual, 3, 2024, regime)
with transaction.atomic():
numero, _ = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_anual, ano=2024)
assert numero == 4
# ===========================================================================
# Testes de get_proximo_numero – numeração única
# ===========================================================================
@pytest.mark.django_db(transaction=False)
def test_proximo_numero_unico(tipo_materia_unico, regime, app_config):
"""Numeração 'U' ignora o ano – sequência é global por tipo."""
_criar_materia(tipo_materia_unico, 1, 2022, regime,
data_apresentacao=date(2022, 1, 1))
_criar_materia(tipo_materia_unico, 2, 2023, regime,
data_apresentacao=date(2023, 1, 1))
with transaction.atomic():
numero, _ = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_unico, ano=2024)
assert numero == 3
# ===========================================================================
# Testes de get_proximo_numero – numeração por legislatura
# ===========================================================================
@pytest.mark.django_db(transaction=False)
def test_proximo_numero_por_legislatura(tipo_materia_legislatura, regime,
app_config):
"""Numeração 'L' conta apenas matérias dentro da legislatura vigente."""
baker.make(Legislatura, numero=1,
data_inicio=date(2021, 1, 1),
data_fim=date(2024, 12, 31),
data_eleicao=date(2020, 11, 15))
_criar_materia(tipo_materia_legislatura, 1, 2022, regime,
data_apresentacao=date(2022, 3, 1))
_criar_materia(tipo_materia_legislatura, 2, 2023, regime,
data_apresentacao=date(2023, 5, 1))
with transaction.atomic():
numero, _ = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_legislatura, ano=2024)
assert numero == 3
@pytest.mark.django_db(transaction=False)
def test_proximo_numero_legislatura_inexistente(tipo_materia_legislatura,
regime, app_config):
"""Sem legislatura vigente, número inicia em 1."""
with transaction.atomic():
numero, _ = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_legislatura, ano=2050)
assert numero == 1
# ===========================================================================
# Testes de get_proximo_numero – numero_candidato
# ===========================================================================
@pytest.mark.django_db(transaction=False)
def test_numero_candidato_disponivel(tipo_materia_anual, regime, app_config):
"""Se o número candidato está disponível, deve ser retornado."""
_criar_materia(tipo_materia_anual, 1, 2024, regime)
with transaction.atomic():
numero, _ = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_anual, ano=2024, numero_candidato=5)
assert numero == 5
@pytest.mark.django_db(transaction=False)
def test_numero_candidato_em_uso_retorna_sequencial(tipo_materia_anual,
regime, app_config):
"""Se o número candidato já existe, retorna o sequencial."""
_criar_materia(tipo_materia_anual, 5, 2024, regime)
with transaction.atomic():
numero, _ = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_anual, ano=2024, numero_candidato=5)
assert numero == 6
# ===========================================================================
# Testes de get_proximo_numero – resolução de tipo por pk (int/str)
# ===========================================================================
@pytest.mark.django_db(transaction=False)
def test_tipo_passado_como_int(tipo_materia_anual, app_config):
"""O tipo pode ser passado como int (pk)."""
with transaction.atomic():
numero, _ = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_anual.pk, ano=2024)
assert numero == 1
@pytest.mark.django_db(transaction=False)
def test_tipo_passado_como_str(tipo_materia_anual, app_config):
"""O tipo pode ser passado como str representando o pk."""
with transaction.atomic():
numero, _ = MateriaLegislativa.get_proximo_numero(
tipo=str(tipo_materia_anual.pk), ano=2024)
assert numero == 1
@pytest.mark.django_db(transaction=False)
def test_tipo_inexistente_levanta_excecao(app_config):
"""Pk inexistente deve levantar DoesNotExist."""
with pytest.raises(TipoMateriaLegislativa.DoesNotExist):
with transaction.atomic():
MateriaLegislativa.get_proximo_numero(tipo=99999, ano=2024)
@pytest.mark.django_db(transaction=False)
def test_tipo_invalido_levanta_validation_error(app_config):
"""Tipo não conversível para int deve levantar ValidationError."""
from django.core.exceptions import ValidationError
with pytest.raises(ValidationError):
with transaction.atomic():
MateriaLegislativa.get_proximo_numero(tipo='abc', ano=2024)
# ===========================================================================
# Testes de get_proximo_numero – ano padrão
# ===========================================================================
@pytest.mark.django_db(transaction=False)
def test_ano_none_usa_ano_atual(tipo_materia_anual, app_config):
"""Se ano=None, deve usar o ano corrente."""
from django.utils import timezone
with transaction.atomic():
_, ano = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia_anual, ano=None)
assert ano == timezone.now().year
# ===========================================================================
# Testes de get_proximo_numero – select_for_update (proteção concorrência)
# ===========================================================================
@pytest.mark.django_db(transaction=False)
def test_unique_together_protege_duplicata(tipo_materia_anual, regime,
app_config):
"""O unique_together (tipo, numero, ano) impede duplicatas no banco."""
_criar_materia(tipo_materia_anual, 1, 2024, regime)
with pytest.raises(IntegrityError):
with transaction.atomic():
_criar_materia(tipo_materia_anual, 1, 2024, regime)
# ===========================================================================
# Testes de global config – tipo sem sequencia_numeracao usa global
# ===========================================================================
@pytest.mark.django_db(transaction=False)
def test_tipo_sem_sequencia_usa_config_global(tipo_materia, regime, app_config):
"""Tipo com sequencia_numeracao vazio deve usar a config global ('A')."""
_criar_materia(tipo_materia, 1, 2024, regime)
_criar_materia(tipo_materia, 2, 2024, regime)
_criar_materia(tipo_materia, 10, 2023, regime,
data_apresentacao=date(2023, 1, 1))
with transaction.atomic():
numero, _ = MateriaLegislativa.get_proximo_numero(
tipo=tipo_materia, ano=2024)
# global default 'A' → sequencial por ano → MAX(2024) = 2 → próximo = 3
assert numero == 3
# ===========================================================================
# Testes do endpoint API create (MateriaLegislativaViewSet)
# ===========================================================================
@pytest.fixture
def api_client_autenticado(db):
"""APIClient autenticado como superusuário (tem todas as permissões)."""
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.create_superuser(
username='admin_api', password='secret123', email='a@b.com')
client = APIClient()
client.force_authenticate(user=user)
return client
@pytest.mark.django_db(transaction=False)
def test_api_create_sem_numero_auto_gera(api_client_autenticado,
tipo_materia_anual, regime,
app_config):
"""POST sem 'numero' deve auto-gerar próximo sequencial."""
_criar_materia(tipo_materia_anual, 1, 2024, regime)
response = api_client_autenticado.post(
'/api/materia/materialegislativa/',
{
'tipo': tipo_materia_anual.pk,
'ano': 2024,
'data_apresentacao': '2024-06-01',
'regime_tramitacao': regime.pk,
'ementa': 'Teste auto-gerar número',
},
format='json',
)
assert response.status_code == 201
assert response.data['numero'] == 2
@pytest.mark.django_db(transaction=False)
def test_api_create_com_numero_respeita_dado(api_client_autenticado,
tipo_materia_anual, regime,
app_config):
"""POST com 'numero' explícito deve criar com o número informado."""
response = api_client_autenticado.post(
'/api/materia/materialegislativa/',
{
'tipo': tipo_materia_anual.pk,
'numero': 42,
'ano': 2024,
'data_apresentacao': '2024-06-01',
'regime_tramitacao': regime.pk,
'ementa': 'Número exato',
},
format='json',
)
assert response.status_code == 201
assert response.data['numero'] == 42
@pytest.mark.django_db(transaction=False)
def test_api_create_numero_duplicado_retorna_erro(api_client_autenticado,
tipo_materia_anual, regime,
app_config):
"""POST com número já existente retorna erro.
O DRF detecta a violação de unique_together via UniqueTogetherValidator
no serializer e retorna 400 antes de chegar ao banco.
"""
_criar_materia(tipo_materia_anual, 42, 2024, regime)
response = api_client_autenticado.post(
'/api/materia/materialegislativa/',
{
'tipo': tipo_materia_anual.pk,
'numero': 42,
'ano': 2024,
'data_apresentacao': '2024-06-01',
'regime_tramitacao': regime.pk,
'ementa': 'Duplicata',
},
format='json',
)
assert response.status_code == 400
@pytest.mark.django_db(transaction=False)
def test_api_create_sem_tipo_valida_serializer(api_client_autenticado, regime):
"""POST sem 'tipo' deve falhar na validação do serializer (400)."""
response = api_client_autenticado.post(
'/api/materia/materialegislativa/',
{
'numero': 1,
'ano': 2024,
'data_apresentacao': '2024-06-01',
'regime_tramitacao': regime.pk,
'ementa': 'Sem tipo',
},
format='json',
)
assert response.status_code == 400

51
sapl/materia/views.py

@ -16,6 +16,7 @@ from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, ValidationError
from django.db import transaction
from django.db.models import Max, Q
from django.http import HttpResponse, JsonResponse
from django.http.response import Http404, HttpResponseRedirect
@ -338,55 +339,17 @@ class ProposicaoTaView(IntegracaoTaView):
return self.get_redirect_deactivated()
@permission_required('materia.detail_materialegislativa')
@transaction.atomic
def recuperar_materia(request):
logger = logging.getLogger(__name__)
username = request.user.username
tipo = TipoMateriaLegislativa.objects.get(pk=request.GET['tipo'])
ano = request.GET.get('ano', '')
if not (tipo and ano):
return JsonResponse({'numero': '', 'ano': ''})
numeracao = None
try:
logger.debug("user=" + username +
". Tentando obter numeração da matéria.")
numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao_protocolo
except AttributeError as e:
logger.error("user=" + username + ". " + str(e) +
" Numeracao da matéria definida como None.")
pass
ano = request.GET.get('ano', None)
if tipo.sequencia_numeracao:
numeracao = tipo.sequencia_numeracao
if numeracao == 'A':
numero = MateriaLegislativa.objects.filter(
ano=ano, tipo=tipo).aggregate(Max('numero'))
elif numeracao == 'L':
legislatura = Legislatura.objects.filter(
data_inicio__year__lte=ano,
data_fim__year__gte=ano).first()
data_inicio = legislatura.data_inicio
data_fim = legislatura.data_fim
numero = MateriaLegislativa.objects.filter(
data_apresentacao__gte=data_inicio,
data_apresentacao__lte=data_fim,
tipo=tipo).aggregate(
Max('numero'))
elif numeracao == 'U':
numero = MateriaLegislativa.objects.filter(
tipo=tipo).aggregate(Max('numero'))
if numeracao is None:
numero['numero__max'] = 0
max_numero = numero['numero__max'] + 1 if numero['numero__max'] else 1
max_numero, ano = MateriaLegislativa.get_proximo_numero(
tipo=tipo,
ano=int(ano) if ano else None
)
response = JsonResponse({'numero': max_numero, 'ano': ano})
return response

Loading…
Cancel
Save