65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
|
|
"""Printable NCR PDF (WeasyPrint) — a clean single-document rendering of the
|
||
|
|
complete NCR for hard-copy travelers and audits.
|
||
|
|
|
||
|
|
WeasyPrint is imported lazily so environments without the Pango/Cairo system
|
||
|
|
libraries (e.g. unit tests) can still import the app.
|
||
|
|
"""
|
||
|
|
from functools import partial
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import anyio
|
||
|
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||
|
|
|
||
|
|
from app.domain import STAGE_LABELS, Stage
|
||
|
|
from app.models import Ncr
|
||
|
|
from app.models.base import utcnow
|
||
|
|
from app.services.storage import attachment_abs_path
|
||
|
|
|
||
|
|
_TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "templates"
|
||
|
|
|
||
|
|
_env = Environment(
|
||
|
|
loader=FileSystemLoader(_TEMPLATES_DIR),
|
||
|
|
autoescape=select_autoescape(["html"]),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _render_html(ncr: Ncr) -> str:
|
||
|
|
images = []
|
||
|
|
other_files = []
|
||
|
|
for att in ncr.attachments:
|
||
|
|
entry = {
|
||
|
|
"filename": att.original_filename,
|
||
|
|
"uploaded_by": att.uploaded_by.display_name,
|
||
|
|
"uploaded_at": att.uploaded_at,
|
||
|
|
"size_kb": max(1, att.size_bytes // 1024),
|
||
|
|
}
|
||
|
|
path = attachment_abs_path(att.stored_path)
|
||
|
|
if att.is_image and path.is_file():
|
||
|
|
entry["src"] = path.as_uri()
|
||
|
|
images.append(entry)
|
||
|
|
else:
|
||
|
|
other_files.append(entry)
|
||
|
|
|
||
|
|
template = _env.get_template("ncr_pdf.html")
|
||
|
|
return template.render(
|
||
|
|
ncr=ncr,
|
||
|
|
stage_label=STAGE_LABELS[Stage(ncr.stage)],
|
||
|
|
stage_labels=STAGE_LABELS,
|
||
|
|
Stage=Stage,
|
||
|
|
images=images,
|
||
|
|
other_files=other_files,
|
||
|
|
generated_at=utcnow(),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _html_to_pdf(html: str) -> bytes:
|
||
|
|
from weasyprint import HTML # lazy: needs Pango/Cairo system libs
|
||
|
|
|
||
|
|
return HTML(string=html).write_pdf()
|
||
|
|
|
||
|
|
|
||
|
|
async def render_ncr_pdf(ncr: Ncr) -> bytes:
|
||
|
|
html = _render_html(ncr)
|
||
|
|
# WeasyPrint rendering is CPU-bound; keep it off the event loop.
|
||
|
|
return await anyio.to_thread.run_sync(partial(_html_to_pdf, html))
|