From 1c24c1684355f4e89ba2b0a4a009816f123a2c1a Mon Sep 17 00:00:00 2001 From: Edward <9326037+edwardoliveira@users.noreply.github.com> Date: Tue, 28 Apr 2020 15:11:19 -0300 Subject: [PATCH] =?UTF-8?q?Permitir=20o=20download=20de=20documentos=20ace?= =?UTF-8?q?ss=C3=B3rios=20em=20mat=C3=A9ria=20legislativa=20(#3139)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix #3127 * Adiciona merger de PDF * Adiciona pypdf4 em requirements.txt * Adicionando mensagem de erro caso não tenha documento acessório * Subido algumas recomendações(logs, exceptions, localização de imports) * Mudando maneira de pegar o diretorio tmp * Concertando problema de css * Arrumando mensagem de erro para quando todos os documentos acessorios não tem pdf cadastrados * Generalizando tmp file para utils.py * Adicionando logs de info e quebrando linhas grandes Co-authored-by: eribeiro Co-authored-by: ulysses --- requirements/requirements.txt | 2 +- sapl/materia/urls.py | 7 +- sapl/materia/views.py | 130 +++++++++++++++++- .../materia/documentoacessorio_list.html | 13 ++ sapl/utils.py | 10 +- 5 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 sapl/templates/materia/documentoacessorio_list.html diff --git a/requirements/requirements.txt b/requirements/requirements.txt index eaea0a843..21ecac890 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -37,7 +37,7 @@ Whoosh==2.7.4 more-itertools==8.2.0 pysolr==3.6.0 - +PyPDF4==1.27.0 pyoai==2.5.0 daphne==2.2.5 diff --git a/sapl/materia/urls.py b/sapl/materia/urls.py index 0034ff548..d59075382 100644 --- a/sapl/materia/urls.py +++ b/sapl/materia/urls.py @@ -28,7 +28,8 @@ from sapl.materia.views import (AcompanhamentoConfirmarView, ExcluirTramitacaoEmLoteView, RetornarProposicao, MateriaPesquisaSimplesView, DespachoInicialMultiCreateView, - TipoTurnoTramitacaoCrud) + TipoTurnoTramitacaoCrud, + get_zip_docacessorios, get_pdf_docacessorios) from sapl.norma.views import NormaPesquisaSimplesView from sapl.protocoloadm.views import ( FichaPesquisaAdmView, FichaSelecionaAdmView) @@ -121,6 +122,10 @@ urlpatterns_materia = [ name='tramitacao_em_lote'), url(r'^materia/excluir-tramitacao-em-lote', ExcluirTramitacaoEmLoteView.as_view(), name='excluir_tramitacao_em_lote'), + url(r'^materia/docacessorio/zip/(?P\d+)$', get_zip_docacessorios, + name='compress_docacessorios'), + url(r'^materia/docacessorio/pdf/(?P\d+)$', get_pdf_docacessorios, + name='merge_docacessorios') ] diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 7e1101289..7f58df62d 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -6,11 +6,15 @@ import sapl import shutil import tempfile import weasyprint +import time from crispy_forms.layout import HTML from datetime import datetime from random import choice from string import ascii_letters, digits +from datetime import datetime +from PyPDF4 import PdfFileReader, PdfFileMerger +import zipfile from django.conf import settings from django.contrib import messages @@ -52,7 +56,7 @@ from sapl.settings import MAX_DOC_UPLOAD_SIZE, MEDIA_ROOT from sapl.utils import (autor_label, autor_modal, gerar_hash_arquivo, get_base_url, get_client_ip, get_mime_type_from_file_extension, lista_anexados, mail_service_configured, montar_row_autor, SEPARADOR_HASH_PROPOSICAO, - show_results_filter_set, YES_NO_CHOICES) + show_results_filter_set, YES_NO_CHOICES,get_tempfile_dir) from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, AnexadaEmLoteFilterSet, AdicionarVariasAutoriasFilterSet, @@ -2749,3 +2753,127 @@ class TipoMateriaCrud(CrudAux): self.object.save() return fv + + +def create_zip_docacessorios(materia): + logger = logging.getLogger(__name__) + docs = materia.documentoacessorio_set.\ + all().values_list('arquivo', flat=True) + if not docs: + return None, None + + docs_path = [os.path.join(MEDIA_ROOT, i) for i in docs] + + if not docs_path: + raise FileNotFoundError("Não há arquivos PDF cadastrados em documentos acessorios.") + logger.info("Gerando compilado PDF de documentos acessorios com {} documentos".format(docs_path)) + zipfilename = '{}/mat_{}_{}_docacessorios.zip'.format( + get_tempfile_dir(), + materia.pk, + time.mktime(datetime.now().timetuple())) + with zipfile.ZipFile(zipfilename, 'w', zipfile.ZIP_DEFLATED) as zipf: + for f in docs_path: + zipf.write(f, f.split(os.sep)[-1]) + + external_name = "mat_{}_{}_docacessorios.zip".format(materia.numero, materia.ano) + return external_name, zipfilename + + +def get_zip_docacessorios(request, pk): + logger = logging.getLogger(__name__) + username = request.user.username + materia = get_object_or_404(MateriaLegislativa, pk=pk) + try: + external_name, zipfilename = create_zip_docacessorios(materia) + logger.info("user= {}. Gerou o zip compilado de documento acessorios") + except FileNotFoundError: + logger.error("user= {}.Não há arquivos cadastrados".format(username)) + msg=_('Não há arquivos cadastrados nesses documentos acessórios.') + messages.add_message(request, messages.ERROR, msg) + return redirect(reverse('sapl.materia:documentoacessorio_list', + kwargs={'pk': pk})) + except Exception as e: + logger.error("user={}. Um erro inesperado ocorreu na criação do pdf de documentos acessorios: {}" + .format(username,str(e))) + msg=_('Um erro inesperado ocorreu. Entre em contato com o suporte do SAPL.') + messages.add_message(request, messages.ERROR, msg) + return redirect(reverse('sapl.materia:documentoacessorio_list', + kwargs={'pk': pk})) + + if not zipfilename: + msg=_('Não há nenhum documento acessório cadastrado.') + messages.add_message(request, messages.ERROR, msg) + return redirect(reverse('sapl.materia:documentoacessorio_list', + kwargs={'pk': pk})) + + with open(os.path.join(get_tempfile_dir(), zipfilename), 'rb') as f: + data = f.read() + response = HttpResponse(data, content_type='application/zip') + response['Content-Disposition'] = ('attachment; filename="%s"' + % external_name) + return response + + +def create_pdf_docacessorios(materia): + logger = logging.getLogger(__name__) + docs = materia.documentoacessorio_set. \ + all().values_list('arquivo', flat=True) + if not docs: + return None, None + + # TODO: o for-comprehension abaixo filtra os arquivos não PDF. + # TODO: o que fazer com os arquivos não PDF? converter? ignorar? + docs_path = [os.path.join(MEDIA_ROOT, i) for i in docs if i.lower().endswith('pdf')] + if not docs_path: + raise FileNotFoundError("Não há arquivos PDF cadastrados em documentos acessorios.") + logger.info("Gerando compilado PDF de documentos acessorios com {} documentos" + .format(docs_path)) + merged_pdf = '{}/mat_{}_{}_docacessorios.pdf'.format( + get_tempfile_dir(), + materia.pk, + time.mktime(datetime.now().timetuple())) + + merger = PdfFileMerger() + for f in docs_path: + merger.append(fileobj=f) + merger.write(fileobj=open(merged_pdf, "wb")) + merger.close() + + external_name = "mat_{}_{}_docacessorios.pdf".format(materia.numero, materia.ano) + return external_name, merged_pdf + + +def get_pdf_docacessorios(request, pk): + materia = get_object_or_404(MateriaLegislativa, pk=pk) + logger = logging.getLogger(__name__) + username = request.user.username + try: + external_name, pdffilename = create_pdf_docacessorios(materia) + logger.info("user= {}. Gerou o pdf compilado de documento acessorios") + except FileNotFoundError: + logger.error("user= {}.Não há arquivos cadastrados".format(username)) + msg=_('Não há arquivos cadastrados nesses documentos acessórios.') + messages.add_message(request, messages.ERROR, msg) + return redirect(reverse('sapl.materia:documentoacessorio_list', + kwargs={'pk': pk})) + except Exception as e: + logger.error("user= {}.Um erro inesperado ocorreu na criação do pdf de documentos acessorios: {}" + .format(username,str(e))) + msg=_('Um erro inesperado ocorreu. Entre em contato com o suporte do SAPL.') + messages.add_message(request, messages.ERROR, msg) + return redirect(reverse('sapl.materia:documentoacessorio_list', + kwargs={'pk': pk})) + + if not pdffilename: + msg=_('Não há nenhum documento acessório PDF cadastrado.') + messages.add_message(request, messages.ERROR, msg) + return redirect(reverse('sapl.materia:documentoacessorio_list', + kwargs={'pk': pk})) + + with open(os.path.join(get_tempfile_dir(), pdffilename), 'rb') as f: + data = f.read() + response = HttpResponse(data, content_type='application/pdf') + response['Content-Disposition'] = ('attachment; filename="%s"' + % external_name) + return response + diff --git a/sapl/templates/materia/documentoacessorio_list.html b/sapl/templates/materia/documentoacessorio_list.html new file mode 100644 index 000000000..08bae0819 --- /dev/null +++ b/sapl/templates/materia/documentoacessorio_list.html @@ -0,0 +1,13 @@ +{% extends "crud/list.html" %} +{% load i18n %} +{% block base_content %} + {{ block.super }} +
+ + +
+{% endblock %} \ No newline at end of file diff --git a/sapl/utils.py b/sapl/utils.py index 7e773bf57..a9e2d1855 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -7,7 +7,8 @@ import magic import os import re import unicodedata - +import platform +import tempfile from crispy_forms.layout import Button, HTML from easy_thumbnails import source_generators from floppyforms import ClearableFileInput @@ -1076,7 +1077,7 @@ class OverwriteStorage(FileSystemStorage): os.remove(os.path.join(settings.MEDIA_ROOT, name)) return name - + def verifica_afastamento_parlamentar(parlamentar, data_inicio, data_fim=None): from sapl.parlamentares.models import AfastamentoParlamentar if data_fim: @@ -1092,3 +1093,8 @@ def verifica_afastamento_parlamentar(parlamentar, data_inicio, data_fim=None): data_fim__gte=data_inicio).exists() return existe_afastamento + + +def get_tempfile_dir(): + return '/tmp' if platform.system() == 'Darwin' else tempfile.gettempdir() +