Performance-only slice extracted from feat/rate-limiter-2026 (commit
2f2a976df), which bundled query-optimization work in with unrelated
rate-limiter/caching changes.
- customize_link_materia() and the Ordem do Dia / Expediente ListViews
now use prefetched querysets instead of per-row queries.
- get_etiqueta_protocolos() batch-fetches MateriaLegislativa and
DocumentoAdministrativo instead of querying per protocolo in a loop.
- New migration redefines the materia_materiaemtramitacao view and adds
a concurrent index (tram_materia_id_desc) backing the tramitacao
prefetch.
- Drops a redundant .distinct() + order_by on a related field in
RelatorioMateriasTramitacaoFilterSet.qs.
- Removes the unused painel_mensagem/parlamentar/votacao views and
templates (also bundled into 2f2a976df by mistake).
- Adds CLAUDE.md project documentation.
Note: feat/painel-votacao-v2 still imports painel_mensagem_view,
painel_parlamentar_view and painel_votacao_view. That branch will need
reconciling with this removal when it's rebased onto 3.1.x.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
O campo `CasaLegislativa.informacao_geral` é editado por editor de texto
rico: `CasaLegislativaForm` aplica `attrs={'id': 'texto-rico'}` no widget
e `frontend/src/__global/main.js` instancia o TinyMCE sobre esse id. O
valor submetido, portanto, é HTML, e não texto puro.
Com `max_length=100` no model, o MaxLengthValidator derivado pelo Django
contava os caracteres do HTML, tags inclusive. Um endereço de três linhas
com 87 caracteres visíveis vira 106 caracteres depois dos `<p></p>`, e o
salvamento era recusado com "no máximo 100 caracteres (ele possui 106)"
sem que o usuário tivesse digitado 100 caracteres. Quanto mais formatação
o texto tem, mais cedo o erro aparece — e não há como o operador
descobrir sozinho que o limite está sendo consumido por tags que ele não
vê.
O limite nunca teve efeito no banco: `TextField` gera coluna `text` no
PostgreSQL, sem restrição de tamanho. A migration é um no-op em SQL e
serve apenas para manter o state do Django coerente com o model.
Adiciona teste que trava o comportamento e comentário no model
registrando por que o campo não deve receber `max_length` de volta.
OSTicket #824090
* Oculta sessões plenárias não iniciadas da consulta pública
Sessões cadastradas com antecedência ficavam visíveis no portal mesmo com
"Publicar pauta?", "Sessão iniciada?" e "Sessão finalizada?" em Não: nenhum
desses campos filtrava queryset público. Só publicar_pauta filtrava, e apenas
nos dois pontos de entrada da pauta.
Passa a esconder do usuário anônimo as sessões marcadas explicitamente como
não iniciadas e sem pauta publicada. Usuário autenticado continua vendo todas.
A regra fica centralizada em SessaoPlenaria.objects.visiveis_para(user) e é
aplicada na pesquisa de sessões, no detalhe, no resumo e no resumo/ata.
iniciada nulo conta como visível: o campo nasceu sem default e o default=True
só chegou na migração 0027, que não preencheu as linhas existentes, então
bases anteriores a out/2018 têm sessões realizadas com o campo em NULL.
Também restringe PautaSessaoDetailView, que não filtrava publicar_pauta e
expunha por URL a pauta de sessões não publicadas.
* Troca custom manager por função utilitária de visibilidade
Atende à revisão do PR: substitui a subclasse SessaoPlenariaQuerySet e o
método .visiveis_para() por uma função restringe_sessoes_visiveis(qs, user),
chamada como wrapper nos pontos de acesso público. Mantém a mesma regra de
filtragem e o comportamento de fundir as cadeias de filter em um único SQL.
---------
Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com>
O PDF do Resumo da Sessão ordenava os expedientes alfabeticamente pelo
nome do tipo, ignorando o campo `ordenacao` de TipoExpediente. Numa Casa
que cadastre vários tipos, isso embaralha o documento: "Grande
Expediente" sai antes de "Leitura e Aprovação da Ata" e o encerramento
pode ser impresso logo após a abertura.
A tela do Resumo e o Extrato passam por sapl.sessao.views.get_expedientes,
que já ordena por `tipo__ordenacao`; apenas o relatório em PDF, que usa
get_sessao_plenaria, ficou para trás. O order_by('tipo__nome') havia sido
introduzido em 25be37eb justamente para acompanhar o Resumo, mas quando
2eb0b796 (#2920) criou o campo `ordenacao` somente get_expedientes foi
atualizado, e os dois caminhos divergiram em silêncio.
Alinha get_sessao_plenaria à mesma ordenação e adiciona testes que travam
a equivalência entre o PDF e o Resumo exibido em tela.
OSTicket #125461
RelatorioNormasPorAutorFilterSet: chaining .filter(autorianorma__primeiro_autor=True)
after the filterset's own autorianorma__autor filter created a second JOIN, causing
each norma to appear once per Autoria with primeiro_autor=True. Removing that filter
leaves a single JOIN so DISTINCT works correctly.
Both filtersets now return none() when no author is selected, preventing a full-table
scan with an unguarded ORDER BY on a related field.
Both views pass the Autor object (not str) into context so the templates can compare
autoria.autor != autor by PK. Both templates now show only co-authors (those different
from the filtered author) in the Coautor(es) column, mirroring each other.
RelatorioMateriasPorAutorView: removed the OrderedDict.fromkeys() deduplication
workaround that was masking the same underlying JOIN issue.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- audiencia/forms.py: also catch ValueError when querying MateriaLegislativa
by numero, so a non-numeric input (e.g. '36 e 61') surfaces as a form
validation error instead of an unhandled 500.
- utils.py: MultiFormatOutputMixin.render_to_response was doing
getattr(request, request.method) which fails for HEAD requests because
Django exposes request.GET/POST but not request.HEAD; remap HEAD → GET.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Corrige polling excessivo em /voto-individual/
- Adiciona @login_required em votante_view para que usuários
anônimos sejam redirecionados ao login em vez de receberem o
template com auto-reload.
- Corrige bug do relógio: setInterval recursivo dentro de
startTime() acumulava timers exponencialmente. Trocado por
setTimeout, mantendo apenas um timer pendente.
- Move o script de reload/relógio para dentro do bloco
{% if not error_message %} para que a variante de erro não
agende reloads de 30s.
Reduz drasticamente o volume de requisições que disparavam o
rate limiter (RATE_LIMITER_RATE=35/m) quando vários parlamentares
mantinham a tela aberta atrás do mesmo IP NAT.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Bloqueia /voto-individual/ para usuários sem Votante/can_vote
Antes, usuários autenticados sem registro de Votante ou sem a
permissão parlamentares.can_vote recebiam HTTP 200 com um template
de erro (e, antes do commit anterior, ainda traziam o JS de auto-
reload). Agora a view exige as duas condições no início e retorna
HTTP 403 caso falhe:
- @permission_required('parlamentares.can_vote', raise_exception=True)
garante a permissão (com raise_exception para 403 ao invés do
redirect padrão pra LOGIN_URL).
- Checagem inline de Votante.objects.filter(user=...).exists() pega
o caso de superusers (que passam pelo bypass automático de
permissões do Django mas não têm cadastro de Votante) e de
qualquer custom group que conceda can_vote sem o cadastro.
Mantém HTTP 200 com mensagem amigável para falhas de estado que
um votante legítimo pode encontrar (nenhuma matéria aberta,
parlamentar não presente na sessão).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: joao <joao@mezzoplanejamento.com.br>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refact: cria método get_proximo_numero
* feat: impl numeração automática em cadastros via API
* Update sapl/materia/models.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/api/serializers.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/api/views_materia.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/materia/models.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/materia/models.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Add transaction atomic no endpoint create
* add validação de tipo se tipo não é objeto do model TipoMateriaLegislativa
* refact: aplica solicitações de reviewer e cria testes
* fix: altera numero_preferido para numero_candidato
* fix: remove espaços entre classes
* fix: corrige uso de transaction e ausencia dele
* fix: corrige testes devido mudança de norme de variável
* fix: altera seleção para select_for_update
* fix: mudança de nome remanescente para numero_candidato
* fix: retorna decorator na view function recuperar_materia
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: impl HEADER LastModifiedDecorator na api
* fix: ajuste no frontend para evitar cache automático devido a LastModified sem tratamento adequado
* rebuild frontend
* fix: corrige last_modified_func para retorna sob retrieve
* refact: impl LastModified diretamente nos models que possuem campos específicos
* Seleciona tipo de votação para múltiplas matérias ao incluir na Ordem do Dia ou Expediente
* Ajustes solicitados na seleção de múltiplas Matérias para Ordem/Expediente
* Update adicionar_varias_materias_expediente.html
* Fix read-only mount on k8s
* Fix recibo proposição e adiciona rate limiter em matéria e norma
* Update forms.py
Alteração do nome do campo todos, conforme solicitação.
* Update adicionar_varias_materias_expediente.html
Ajuste no template por alteração do nome do campo "todos".
* Fix recibo proposição e adiciona rate limiter em matéria e norma
---------
Co-authored-by: root <root@info38.camaranh>
Co-authored-by: Edward Oliveira <edwardr@senado.gov.br>
Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com>
* Fix recibo proposição e adiciona rate limiter em matéria e norma
* Conserta bug na pesquisa do Relatório de Votações Nominais
Na pesquisa, ao selecionar filtro por Tipo de Matéria, Número e/ou Ano da Matéria, o sistema retorna erro 500.
* Fix recibo proposição e adiciona rate limiter em matéria e norma
---------
Co-authored-by: Edward Oliveira <edwardr@senado.gov.br>
* feat: impl filtro m2m com lookup '__in' para buscas com multiplos ids
* fix: remove alteração na criação de rotas da api
A classe DrfautoapiRouter removida nesse commit bem como a alteração no classmethod router é uma aleração necessária para que a api rode no django 5.2. Por outro lado, esta alteração quebra os links gerados pela template tag url e exigiria refatoração das mesmas. Esta alteração não é necessária para o propósito do PR.
* Implementa Relatório de Votações Nominais
* Alterações solicitadas - Relatório de Votações Nominais
* Apply suggestions from code review
Todas as sugestões de alteração acatadas.
Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com>
* Update views.py
Conforme observação sobre o retorno da QuerySet, escolha da opção 2 - colocar o qs dentro dos if's. Também houve a alteração na view, utilizando diretamente a classe genérica MultiFormatOutputMixin. Com a refatoração efetuada, não foi necessário definir uma especificação da mesma.
---------
Co-authored-by: root <root@info38.camaranh>
Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com>