Browse Source

Feat(Materia): Adicionar botão Baixar Todos os Documentos (ZIP)

Permite baixar ZIP com o PDF da matéria (assinado ou original) junto
com todos os documentos acessórios. Botão aparece na página de detalhe
e na pesquisa de matérias quando há documentos acessórios cadastrados.
pull/3858/head
rangelbruno 7 months ago
committed by rangelbruno
parent
commit
83a7263a75
  1. 4
      sapl/materia/urls.py
  2. 87
      sapl/materia/views.py
  3. 13
      sapl/static/js/materia_pesquisa_download_pdfs.js
  4. 7
      sapl/templates/materia/materialegislativa_detail.html
  5. 3
      sapl/templates/materia/materialegislativa_filter.html

4
sapl/materia/urls.py

@ -29,7 +29,7 @@ from sapl.materia.views import (AcompanhamentoConfirmarView,
RetornarProposicao,
MateriaPesquisaSimplesView,
DespachoInicialMultiCreateView,
get_zip_docacessorios, get_pdf_docacessorios,
get_zip_docacessorios, get_pdf_docacessorios, get_zip_completo,
configEtiquetaMateriaLegislativaCrud,
PesquisarStatusTramitacaoView, HistoricoProposicaoView)
from sapl.materia.onlyoffice_views import (onlyoffice_config, onlyoffice_download,
@ -147,6 +147,8 @@ urlpatterns_materia = [
name='compress_docacessorios'),
url(r'^materia/docacessorio/pdf/(?P<pk>\d+)$', get_pdf_docacessorios,
name='merge_docacessorios'),
url(r'^materia/zip-completo/(?P<pk>\d+)$', get_zip_completo,
name='zip_completo_materia'),
# OnlyOffice endpoints para Matéria Legislativa
url(r'^materia/(?P<pk>\d+)/onlyoffice/editor$', materia_onlyoffice_editor,

87
sapl/materia/views.py

@ -3256,6 +3256,93 @@ def get_zip_docacessorios(request, pk):
return response
def create_zip_completo(materia):
"""
Creates in memory zip file with materia PDF + all documentos acessórios
"""
logger = logging.getLogger(__name__)
files_to_zip = []
# PDF da matéria: prefere pdf_assinado, fallback texto_original
if materia.pdf_assinado:
files_to_zip.append(os.path.join(MEDIA_ROOT, str(materia.pdf_assinado)))
elif materia.texto_original:
files_to_zip.append(os.path.join(MEDIA_ROOT, str(materia.texto_original)))
# Documentos acessórios: prefere pdf_assinado, fallback arquivo
for doc in materia.documentoacessorio_set.all():
if doc.pdf_assinado:
files_to_zip.append(os.path.join(MEDIA_ROOT, str(doc.pdf_assinado)))
elif doc.arquivo:
files_to_zip.append(os.path.join(MEDIA_ROOT, str(doc.arquivo)))
if not files_to_zip:
return None, None
logger.info(
"Gerando zip completo da matéria com {} arquivos: {}".format(
len(files_to_zip), files_to_zip))
_zipfile = BytesIO()
added = 0
try:
with zipfile.ZipFile(_zipfile, 'w', zipfile.ZIP_DEFLATED) as zipf:
for f in files_to_zip:
if os.path.exists(f):
zipf.write(f, f.split(os.sep)[-1])
added += 1
else:
logger.warning("Arquivo não encontrado: {}".format(f))
except Exception as e:
logger.error(e)
raise e
if not added:
raise FileNotFoundError(
"Nenhum arquivo encontrado no disco para a matéria.")
external_name = "mat_{}_{}_completo.zip".format(
materia.numero, materia.ano)
return external_name, _zipfile.getvalue()
def get_zip_completo(request, pk):
logger = logging.getLogger(__name__)
username = 'Usuário anônimo' if request.user.is_anonymous else request.user.username
materia = get_object_or_404(MateriaLegislativa, pk=pk)
data = None
try:
external_name, data = create_zip_completo(materia)
logger.info(
"user= {}. Gerou o zip completo da matéria {}".format(username, pk))
except FileNotFoundError:
logger.error("user= {}.Arquivos não encontrados para matéria {}".format(username, pk))
msg = _('Não há arquivos disponíveis para download nesta matéria.')
messages.add_message(request, messages.ERROR, msg)
return redirect(reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': pk}))
except Exception as e:
logger.error("user={}. Erro ao criar zip completo da matéria {}: {}"
.format(username, pk, str(e)))
msg = _('Um erro inesperado ocorreu. Entre em contato com o suporte do SGVP.')
messages.add_message(request, messages.ERROR, msg)
return redirect(reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': pk}))
if not data:
msg = _('Não há nenhum documento cadastrado para esta matéria.')
messages.add_message(request, messages.ERROR, msg)
return redirect(reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': pk}))
response = HttpResponse(data, content_type='application/zip')
response['Content-Disposition'] = ('attachment; filename="%s"'
% external_name)
return response
def create_pdf_docacessorios(materia):
"""
Creates a unified in memory PDF file

13
sapl/static/js/materia_pesquisa_download_pdfs.js

@ -166,6 +166,19 @@
// insere logo após o </strong>
strong.parentNode.insertBefore(document.createTextNode(' '), strong.nextSibling);
strong.parentNode.insertBefore(btn, strong.nextSibling);
// Botão "Baixar Todos" (ZIP com matéria + acessórios)
const materiaPk = link.getAttribute('data-materia-pk');
if (materiaPk) {
const zipBtn = document.createElement('a');
zipBtn.setAttribute('data-sapl-download-all-btn', '1');
zipBtn.className = 'btn btn-sm btn-info ml-1';
zipBtn.title = 'Baixar todos os documentos (ZIP)';
zipBtn.href = '/materia/zip-completo/' + materiaPk;
zipBtn.innerHTML = '<i class="fas fa-file-archive"></i>';
strong.parentNode.insertBefore(document.createTextNode(' '), btn.nextSibling);
strong.parentNode.insertBefore(zipBtn, btn.nextSibling.nextSibling);
}
});
}

7
sapl/templates/materia/materialegislativa_detail.html

@ -28,6 +28,13 @@
{% endif %}
</div>
{% endif %}
{% if object.documentoacessorio_set.all.exists %}
<div class="actions btn-group btn-group-sm" role="group">
<a class="btn btn-info" href="{% url 'sapl.materia:zip_completo_materia' object.pk %}">
<i class="fa fa-file-archive-o"></i> {% trans "Baixar Todos os Documentos" %}
</a>
</div>
{% endif %}
{% endblock sub_actions %}
{% block editions %}

3
sapl/templates/materia/materialegislativa_filter.html

@ -210,7 +210,8 @@
{% if m.texto_original %}
<strong>
<a href="{{m.texto_original.url}}"
{% if m.pdf_assinado %}data-pdf-assinado="{{m.pdf_assinado.url}}"{% endif %}>
{% if m.pdf_assinado %}data-pdf-assinado="{{m.pdf_assinado.url}}"{% endif %}
{% if m.documentoacessorio_set.all.exists %}data-materia-pk="{{ m.id }}"{% endif %}>
Texto Original
</a>
</strong>

Loading…
Cancel
Save