Browse Source

Optimise relatorio and sessao views; fix RelatorioMateriasTramitacao 504s

- RelatorioMateriasTramitacao: strip UI-only params before deciding whether
  to run a query, short-circuit RelatorioMixin.get() on permission checks,
  remove redundant per-view @ratelimit decorators (RateLimitMiddleware
  already covers them with stricter checks), and cache get_report_urls_map()
  with lru_cache
- RelatorioMateriasTramitacaoView: rewrite the materia_materiaemtramitacao
  view (migration 0088) to use DISTINCT ON instead of a correlated subquery,
  add a composite index on materia_tramitacao(materia_id, id DESC), and drop
  the now-redundant .distinct() from the filterset queryset
- customize_link_materia (MateriaOrdemDiaCrud / ExpedienteMateriaCrud):
  eliminate per-row N+1 queries via select_related/prefetch_related with
  to_attr caches, resolve sessao_plenaria once per page, and use a flat
  paginate_by=100 instead of the count()-dependent 50/None toggle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rate-limiter-2026
Edward Ribeiro 1 week ago
parent
commit
b7ca1609e8
  1. 62
      sapl/materia/migrations/0088_fix_view_materiaemtramitacao.py
  2. 3
      sapl/materia/models.py
  3. 4
      sapl/relatorios/forms.py
  4. 81
      sapl/relatorios/views.py
  5. 212
      sapl/sessao/views.py
  6. 26
      sapl/utils.py

62
sapl/materia/migrations/0088_fix_view_materiaemtramitacao.py

@ -0,0 +1,62 @@
from django.db import migrations, models
_OLD_VIEW = """
create or replace view materia_materiaemtramitacao as
select m.id as id,
m.id as materia_id,
t.id as tramitacao_id,
t.unidade_tramitacao_destino_id as unidade_tramitacao_atual_id
from materia_materialegislativa m
inner join materia_tramitacao t on (m.id = t.materia_id)
where t.id = (select max(id) from materia_tramitacao where materia_id = m.id)
order by m.id DESC
"""
_NEW_VIEW = """
create or replace view materia_materiaemtramitacao as
select distinct on (m.id)
m.id as id,
m.id as materia_id,
t.id as tramitacao_id,
t.unidade_tramitacao_destino_id as unidade_tramitacao_atual_id
from materia_materialegislativa m
inner join materia_tramitacao t on t.materia_id = m.id
order by m.id desc, t.id desc
"""
class Migration(migrations.Migration):
# CREATE INDEX CONCURRENTLY cannot run inside a transaction.
atomic = False
dependencies = [
('materia', '0087_update_viewdb_materiaemtramitacao'),
]
operations = [
migrations.RunSQL(sql=_NEW_VIEW, reverse_sql=_OLD_VIEW),
migrations.SeparateDatabaseAndState(
database_operations=[
migrations.RunSQL(
sql="""
CREATE INDEX CONCURRENTLY IF NOT EXISTS
tram_materia_id_desc
ON materia_tramitacao (materia_id, id DESC)
""",
reverse_sql="""
DROP INDEX CONCURRENTLY IF EXISTS
tram_materia_id_desc
""",
),
],
state_operations=[
migrations.AddIndex(
model_name='tramitacao',
index=models.Index(
fields=['materia', '-id'],
name='tram_materia_id_desc',
),
),
],
),
]

3
sapl/materia/models.py

@ -1350,6 +1350,9 @@ class Tramitacao(models.Model):
verbose_name = _('Tramitação') verbose_name = _('Tramitação')
verbose_name_plural = _('Tramitações') verbose_name_plural = _('Tramitações')
ordering = ('-data_tramitacao', '-id') ordering = ('-data_tramitacao', '-id')
indexes = [
models.Index(fields=['materia', '-id'], name='tram_materia_id_desc'),
]
def __str__(self): def __str__(self):
return _('%(materia)s | %(status)s | %(data)s') % { return _('%(materia)s | %(status)s | %(data)s') % {

4
sapl/relatorios/forms.py

@ -543,9 +543,7 @@ class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet):
@property @property
def qs(self): def qs(self):
parent = super(RelatorioMateriasTramitacaoFilterSet, self).qs parent = super(RelatorioMateriasTramitacaoFilterSet, self).qs
return parent.distinct().order_by( return parent.order_by('-materia__ano', 'materia__tipo', '-materia__numero')
'-materia__ano', 'materia__tipo', '-materia__numero'
)
class Meta: class Meta:
model = MateriaEmTramitacao model = MateriaEmTramitacao

81
sapl/relatorios/views.py

@ -51,8 +51,7 @@ from sapl.sessao.views import (get_identificacao_basica, get_mesa_diretora,
from sapl.settings import MEDIA_URL from sapl.settings import MEDIA_URL
from sapl.settings import STATIC_ROOT from sapl.settings import STATIC_ROOT
from sapl.utils import LISTA_DE_UFS, TrocaTag, filiacao_data, create_barcode, show_results_filter_set, \ from sapl.utils import LISTA_DE_UFS, TrocaTag, filiacao_data, create_barcode, show_results_filter_set, \
num_materias_por_tipo, parlamentares_ativos, MultiFormatOutputMixin num_materias_por_tipo, parlamentares_ativos, MultiFormatOutputMixin, is_report_allowed
from sapl.middleware.ratelimit import smart_key, smart_rate
from .templates import (pdf_capa_processo_gerar, from .templates import (pdf_capa_processo_gerar,
pdf_documento_administrativo_gerar, pdf_espelho_gerar, pdf_documento_administrativo_gerar, pdf_espelho_gerar,
pdf_etiqueta_protocolo_gerar, pdf_materia_gerar, pdf_etiqueta_protocolo_gerar, pdf_materia_gerar,
@ -60,8 +59,6 @@ from .templates import (pdf_capa_processo_gerar,
pdf_protocolo_gerar, pdf_sessao_plenaria_gerar) pdf_protocolo_gerar, pdf_sessao_plenaria_gerar)
from sapl.crud.base import make_pagination from sapl.crud.base import make_pagination
from ratelimit.decorators import ratelimit
from django.utils.decorators import method_decorator
def get_kwargs_params(request, fields): def get_kwargs_params(request, fields):
@ -1850,26 +1847,18 @@ class RelatoriosListView(TemplateView):
class RelatorioMixin: class RelatorioMixin:
# TODO: verificar se todos os relatorios de sistema/relatorios extendem esse Mixin # TODO: verificar se todos os relatorios de sistema/relatorios extendem esse Mixin
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
super(RelatorioMixin, self).get(request)
# TODO: import as global
from sapl.utils import is_report_allowed
if not is_report_allowed(request): if not is_report_allowed(request):
raise Http404() raise Http404()
is_relatorio = request.GET.get('relatorio') if request.GET.get('relatorio'):
context = self.get_context_data(filter=self.filterset) filterset_class = self.get_filterset_class()
self.filterset = self.get_filterset(filterset_class)
if is_relatorio: context = self.get_context_data(filter=self.filterset)
return self.relatorio(request, context) return self.relatorio(request, context)
else:
return self.render_to_response(context) return super(RelatorioMixin, self).get(request, *args, **kwargs)
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView): class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView):
model = DocumentoAcessorio model = DocumentoAcessorio
filterset_class = RelatorioDocumentosAcessoriosFilterSet filterset_class = RelatorioDocumentosAcessoriosFilterSet
@ -1914,10 +1903,6 @@ class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioVotacoesNominaisView(RelatorioMixin, MultiFormatOutputMixin, FilterView): class RelatorioVotacoesNominaisView(RelatorioMixin, MultiFormatOutputMixin, FilterView):
model = VotoParlamentar model = VotoParlamentar
filterset_class = RelatorioVotacoesNominaisFilterSet filterset_class = RelatorioVotacoesNominaisFilterSet
@ -1987,10 +1972,6 @@ class RelatorioVotacoesNominaisView(RelatorioMixin, MultiFormatOutputMixin, Filt
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioAtasView(RelatorioMixin, FilterView): class RelatorioAtasView(RelatorioMixin, FilterView):
model = SessaoPlenaria model = SessaoPlenaria
filterset_class = RelatorioAtasFilterSet filterset_class = RelatorioAtasFilterSet
@ -2016,10 +1997,6 @@ class RelatorioAtasView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioPresencaSessaoView(RelatorioMixin, FilterView): class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
model = SessaoPlenaria model = SessaoPlenaria
@ -2254,10 +2231,6 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView): class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView):
model = MateriaLegislativa model = MateriaLegislativa
filterset_class = RelatorioHistoricoTramitacaoFilterSet filterset_class = RelatorioHistoricoTramitacaoFilterSet
@ -2315,10 +2288,6 @@ class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioDataFimPrazoTramitacaoView(RelatorioMixin, FilterView): class RelatorioDataFimPrazoTramitacaoView(RelatorioMixin, FilterView):
model = MateriaEmTramitacao model = MateriaEmTramitacao
filterset_class = RelatorioDataFimPrazoTramitacaoFilterSet filterset_class = RelatorioDataFimPrazoTramitacaoFilterSet
@ -2382,10 +2351,6 @@ class RelatorioDataFimPrazoTramitacaoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioReuniaoView(RelatorioMixin, FilterView): class RelatorioReuniaoView(RelatorioMixin, FilterView):
model = Reuniao model = Reuniao
filterset_class = RelatorioReuniaoFilterSet filterset_class = RelatorioReuniaoFilterSet
@ -2420,10 +2385,6 @@ class RelatorioReuniaoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioAudienciaView(RelatorioMixin, FilterView): class RelatorioAudienciaView(RelatorioMixin, FilterView):
model = AudienciaPublica model = AudienciaPublica
filterset_class = RelatorioAudienciaFilterSet filterset_class = RelatorioAudienciaFilterSet
@ -2458,10 +2419,6 @@ class RelatorioAudienciaView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView): class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
model = MateriaEmTramitacao model = MateriaEmTramitacao
filterset_class = RelatorioMateriasTramitacaoFilterSet filterset_class = RelatorioMateriasTramitacaoFilterSet
@ -2576,10 +2533,6 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioMateriasPorAnoAutorTipoView(RelatorioMixin, FilterView): class RelatorioMateriasPorAnoAutorTipoView(RelatorioMixin, FilterView):
model = MateriaLegislativa model = MateriaLegislativa
filterset_class = RelatorioMateriasPorAnoAutorTipoFilterSet filterset_class = RelatorioMateriasPorAnoAutorTipoFilterSet
@ -2659,10 +2612,6 @@ class RelatorioMateriasPorAnoAutorTipoView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioMateriasPorAutorView(RelatorioMixin, FilterView): class RelatorioMateriasPorAutorView(RelatorioMixin, FilterView):
model = MateriaLegislativa model = MateriaLegislativa
filterset_class = RelatorioMateriasPorAutorFilterSet filterset_class = RelatorioMateriasPorAutorFilterSet
@ -2734,10 +2683,6 @@ class RelatorioMateriaAnoAssuntoView(ListView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView): class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView):
model = NormaJuridica model = NormaJuridica
filterset_class = RelatorioNormasMesFilterSet filterset_class = RelatorioNormasMesFilterSet
@ -2778,10 +2723,6 @@ class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioNormasVigenciaView(RelatorioMixin, FilterView): class RelatorioNormasVigenciaView(RelatorioMixin, FilterView):
model = NormaJuridica model = NormaJuridica
filterset_class = RelatorioNormasVigenciaFilterSet filterset_class = RelatorioNormasVigenciaFilterSet
@ -2846,10 +2787,6 @@ class RelatorioNormasVigenciaView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView): class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView):
model = DocumentoAdministrativo model = DocumentoAdministrativo
filterset_class = RelatorioHistoricoTramitacaoAdmFilterSet filterset_class = RelatorioHistoricoTramitacaoAdmFilterSet
@ -2900,10 +2837,6 @@ class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView):
return context return context
@method_decorator(ratelimit(key=smart_key,
rate=smart_rate,
block=True),
name='dispatch')
class RelatorioNormasPorAutorView(RelatorioMixin, FilterView): class RelatorioNormasPorAutorView(RelatorioMixin, FilterView):
model = NormaJuridica model = NormaJuridica
filterset_class = RelatorioNormasPorAutorFilterSet filterset_class = RelatorioNormasPorAutorFilterSet

212
sapl/sessao/views.py

@ -9,7 +9,7 @@ from django.contrib import messages
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Max, Q from django.db.models import Max, Prefetch, Q
from django.http import JsonResponse from django.http import JsonResponse
from django.http.response import Http404, HttpResponseRedirect from django.http.response import Http404, HttpResponseRedirect
from django.urls import reverse from django.urls import reverse
@ -174,7 +174,7 @@ def verifica_sessao_iniciada(request, spk, is_leitura=False):
aux_text = 'leitura' if is_leitura else 'votação' aux_text = 'leitura' if is_leitura else 'votação'
logger.info('user=' + username + '. Não é possível abrir matérias para {}. ' logger.info('user=' + username + '. Não é possível abrir matérias para {}. '
'Esta SessaoPlenaria (id={}) não foi iniciada ou está finalizada.'.format( 'Esta SessaoPlenaria (id={}) não foi iniciada ou está finalizada.'.format(
aux_text, spk)) aux_text, spk))
msg = _('Não é possível abrir matérias para {}. ' msg = _('Não é possível abrir matérias para {}. '
'Esta Sessão Plenária não foi iniciada ou está finalizada.' 'Esta Sessão Plenária não foi iniciada ou está finalizada.'
' Vá em "Abertura"->"Dados Básicos" e altere os valores dos campos necessários.'.format(aux_text)) ' Vá em "Abertura"->"Dados Básicos" e altere os valores dos campos necessários.'.format(aux_text))
@ -226,38 +226,43 @@ def abrir_votacao(request, pk, spk):
def customize_link_materia(context, pk, has_permission, is_expediente): def customize_link_materia(context, pk, has_permission, is_expediente):
# sessao_plenaria is the same for every row — resolve once
object_list = context['object_list']
if object_list:
sessao_plenaria = object_list[0].sessao_plenaria
else:
sessao_plenaria = SessaoPlenaria.objects.get(id=pk)
data_sessao = sessao_plenaria.data_fim or sessao_plenaria.data_inicio
for i, row in enumerate(context['rows']): for i, row in enumerate(context['rows']):
materia = context['object_list'][i].materia obj = object_list[i]
obj = context['object_list'][i] materia = obj.materia # already select_related
url_materia = reverse( url_materia = reverse(
'sapl.materia:materialegislativa_detail', kwargs={'pk': materia.id}) 'sapl.materia:materialegislativa_detail', kwargs={'pk': materia.id})
numeracao = materia.numeracao_set.first() if materia.numeracao_set.first() else "-"
todos_autoria = materia.autoria_set.all() numeracao = materia._numeracao_prefetch[0] if materia._numeracao_prefetch else "-"
autoria = todos_autoria.filter(primeiro_autor=True)
todos_autoria = materia._autoria_prefetch
autoria = [a for a in todos_autoria if a.primeiro_autor]
autor = ', '.join([str(a.autor) for a in autoria]) if autoria else "-" autor = ', '.join([str(a.autor) for a in autoria]) if autoria else "-"
todos_autores = ', '.join([str(a.autor) for a in todos_autoria]) if autoria else "-"
todos_autores = ', '.join([str(a.autor) num_protocolo = materia.numero_protocolo or "-"
for a in todos_autoria]) if autoria else "-"
num_protocolo = materia.numero_protocolo if materia.numero_protocolo else "-" tramitacao = next(
sessao_plenaria = SessaoPlenaria.objects.get(id=pk) (t for t in materia._tramitacao_prefetch if t.data_tramitacao <= data_sessao),
data_sessao = sessao_plenaria.data_fim if sessao_plenaria.data_fim else sessao_plenaria.data_inicio None,
tramitacao = Tramitacao.objects \ )
.select_related('materia', 'status', 'materia__tipo') \
.filter(materia=materia, turno__isnull=False, data_tramitacao__lte=data_sessao) \
.exclude(turno__exact='') \
.order_by('-data_tramitacao', '-id') \
.first()
turno = '-' turno = '-'
if tramitacao: if tramitacao:
for t in Tramitacao.TURNO_CHOICES: for t in Tramitacao.TURNO_CHOICES:
if t[0] == tramitacao.turno: if t[0] == tramitacao.turno:
turno = t[1] turno = t[1]
break break
materia_em_tramitacao = MateriaEmTramitacao.objects \
.select_related("materia", "tramitacao") \ materia_em_tramitacao = materia._met_prefetch[0] if materia._met_prefetch else None
.filter(materia=materia) \
.first()
# idUnica para cada materia # idUnica para cada materia
idAutor = "autor" + str(i) idAutor = "autor" + str(i)
idAutores = "autores" + str(i) idAutores = "autores" + str(i)
@ -283,12 +288,9 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
# url em toda a string de title_materia # url em toda a string de title_materia
context['rows'][i][1] = (title_materia, None) context['rows'][i][1] = (title_materia, None)
exist_resultado = obj.registrovotacao_set.filter( exist_resultado = bool(obj._votacao_prefetch)
materia=obj.materia).exists() exist_retirada = bool(obj._retirada_prefetch)
exist_retirada = obj.retiradapauta_set.filter( exist_leitura = bool(obj._leitura_prefetch)
materia=obj.materia).exists()
exist_leitura = obj.registroleitura_set.filter(
materia=obj.materia).exists()
if (obj.tipo_votacao != LEITURA and not exist_resultado and not exist_retirada) or \ if (obj.tipo_votacao != LEITURA and not exist_resultado and not exist_retirada) or \
(obj.tipo_votacao == LEITURA and not exist_leitura): (obj.tipo_votacao == LEITURA and not exist_leitura):
@ -410,8 +412,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
resultado = '''Não há resultado''' resultado = '''Não há resultado'''
elif exist_retirada: elif exist_retirada:
retirada = obj.retiradapauta_set.filter( retirada = obj._retirada_prefetch[-1]
materia_id=obj.materia_id).last()
retirada_descricao = retirada.tipo_de_retirada.descricao retirada_descricao = retirada.tipo_de_retirada.descricao
retirada_observacao = retirada.observacao retirada_observacao = retirada.observacao
url = reverse('sapl.sessao:retiradapauta_detail', url = reverse('sapl.sessao:retiradapauta_detail',
@ -423,13 +424,11 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
else: else:
if obj.tipo_votacao == LEITURA: if obj.tipo_votacao == LEITURA:
resultado = obj.registroleitura_set.filter( resultado = obj._leitura_prefetch[-1]
materia_id=obj.materia_id).last()
resultado_descricao = "Matéria lida" resultado_descricao = "Matéria lida"
resultado_observacao = resultado.observacao resultado_observacao = resultado.observacao
else: else:
resultado = obj.registrovotacao_set.filter( resultado = obj._votacao_prefetch[-1]
materia_id=obj.materia_id).last()
resultado_descricao = resultado.tipo_resultado_votacao.nome resultado_descricao = resultado.tipo_resultado_votacao.nome
resultado_observacao = resultado.observacao resultado_observacao = resultado.observacao
@ -488,11 +487,11 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
'mid': obj.materia_id}) 'mid': obj.materia_id})
resultado = ( resultado = (
'<a href="%s?page=%s">%s<br/><br/>%s</a>' % ( '<a href="%s?page=%s">%s<br/><br/>%s</a>' % (
url, url,
context.get('page', 1), context.get('page', 1),
resultado_descricao, resultado_descricao,
resultado_observacao)) resultado_observacao))
else: else:
if obj.tipo_votacao == NOMINAL: if obj.tipo_votacao == NOMINAL:
@ -503,7 +502,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
'pk': obj.sessao_plenaria_id, 'pk': obj.sessao_plenaria_id,
'oid': obj.pk, 'oid': obj.pk,
'mid': obj.materia_id}) + \ 'mid': obj.materia_id}) + \
'?&materia=expediente' '?&materia=expediente'
else: else:
url = reverse( url = reverse(
'sapl.sessao:votacao_nominal_transparencia', 'sapl.sessao:votacao_nominal_transparencia',
@ -511,7 +510,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
'pk': obj.sessao_plenaria_id, 'pk': obj.sessao_plenaria_id,
'oid': obj.pk, 'oid': obj.pk,
'mid': obj.materia_id}) + \ 'mid': obj.materia_id}) + \
'?&materia=ordem' '?&materia=ordem'
resultado = ('<a href="%s">%s<br/>%s</a>' % resultado = ('<a href="%s">%s<br/>%s</a>' %
(url, (url,
@ -526,7 +525,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
'pk': obj.sessao_plenaria_id, 'pk': obj.sessao_plenaria_id,
'oid': obj.pk, 'oid': obj.pk,
'mid': obj.materia_id}) + \ 'mid': obj.materia_id}) + \
'?&materia=expediente' '?&materia=expediente'
else: else:
url = reverse( url = reverse(
'sapl.sessao:votacao_simbolica_transparencia', 'sapl.sessao:votacao_simbolica_transparencia',
@ -534,7 +533,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
'pk': obj.sessao_plenaria_id, 'pk': obj.sessao_plenaria_id,
'oid': obj.pk, 'oid': obj.pk,
'mid': obj.materia_id}) + \ 'mid': obj.materia_id}) + \
'?&materia=ordem' '?&materia=ordem'
resultado = ('<a href="%s">%s<br/>%s</a>' % resultado = ('<a href="%s">%s<br/>%s</a>' %
(url, (url,
@ -798,7 +797,7 @@ class MateriaOrdemDiaCrud(MasterDetailCrud):
sessao_plenaria=self.kwargs['pk']).aggregate( sessao_plenaria=self.kwargs['pk']).aggregate(
Max('numero_ordem'))['numero_ordem__max'] Max('numero_ordem'))['numero_ordem__max']
self.initial['numero_ordem'] = ( self.initial['numero_ordem'] = (
max_numero_ordem if max_numero_ordem else 0) + 1 max_numero_ordem if max_numero_ordem else 0) + 1
return self.initial return self.initial
def get_success_url(self): def get_success_url(self):
@ -835,20 +834,58 @@ class MateriaOrdemDiaCrud(MasterDetailCrud):
layout_key = 'OrdemDiaDetail' layout_key = 'OrdemDiaDetail'
class ListView(MasterDetailCrud.ListView): class ListView(MasterDetailCrud.ListView):
paginate_by = None paginate_by = 100
ordering = ['numero_ordem', 'materia', 'resultado'] ordering = ['numero_ordem', 'materia', 'resultado']
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
if self.get_queryset().count() > 500:
self.paginate_by = 50
else:
self.paginate_by = None
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
has_permition = self.request.user.has_module_perms(AppConfig.label) has_permition = self.request.user.has_module_perms(AppConfig.label)
return customize_link_materia(context, self.kwargs['pk'], has_permition, False) return customize_link_materia(context, self.kwargs['pk'], has_permition, False)
def get_queryset(self):
return super().get_queryset().select_related(
'materia', 'materia__tipo', 'sessao_plenaria',
).prefetch_related(
Prefetch(
'materia__materiaemtramitacao_set',
to_attr='_met_prefetch',
),
Prefetch(
'materia__numeracao_set',
to_attr='_numeracao_prefetch',
),
Prefetch(
'materia__autoria_set',
queryset=Autoria.objects.select_related('autor'),
to_attr='_autoria_prefetch',
),
Prefetch(
'materia__tramitacao_set',
queryset=Tramitacao.objects.filter(
turno__isnull=False,
).exclude(turno='').order_by('-data_tramitacao', '-id'),
to_attr='_tramitacao_prefetch',
),
Prefetch(
'registrovotacao_set',
queryset=RegistroVotacao.objects.select_related(
'tipo_resultado_votacao',
),
to_attr='_votacao_prefetch',
),
Prefetch(
'retiradapauta_set',
queryset=RetiradaPauta.objects.select_related(
'tipo_de_retirada',
),
to_attr='_retirada_prefetch',
),
Prefetch(
'registroleitura_set',
to_attr='_leitura_prefetch',
),
)
def recuperar_materia(request): def recuperar_materia(request):
tipo = TipoMateriaLegislativa.objects.get(pk=request.GET['tipo_materia']) tipo = TipoMateriaLegislativa.objects.get(pk=request.GET['tipo_materia'])
@ -907,24 +944,60 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
'resultado'] 'resultado']
class ListView(MasterDetailCrud.ListView): class ListView(MasterDetailCrud.ListView):
paginate_by = None paginate_by = 100
ordering = ['numero_ordem', 'materia', 'resultado'] ordering = ['numero_ordem', 'materia', 'resultado']
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
if self.get_queryset().count() > 500:
self.paginate_by = 50
else:
self.paginate_by = None
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
if self.request.GET.get('page'): if self.request.GET.get('page'):
context['page'] = self.request.GET.get('page') context['page'] = self.request.GET.get('page')
has_permition = self.request.user.has_module_perms(AppConfig.label) has_permition = self.request.user.has_module_perms(AppConfig.label)
return customize_link_materia(context, self.kwargs['pk'], has_permition, True) return customize_link_materia(context, self.kwargs['pk'], has_permition, True)
def get_queryset(self):
return super().get_queryset().select_related(
'materia', 'materia__tipo', 'sessao_plenaria',
).prefetch_related(
Prefetch(
'materia__materiaemtramitacao_set',
to_attr='_met_prefetch',
),
Prefetch(
'materia__numeracao_set',
to_attr='_numeracao_prefetch',
),
Prefetch(
'materia__autoria_set',
queryset=Autoria.objects.select_related('autor'),
to_attr='_autoria_prefetch',
),
Prefetch(
'materia__tramitacao_set',
queryset=Tramitacao.objects.filter(
turno__isnull=False,
).exclude(turno='').order_by('-data_tramitacao', '-id'),
to_attr='_tramitacao_prefetch',
),
Prefetch(
'registrovotacao_set',
queryset=RegistroVotacao.objects.select_related(
'tipo_resultado_votacao',
),
to_attr='_votacao_prefetch',
),
Prefetch(
'retiradapauta_set',
queryset=RetiradaPauta.objects.select_related(
'tipo_de_retirada',
),
to_attr='_retirada_prefetch',
),
Prefetch(
'registroleitura_set',
to_attr='_leitura_prefetch',
),
)
class CreateView(MasterDetailCrud.CreateView): class CreateView(MasterDetailCrud.CreateView):
form_class = ExpedienteMateriaForm form_class = ExpedienteMateriaForm
@ -941,7 +1014,7 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
sessao_plenaria=self.kwargs['pk']).aggregate( sessao_plenaria=self.kwargs['pk']).aggregate(
Max('numero_ordem'))['numero_ordem__max'] Max('numero_ordem'))['numero_ordem__max']
initial['numero_ordem'] = ( initial['numero_ordem'] = (
max_numero_ordem if max_numero_ordem else 0) + 1 max_numero_ordem if max_numero_ordem else 0) + 1
return initial return initial
def get_success_url(self): def get_success_url(self):
@ -975,7 +1048,6 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
return initial return initial
class DetailView(MasterDetailCrud.DetailView): class DetailView(MasterDetailCrud.DetailView):
layout_key = 'ExpedienteMateriaDetail' layout_key = 'ExpedienteMateriaDetail'
@ -1426,7 +1498,7 @@ class PresencaView(FormMixin, PresencaMixin, DetailView):
# Id dos parlamentares presentes # Id dos parlamentares presentes
marcados = request.POST.getlist('presenca_ativos') \ marcados = request.POST.getlist('presenca_ativos') \
+ request.POST.getlist('presenca_inativos') + request.POST.getlist('presenca_inativos')
# Deletar os que foram desmarcados # Deletar os que foram desmarcados
deletar = set(presentes_banco) - set(marcados) deletar = set(presentes_banco) - set(marcados)
@ -1541,7 +1613,7 @@ class PresencaOrdemDiaView(FormMixin, PresencaMixin, DetailView):
# Id dos parlamentares presentes # Id dos parlamentares presentes
marcados = request.POST.getlist('presenca_ativos') \ marcados = request.POST.getlist('presenca_ativos') \
+ request.POST.getlist('presenca_inativos') + request.POST.getlist('presenca_inativos')
# Deletar os que foram desmarcados # Deletar os que foram desmarcados
deletar = set(presentes_banco) - set(marcados) deletar = set(presentes_banco) - set(marcados)
@ -1803,7 +1875,7 @@ def insere_parlamentar_composicao(request):
username = request.user.username username = request.user.username
if request.user.has_perm( if request.user.has_perm(
'%s.add_%s' % ( '%s.add_%s' % (
AppConfig.label, IntegranteMesa._meta.model_name)): AppConfig.label, IntegranteMesa._meta.model_name)):
composicao = IntegranteMesa() composicao = IntegranteMesa()
@ -1867,7 +1939,7 @@ def remove_parlamentar_composicao(request):
username = request.user.username username = request.user.username
if request.POST and request.user.has_perm( if request.POST and request.user.has_perm(
'%s.delete_%s' % ( '%s.delete_%s' % (
AppConfig.label, IntegranteMesa._meta.model_name)): AppConfig.label, IntegranteMesa._meta.model_name)):
if 'composicao_mesa' in request.POST: if 'composicao_mesa' in request.POST:
try: try:
@ -2921,7 +2993,7 @@ class VotacaoView(SessaoPermissionMixin):
username = request.user.username username = request.user.username
self.logger.error('user=' + username + '. Problemas ao salvar RegistroVotacao da materia de id={} ' self.logger.error('user=' + username + '. Problemas ao salvar RegistroVotacao da materia de id={} '
'e da ordem de id={}. '.format(materia_id, ordem_id) + str( 'e da ordem de id={}. '.format(materia_id, ordem_id) + str(
e)) e))
return self.form_invalid(form) return self.form_invalid(form)
else: else:
ordem = OrdemDia.objects.get(id=ordem_id) ordem = OrdemDia.objects.get(id=ordem_id)
@ -3991,7 +4063,8 @@ class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView):
'resultado_observacao': resultado_observacao, 'resultado_observacao': resultado_observacao,
'situacao': ultima_tramitacao.status if ultima_tramitacao else _("Não informada"), 'situacao': ultima_tramitacao.status if ultima_tramitacao else _("Não informada"),
'processo': f'{str(numeracao.numero_materia)}/{str(numeracao.ano_materia)}' if numeracao else '-', 'processo': f'{str(numeracao.numero_materia)}/{str(numeracao.ano_materia)}' if numeracao else '-',
'autor': [str(x.autor) for x in Autoria.objects.select_related("autor").filter(materia_id=o.materia_id)], 'autor': [str(x.autor) for x in
Autoria.objects.select_related("autor").filter(materia_id=o.materia_id)],
'turno': get_turno(ultima_tramitacao.turno) if ultima_tramitacao else '', 'turno': get_turno(ultima_tramitacao.turno) if ultima_tramitacao else '',
'periodo': 'ordem dia', 'periodo': 'ordem dia',
}) })
@ -4099,7 +4172,6 @@ class PesquisarSessaoPlenariaView(MultiFormatOutputMixin, FilterView):
return r return r
class PesquisarPautaSessaoView(PesquisarSessaoPlenariaView): class PesquisarPautaSessaoView(PesquisarSessaoPlenariaView):
filterset_class = PautaSessaoFilterSet filterset_class = PautaSessaoFilterSet
template_name = 'sessao/pauta_sessao_filter.html' template_name = 'sessao/pauta_sessao_filter.html'
@ -5338,7 +5410,7 @@ class CorrespondenciaCrud(MasterDetailCrud):
sessao_plenaria=self.kwargs['pk']).aggregate( sessao_plenaria=self.kwargs['pk']).aggregate(
Max('numero_ordem'))['numero_ordem__max'] Max('numero_ordem'))['numero_ordem__max']
initial['numero_ordem'] = ( initial['numero_ordem'] = (
max_numero_ordem if max_numero_ordem else 0) + 1 max_numero_ordem if max_numero_ordem else 0) + 1
return initial return initial

26
sapl/utils.py

@ -1,6 +1,6 @@
import csv import csv
import string import string
from functools import wraps from functools import lru_cache, wraps
import hashlib import hashlib
import io import io
from itertools import groupby, chain from itertools import groupby, chain
@ -959,13 +959,15 @@ def parlamentares_ativos(data_inicio, data_fim=None):
return Parlamentar.objects.filter(id__in=parlamentares_id) return Parlamentar.objects.filter(id__in=parlamentares_id)
def show_results_filter_set(qr): _IGNORED_PARAMS = frozenset({'iframe', 'pesquisar', 'csrfmiddlewaretoken'})
query_params = set(qr.keys())
if ((len(query_params) == 1 and 'iframe' in query_params) or
len(query_params) == 0):
return False
return True
def show_results_filter_set(qr):
meaningful = {
k for k, v in qr.items()
if k not in _IGNORED_PARAMS and v and v.strip()
}
return bool(meaningful)
def sort_lista_chave(lista, chave): def sort_lista_chave(lista, chave):
@ -1249,7 +1251,7 @@ class GoogleRecapthaMixin:
return cd return cd
# TODO: cache this map and invalidate on each update @lru_cache(maxsize=None)
def get_report_urls_map(): def get_report_urls_map():
from django.urls import get_resolver from django.urls import get_resolver
from django.urls.base import reverse from django.urls.base import reverse
@ -1278,13 +1280,11 @@ def get_report_urls_map():
def is_report_allowed(request, url_path=None): def is_report_allowed(request, url_path=None):
from sapl.utils import get_report_urls_map # TODO: import global url_map = get_report_urls_map()
url_map = get_report_urls_map() # TODO: cache this!!! Globally
path = url_path if url_path else request.path path = url_path if url_path else request.path
authenticated = True if request.user.is_authenticated else False authenticated = request.user.is_authenticated
if path in url_map.keys(): if path in url_map:
path_metadata = url_map[path] path_metadata = url_map[path]
if not authenticated and path_metadata['public']: if not authenticated and path_metadata['public']:
return True return True

Loading…
Cancel
Save