Browse Source

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.
pull/3845/head
João Moraes 3 weeks ago
parent
commit
57fa1e603d
  1. 24
      sapl/sessao/models.py
  2. 85
      sapl/sessao/tests/test_sessao_view.py
  3. 17
      sapl/sessao/views.py

24
sapl/sessao/models.py

@ -153,6 +153,28 @@ def anexo_upload_path(instance, filename):
# return get_sessao_media_path(instance, 'anexo', filename) # return get_sessao_media_path(instance, 'anexo', filename)
class SessaoPlenariaQuerySet(models.QuerySet):
def visiveis_para(self, user):
"""Restringe às sessões que o usuário pode ver na consulta pública.
Usuário autenticado enxerga todas. Para o público, ficam ocultas as
sessões cadastradas com antecedência, ou seja, as marcadas como não
iniciadas e sem pauta publicada.
`iniciada` nulo conta como visível: o default do campo surgiu na
migração 0027, que não preencheu as linhas existentes, então bases
antigas têm sessões realizadas com o campo em NULL.
"""
if user.is_authenticated:
return self
return self.filter(
Q(publicar_pauta=True) |
Q(iniciada=True) |
Q(iniciada__isnull=True))
class SessaoPlenaria(models.Model): class SessaoPlenaria(models.Model):
# TODO trash??? Seems to have been a FK in the past. Would be: # TODO trash??? Seems to have been a FK in the past. Would be:
# andamento_sessao = models.ForeignKey( # andamento_sessao = models.ForeignKey(
@ -255,6 +277,8 @@ class SessaoPlenaria(models.Model):
) )
) )
objects = SessaoPlenariaQuerySet.as_manager()
class Meta: class Meta:
verbose_name = _('Sessão Plenária') verbose_name = _('Sessão Plenária')
verbose_name_plural = _('Sessões Plenárias') verbose_name_plural = _('Sessões Plenárias')

85
sapl/sessao/tests/test_sessao_view.py

@ -1,4 +1,7 @@
import pytest import pytest
from datetime import date
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from model_bakery import baker from model_bakery import baker
@ -147,3 +150,85 @@ class TestResumoView():
resultado_get_ocorrencia = get_ocorrencias_da_sessao(self.sessao_plenaria) resultado_get_ocorrencia = get_ocorrencias_da_sessao(self.sessao_plenaria)
assert resultado_get_ocorrencia['ocorrencias_da_sessao'][0] == ocorrencia assert resultado_get_ocorrencia['ocorrencias_da_sessao'][0] == ocorrencia
@pytest.mark.django_db(transaction=False)
def test_visiveis_para_oculta_do_anonimo_apenas_a_sessao_previa():
previa = baker.make(SessaoPlenaria, iniciada=False, publicar_pauta=False)
com_pauta = baker.make(SessaoPlenaria, iniciada=False, publicar_pauta=True)
iniciada = baker.make(SessaoPlenaria, iniciada=True, publicar_pauta=False)
# Sessões anteriores à migração 0027 ficaram com `iniciada` em NULL.
legada = baker.make(SessaoPlenaria, iniciada=None, publicar_pauta=False)
visiveis = SessaoPlenaria.objects.visiveis_para(AnonymousUser())
assert previa not in visiveis
assert com_pauta in visiveis
assert iniciada in visiveis
assert legada in visiveis
@pytest.mark.django_db(transaction=False)
def test_visiveis_para_nao_oculta_nada_de_usuario_autenticado():
previa = baker.make(SessaoPlenaria, iniciada=False, publicar_pauta=False)
operador = baker.make(get_user_model())
assert previa in SessaoPlenaria.objects.visiveis_para(operador)
@pytest.mark.django_db(transaction=False)
def test_pesquisar_sessao_nao_lista_sessao_previa_para_anonimo(client):
previa = baker.make(SessaoPlenaria, iniciada=False, publicar_pauta=False,
data_inicio=date(2025, 11, 5))
iniciada = baker.make(SessaoPlenaria, iniciada=True, publicar_pauta=False,
data_inicio=date(2025, 11, 5))
response = client.get(reverse('sapl.sessao:pesquisar_sessao'),
{'data_inicio__year': '2025'})
assert response.status_code == 200
assert previa not in response.context['object_list']
assert iniciada in response.context['object_list']
@pytest.mark.django_db(transaction=False)
def test_pesquisar_sessao_lista_sessao_previa_para_autenticado(admin_client):
previa = baker.make(SessaoPlenaria, iniciada=False, publicar_pauta=False,
data_inicio=date(2025, 11, 5))
response = admin_client.get(reverse('sapl.sessao:pesquisar_sessao'),
{'data_inicio__year': '2025'})
assert response.status_code == 200
assert previa in response.context['object_list']
@pytest.mark.django_db(transaction=False)
def test_detail_sessao_previa_indisponivel_para_anonimo(client):
previa = baker.make(SessaoPlenaria, iniciada=False, publicar_pauta=False)
response = client.get(reverse('sapl.sessao:sessaoplenaria_detail',
kwargs={'pk': previa.pk}))
assert response.status_code == 404
@pytest.mark.django_db(transaction=False)
def test_resumo_de_sessao_previa_indisponivel_para_anonimo(client):
previa = baker.make(SessaoPlenaria, iniciada=False, publicar_pauta=False)
response = client.get(reverse('sapl.sessao:resumo',
kwargs={'pk': previa.pk}))
assert response.status_code == 404
@pytest.mark.django_db(transaction=False)
def test_pauta_nao_publicada_indisponivel_para_anonimo(client):
sem_pauta = baker.make(SessaoPlenaria, iniciada=True, publicar_pauta=False)
response = client.get(reverse('sapl.sessao:pauta_sessao_detail',
kwargs={'pk': sem_pauta.pk}))
assert response.status_code == 404

17
sapl/sessao/views.py

@ -1348,6 +1348,12 @@ class SessaoCrud(Crud):
class DetailView(Crud.DetailView): class DetailView(Crud.DetailView):
def get(self, request, *args, **kwargs):
if not SessaoPlenaria.objects.visiveis_para(
request.user).filter(pk=kwargs.get('pk')).exists():
raise Http404()
return super().get(request, *args, **kwargs)
@property @property
def layout_key(self): def layout_key(self):
sessao = self.object sessao = self.object
@ -2327,6 +2333,9 @@ class ResumoView(DetailView):
model = SessaoPlenaria model = SessaoPlenaria
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def get_queryset(self):
return SessaoPlenaria.objects.visiveis_para(self.request.user)
def get_context(self, *args, **kwargs): def get_context(self, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
context = self.get_context_data(object=self.object) context = self.get_context_data(object=self.object)
@ -3833,6 +3842,12 @@ class PautaSessaoDetailView(PautaMultiFormatOutputMixin, DetailView):
('situacao', 'Situação') ('situacao', 'Situação')
) )
def get_queryset(self):
qs = SessaoPlenaria.objects.all()
if not self.request.user.is_authenticated:
qs = qs.filter(publicar_pauta=True)
return qs
def hook_autor(self, obj): def hook_autor(self, obj):
return ','.join(obj['autor']) return ','.join(obj['autor'])
@ -4028,6 +4043,8 @@ class PesquisarSessaoPlenariaView(MultiFormatOutputMixin, FilterView):
qs = self.get_queryset().select_related( qs = self.get_queryset().select_related(
'tipo', 'sessao_legislativa', 'legislatura') 'tipo', 'sessao_legislativa', 'legislatura')
qs = qs.visiveis_para(self.request.user)
qs = qs.distinct().order_by( qs = qs.distinct().order_by(
'-legislatura__numero', '-data_inicio', '-hora_inicio') '-legislatura__numero', '-data_inicio', '-hora_inicio')

Loading…
Cancel
Save