From 00fc6c394ae19321e8418cd44e4eebb51f8eb48a Mon Sep 17 00:00:00 2001 From: "arnaud.morvan@camptocamp.com" Date: Mon, 18 May 2026 09:59:03 +0200 Subject: [PATCH 1/2] Add .venv to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ac2835f0..30447777 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.log .build +.venv node_modules thinkhazard/static/build thinkhazard/static/fonts From 665f9f3f2cb2c8b218fde3753f16527bbf149c87 Mon Sep 17 00:00:00 2001 From: "arnaud.morvan@camptocamp.com" Date: Mon, 18 May 2026 10:24:57 +0200 Subject: [PATCH 2/2] Remove S3 PDF storage --- tests/views/test_report.py | 13 +++---- thinkhazard/views/pdf.py | 78 +++++++++++++++++--------------------- 2 files changed, 39 insertions(+), 52 deletions(-) diff --git a/tests/views/test_report.py b/tests/views/test_report.py index 8bc4d1c0..bbd1abd3 100644 --- a/tests/views/test_report.py +++ b/tests/views/test_report.py @@ -17,10 +17,9 @@ # You should have received a copy of the GNU General Public License along with # ThinkHazard. If not, see . import os -import shutil -import asyncio import tempfile -from unittest.mock import patch, Mock +from io import BytesIO +from unittest.mock import patch from . import BaseTestCase @@ -147,12 +146,10 @@ def test_report__contacts(self): resp = self.testapp.get("/en/report/12-slug/EQ") self.assertEqual(len(resp.pyquery(".contacts ul li")), 0) - @patch('thinkhazard.views.pdf.create_and_upload_pdf') + @patch('thinkhazard.views.pdf.create_pdf') def test_create_pdf_report(self, mock): - # thanks to https://stackoverflow.com/a/29905620 - async def create(request, file_name, pages, object_name): - with open(file_name, "w") as file: - file.write("The pdf file") + async def create(request, pages): + return BytesIO("The pdf file".encode()) mock.side_effect = create resp = self.testapp.post("/en/report/create/32", status=200) diff --git a/thinkhazard/views/pdf.py b/thinkhazard/views/pdf.py index 62a65664..8c61b048 100644 --- a/thinkhazard/views/pdf.py +++ b/thinkhazard/views/pdf.py @@ -21,15 +21,13 @@ import datetime import logging import re -import tempfile -import os from typing import List import httpx from pyramid.view import view_config from pyramid.httpexceptions import HTTPBadRequest -from pyramid.response import FileResponse +from pyramid.response import Response from io import BytesIO from pypdf import PdfReader, PdfWriter @@ -104,7 +102,7 @@ def pdf_cover(request): """pdf_about: see index.py""" -async def create_and_upload_pdf(request, file_name: str, pages: List[str], object_name: str): +async def create_pdf(request, pages: List[str]) -> BytesIO: """Create a PDF file with the given pages using pyppeteer. """ async def render_page(page): @@ -143,56 +141,48 @@ async def render_page(page): for page in reader.pages: writer.add_page(page) - with open(file_name, "wb") as output: - writer.write(output) - - request.s3_helper.upload_file(file_name, object_name) + stream = BytesIO() + writer.write(stream) + return stream @view_config(route_name="create_pdf_report") def create_pdf_report(request): """View to create an asynchronous print job. """ - publication_date = request.publication_date - locale = request.locale_name division_code = request.matchdict.get("divisioncode") - force = "force" in request.params - - filename = "{:s}-{:s}.pdf".format(division_code, locale) - s3_path = "reports/{:%Y-%m-%d}/{}".format(publication_date, filename) - local_path = os.path.join(tempfile.gettempdir(), filename) - - if not force and request.s3_helper.object_exists(s3_path): - request.s3_helper.download_file(s3_path, local_path) - - if force or not os.path.isfile(local_path): - categories = ( - request.dbsession.query(HazardCategory) - .options(joinedload(HazardCategory.hazardtype)) - .join(HazardCategoryAdministrativeDivisionAssociation) - .join(AdministrativeDivision) - .join(HazardLevel) - .filter(AdministrativeDivision.code == division_code) - .order_by(HazardLevel.order) - ) - query_args = {"_query": {"_LOCALE_": request.locale_name}} - pages = [ - request.route_path("pdf_cover", divisioncode=division_code, **query_args), - request.route_path("pdf_about", **query_args), - ] - for cat in categories: - pages.append( - request.route_path( - "report_print", - divisioncode=division_code, - hazardtype=cat.hazardtype.mnemonic, - **query_args, - ) + + categories = ( + request.dbsession.query(HazardCategory) + .options(joinedload(HazardCategory.hazardtype)) + .join(HazardCategoryAdministrativeDivisionAssociation) + .join(AdministrativeDivision) + .join(HazardLevel) + .filter(AdministrativeDivision.code == division_code) + .order_by(HazardLevel.order) + ) + query_args = {"_query": {"_LOCALE_": request.locale_name}} + pages = [ + request.route_path("pdf_cover", divisioncode=division_code, **query_args), + request.route_path("pdf_about", **query_args), + ] + for cat in categories: + pages.append( + request.route_path( + "report_print", + divisioncode=division_code, + hazardtype=cat.hazardtype.mnemonic, + **query_args, ) - run(create_and_upload_pdf(request, local_path, pages, s3_path)) + ) + stream = run(create_pdf(request, pages)) + stream.seek(0) - response = FileResponse(local_path, request=request, content_type="application/pdf") + response = Response( + content_type="application/pdf", + ) response.headers["Content-Disposition"] = ( 'attachment; filename="ThinkHazard.pdf"' ) + response.app_iter = stream return response