Browse Source

Merge a212ae6c73 into c46a5a0c7c

pull/3869/merge
joaohortsenado 2 weeks ago
committed by GitHub
parent
commit
0a60cb3896
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 47
      sapl/relatorios/forms.py
  2. 155
      sapl/relatorios/tests.py
  3. 8
      sapl/relatorios/views.py
  4. 4
      sapl/templates/relatorios/RelatorioMateriasPorAutor_filter.html
  5. 4
      sapl/templates/relatorios/RelatorioNormasPorAutor_filter.html
  6. 2
      sapl/templates/relatorios/relatorio_materias_por_autor.html
  7. 2
      sapl/templates/relatorios/relatorio_normas_por_autor.html

47
sapl/relatorios/forms.py

@ -625,19 +625,52 @@ class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
) )
class AutorOuPeriodoForm(forms.Form):
"""
Relatórios por autor aceitam pesquisa sem autor, desde que informado um
período fechado, para não listar todo o acervo da Casa de uma só vez.
"""
campo_autor = None
campo_periodo = None
def clean(self):
cleaned_data = super().clean()
if cleaned_data.get(self.campo_autor) or \
self.campo_periodo in self.errors:
return cleaned_data
periodo = cleaned_data.get(self.campo_periodo)
if not periodo or periodo.start is None or periodo.stop is None:
raise forms.ValidationError(
_('Selecione um autor ou informe o período '
'(data inicial e final).'))
return cleaned_data
class RelatorioMateriasPorAutorForm(AutorOuPeriodoForm):
campo_autor = 'autoria__autor'
campo_periodo = 'data_apresentacao'
class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet): class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput()) autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
@property @property
def qs(self): def qs(self):
parent = super().qs parent = super().qs
if not self.data.get('autoria__autor'): if not self.is_valid():
return parent.none() return parent.none()
if self.form.cleaned_data.get('autoria__autor'):
return parent.distinct().order_by('-ano', '-numero', 'tipo', 'autoria__autor', '-autoria__primeiro_autor') return parent.distinct().order_by('-ano', '-numero', 'tipo', 'autoria__autor', '-autoria__primeiro_autor')
# Sem autor, o período exigido pelo form limita a consulta.
return parent.order_by('-ano', '-numero', 'tipo')
class Meta(FilterOverridesMetaMixin): class Meta(FilterOverridesMetaMixin):
model = MateriaLegislativa model = MateriaLegislativa
fields = ['tipo', 'data_apresentacao'] fields = ['tipo', 'data_apresentacao']
form = RelatorioMateriasPorAutorForm
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -738,20 +771,30 @@ class RelatorioHistoricoTramitacaoAdmFilterSet(django_filters.FilterSet):
) )
class RelatorioNormasPorAutorForm(AutorOuPeriodoForm):
campo_autor = 'autorianorma__autor'
campo_periodo = 'data'
class RelatorioNormasPorAutorFilterSet(django_filters.FilterSet): class RelatorioNormasPorAutorFilterSet(django_filters.FilterSet):
autorianorma__autor = django_filters.CharFilter(widget=forms.HiddenInput()) autorianorma__autor = django_filters.CharFilter(widget=forms.HiddenInput())
@property @property
def qs(self): def qs(self):
parent = super().qs parent = super().qs
if not self.data.get('autorianorma__autor'): if not self.is_valid():
return parent.none() return parent.none()
if self.form.cleaned_data.get('autorianorma__autor'):
return parent.distinct() \ return parent.distinct() \
.order_by('autorianorma__autor', '-autorianorma__primeiro_autor', 'tipo', '-ano', '-numero') .order_by('autorianorma__autor', '-autorianorma__primeiro_autor', 'tipo', '-ano', '-numero')
# Sem autor, agrupa as normas do período pelo primeiro autor.
return parent.filter(autorianorma__primeiro_autor=True) \
.order_by('autorianorma__autor', 'tipo', '-ano', '-numero')
class Meta(FilterOverridesMetaMixin): class Meta(FilterOverridesMetaMixin):
model = NormaJuridica model = NormaJuridica
fields = ['tipo', 'data'] fields = ['tipo', 'data']
form = RelatorioNormasPorAutorForm
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

155
sapl/relatorios/tests.py

@ -1,7 +1,13 @@
from datetime import date
import pytest import pytest
from django.urls import reverse
from model_bakery import baker from model_bakery import baker
from sapl.base.models import CasaLegislativa from sapl.base.models import Autor, CasaLegislativa
from sapl.materia.models import Autoria, MateriaLegislativa
from sapl.norma.models import AutoriaNorma, NormaJuridica
from sapl.relatorios import views as relatorios_views
from sapl.relatorios.views import get_sessao_plenaria from sapl.relatorios.views import get_sessao_plenaria
from sapl.sessao.models import ExpedienteSessao, SessaoPlenaria, TipoExpediente from sapl.sessao.models import ExpedienteSessao, SessaoPlenaria, TipoExpediente
from sapl.sessao.views import get_expedientes from sapl.sessao.views import get_expedientes
@ -50,3 +56,150 @@ def test_relatorio_sessao_tem_mesma_ordem_de_expedientes_do_resumo():
da_tela = [e['tipo'].nome for e in get_expedientes(sessao)['expedientes']] da_tela = [e['tipo'].nome for e in get_expedientes(sessao)['expedientes']]
assert do_pdf == da_tela assert do_pdf == da_tela
MSG_AUTOR_OU_PERIODO = 'Selecione um autor ou informe o período'
def params_materias_por_autor(autor='', inicio='', fim='', **extra):
return dict(tipo='', data_apresentacao_0=inicio,
data_apresentacao_1=fim, autoria__autor=autor,
pesquisar='Pesquisar', **extra)
def params_normas_por_autor(autor='', inicio='', fim='', **extra):
return dict(tipo='', data_0=inicio, data_1=fim,
autorianorma__autor=autor, pesquisar='Pesquisar', **extra)
def cria_materia(numero, data_apresentacao, autores):
materia = baker.make(MateriaLegislativa, numero=numero,
ano=data_apresentacao.year,
data_apresentacao=data_apresentacao,
ementa='Ementa da matéria {}'.format(numero))
for i, autor in enumerate(autores):
baker.make(Autoria, materia=materia, autor=autor,
primeiro_autor=(i == 0))
return materia
def cria_norma(numero, data_norma, autores):
norma = baker.make(NormaJuridica, numero=numero, ano=data_norma.year,
data=data_norma,
ementa='Ementa da norma {}'.format(numero))
for i, autor in enumerate(autores):
baker.make(AutoriaNorma, norma=norma, autor=autor,
primeiro_autor=(i == 0))
return norma
@pytest.fixture
def pdf_html(monkeypatch):
"""Troca o WeasyPrint por uma captura do HTML que iria para o PDF."""
baker.make(CasaLegislativa, cep='')
capturado = {}
def make_pdf(base_url, main_template, header_template, **kwargs):
capturado['html'] = main_template
return b'%PDF-fake'
monkeypatch.setattr(relatorios_views, 'make_pdf', make_pdf)
return capturado
@pytest.mark.django_db(transaction=False)
def test_materias_por_autor_sem_autor_e_sem_periodo_pede_filtro(client):
response = client.get(reverse('sapl.relatorios:materia_por_autor'),
params_materias_por_autor())
assert MSG_AUTOR_OU_PERIODO in response.content.decode()
assert not response.context.get('show_results')
@pytest.mark.django_db(transaction=False)
def test_materias_por_autor_sem_autor_lista_materias_do_periodo(client):
"""OSTicket #885134: sem autor, a pesquisa por período deve listar as
matérias do período, uma vez cada, mesmo as de mais de um autor."""
ana = baker.make(Autor, nome='Ana')
bruno = baker.make(Autor, nome='Bruno')
m1 = cria_materia(1, date(2025, 3, 1), [ana, bruno])
m2 = cria_materia(2, date(2025, 6, 1), [bruno])
cria_materia(3, date(2024, 1, 1), [ana])
response = client.get(
reverse('sapl.relatorios:materia_por_autor'),
params_materias_por_autor(inicio='01/01/2025', fim='31/12/2025'))
assert list(response.context['object_list']) == [m2, m1]
content = response.content.decode()
assert 'Autor: Todos' in content
assert 'Autor(es)' in content
@pytest.mark.django_db(transaction=False)
def test_materias_por_autor_com_autor_dispensa_periodo(client):
ana = baker.make(Autor, nome='Ana')
bruno = baker.make(Autor, nome='Bruno')
m1 = cria_materia(1, date(2025, 3, 1), [ana, bruno])
cria_materia(2, date(2025, 6, 1), [bruno])
m3 = cria_materia(3, date(2024, 1, 1), [ana])
response = client.get(reverse('sapl.relatorios:materia_por_autor'),
params_materias_por_autor(autor=ana.pk))
assert list(response.context['object_list']) == [m1, m3]
@pytest.mark.django_db(transaction=False)
def test_materias_por_autor_pdf_lista_materias(client, pdf_html):
"""OSTicket #885134: o PDF saía "Nenhum documento encontrado" mesmo com
a tela listando as matérias do autor."""
ana = baker.make(Autor, nome='Ana')
cria_materia(1, date(2025, 3, 1), [ana])
response = client.get(
reverse('sapl.relatorios:materia_por_autor'),
params_materias_por_autor(autor=ana.pk, relatorio='on'))
assert response['Content-Type'].startswith('application/pdf')
assert 'Ementa da matéria 1' in pdf_html['html']
assert 'Nenhum documento encontrado' not in pdf_html['html']
@pytest.mark.django_db(transaction=False)
def test_materias_por_autor_pdf_sem_filtro_volta_ao_form(client, pdf_html):
response = client.get(
reverse('sapl.relatorios:materia_por_autor'),
params_materias_por_autor(relatorio='on'))
assert response['Content-Type'].startswith('text/html')
assert MSG_AUTOR_OU_PERIODO in response.content.decode()
assert 'html' not in pdf_html
@pytest.mark.django_db(transaction=False)
def test_normas_por_autor_sem_autor_e_sem_periodo_pede_filtro(client):
response = client.get(reverse('sapl.relatorios:normas_por_autor'),
params_normas_por_autor())
assert MSG_AUTOR_OU_PERIODO in response.content.decode()
assert not response.context.get('show_results')
@pytest.mark.django_db(transaction=False)
def test_normas_por_autor_sem_autor_agrupa_pelo_primeiro_autor(client):
ana = baker.make(Autor, nome='Ana')
bruno = baker.make(Autor, nome='Bruno')
n1 = cria_norma('1', date(2025, 3, 1), [ana, bruno])
n2 = cria_norma('2', date(2025, 6, 1), [bruno])
cria_norma('3', date(2024, 1, 1), [ana])
response = client.get(
reverse('sapl.relatorios:normas_por_autor'),
params_normas_por_autor(inicio='01/01/2025', fim='31/12/2025'))
assert list(response.context['object_list']) == [n1, n2]
content = response.content.decode()
assert 'Autor: Todos' in content
assert 'Autor: Ana' in content
assert 'Autor: Bruno' in content

8
sapl/relatorios/views.py

@ -1882,7 +1882,8 @@ class RelatorioMixin:
is_relatorio = request.GET.get('relatorio') is_relatorio = request.GET.get('relatorio')
context = self.get_context_data(filter=self.filterset) context = self.get_context_data(filter=self.filterset)
if is_relatorio: # Com o form inválido, volta à tela com os erros em vez de um PDF vazio
if is_relatorio and self.filterset.form.is_valid():
return self.relatorio(request, context) return self.relatorio(request, context)
else: else:
return self.render_to_response(context) return self.render_to_response(context)
@ -2710,6 +2711,8 @@ class RelatorioMateriasPorAutorView(RelatorioMixin, FilterView):
qs = context['object_list'] qs = context['object_list']
context['qtdes'] = num_materias_por_tipo(qs) context['qtdes'] = num_materias_por_tipo(qs)
context['object_list'] = qs.select_related('tipo').prefetch_related(
'autoria_set__autor__autor_related')
qr = self.request.GET.copy() qr = self.request.GET.copy()
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
@ -2955,6 +2958,9 @@ class RelatorioNormasPorAutorView(RelatorioMixin, FilterView):
if qtde > 0: if qtde > 0:
qtdes[tipo] = qtde qtdes[tipo] = qtde
context['qtdes'] = qtdes context['qtdes'] = qtdes
context['object_list'] = context['object_list'] \
.select_related('tipo') \
.prefetch_related('autorianorma_set__autor__autor_related')
qr = self.request.GET.copy() qr = self.request.GET.copy()
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''

4
sapl/templates/relatorios/RelatorioMateriasPorAutor_filter.html

@ -13,7 +13,7 @@
</div> </div>
<br /><br /><br /><br /> <br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b> <b>PARÂMETROS DE PESQUISA:<br /></b>
&emsp;Autor: {{ autor }}<br /> &emsp;Autor: {{ autor|default_if_none:"Todos" }}<br />
&emsp;Tipo de matéria: {{ tipo }}<br /> &emsp;Tipo de matéria: {{ tipo }}<br />
&emsp;Data de apresentação: {{ periodo }}<br /><br /><br/> &emsp;Data de apresentação: {{ periodo }}<br /><br /><br/>
@ -43,7 +43,7 @@
<tr class="active"> <tr class="active">
<th width="10%">Matéria</th> <th width="10%">Matéria</th>
<th>Ementa</th> <th>Ementa</th>
<th width="20%">Coautor(es)</th> <th width="20%">{% if autor %}Coautor(es){% else %}Autor(es){% endif %}</th>
</tr> </tr>
</thead> </thead>
{% for materia in object_list %} {% for materia in object_list %}

4
sapl/templates/relatorios/RelatorioNormasPorAutor_filter.html

@ -13,7 +13,7 @@
</div> </div>
<br /><br /><br /><br /> <br /><br /><br /><br />
<b>PARÂMETROS DE PESQUISA:<br /></b> <b>PARÂMETROS DE PESQUISA:<br /></b>
&emsp;Autor: {{ autor }}<br /> &emsp;Autor: {{ autor|default_if_none:"Todos" }}<br />
&emsp;Tipo de Norma: {{ tipo }}<br /> &emsp;Tipo de Norma: {{ tipo }}<br />
&emsp;Data: {{periodo}}<br /><br /><br/> &emsp;Data: {{periodo}}<br /><br /><br/>
{% if object_list %} {% if object_list %}
@ -55,7 +55,7 @@
<td>{{norma.ementa}}<br>{{norma.observacao}}</td> <td>{{norma.ementa}}<br>{{norma.observacao}}</td>
<td> <td>
{% for autoria in norma.autorianorma_set.all %} {% for autoria in norma.autorianorma_set.all %}
{% if autoria.autor != autor %} {% if autor and autoria.autor != autor or not autor and not autoria.primeiro_autor %}
{{ autoria.autor }}<br /> {{ autoria.autor }}<br />
{% endif %} {% endif %}
{% endfor %} {% endfor %}

2
sapl/templates/relatorios/relatorio_materias_por_autor.html

@ -6,7 +6,7 @@
{% block content %} {% block content %}
<h2>Matérias por Autor</h2> <h2>Matérias por Autor</h2>
<b>PARÂMETROS DE PESQUISA:<br /></b> <b>PARÂMETROS DE PESQUISA:<br /></b>
&emsp;Autor: {{ autor }}<br /> &emsp;Autor: {{ autor|default_if_none:"Todos" }}<br />
&emsp;Tipo de matéria: {{ tipo }}<br /> &emsp;Tipo de matéria: {{ tipo }}<br />
&emsp;Data de apresentação: {{periodo}}<br /><br /><br/> &emsp;Data de apresentação: {{periodo}}<br /><br /><br/>

2
sapl/templates/relatorios/relatorio_normas_por_autor.html

@ -8,7 +8,7 @@
<h2>Norma por Autor</h2> <h2>Norma por Autor</h2>
<b>PARÂMETROS DE PESQUISA:<br /></b> <b>PARÂMETROS DE PESQUISA:<br /></b>
&emsp;Autor: {{ autor }}<br /> &emsp;Autor: {{ autor|default_if_none:"Todos" }}<br />
&emsp;Tipo de Norma: {{ tipo }}<br /> &emsp;Tipo de Norma: {{ tipo }}<br />
&emsp;Data: {{periodo}}<br /><br /><br/> &emsp;Data: {{periodo}}<br /><br /><br/>
{% if object_list %} {% if object_list %}

Loading…
Cancel
Save