10. PDF documents
A borrower leaves with a receipt. The module supplies the template; the kernel does the rest.
One file is enough
Drop reports/library_loan.html. The name carries everything: <model>.html is the model’s
default report, <model>.<name>.html is another, named one.
{% extends "kewos:document" %}{% block body %}<h1>{{ t("library_loan.receipt.title") }}</h1>
<table class="kws-table"> <tbody> <tr><th>{{ t("library_loan.member_id") }}</th><td>{{ record.member_id }}</td></tr> <tr><th>{{ t("library_loan.copy_id") }}</th><td>{{ record.copy_id }}</td></tr> <tr><th>{{ t("library_loan.taken_on") }}</th><td>{{ record.taken_on }}</td></tr> <tr><th>{{ t("library_loan.due_on") }}</th><td><strong>{{ record.due_on }}</strong></td></tr> <tr><th>{{ t("library_loan.state") }}</th> <td>{{ t("library_loan.state." ~ record.state) }}</td></tr> </tbody></table>
<p class="kws-muted">{{ t("library_loan.receipt.note") }}</p>{% endblock %}And one line in the module’s entry point:
from kewos.pdf import register_reports_from_dir
register_reports_from_dir(_MODULE_DIR / "reports")What it produces

The letterhead, the logo, the footer and the colours come from the company’s document
theme. You wrote neither: {% extends "kewos:document" %} brings them, and a customer who
changes their logo changes every document at once.
The print button appears on the model’s screen, with nothing more to declare.
The context: what the template receives
A report that needs no Python receives a context built generically:
| In the template | What you get |
|---|---|
record.<field> | The value, stringified. |
record.<relation> | The label of the record it points at. |
record.<children> | The children, as dictionaries. |
t("key") | The translation, in the document’s language. |
When the generic shape is not enough
Register the report yourself, with a context builder:
from kewos.pdf import register_model_report
def _receipt_context(session, classes, record_id): """Everything the receipt needs, including what is two hops away.""" loan = session.get(classes["library_loan"], record_id) return { "record": loan, "title": loan.copy_id.title_id.name, "member_card": loan.member_id.card_number, }
register_model_report( "library_loan", code="library_loan", title="Loan receipt", body_template=TEMPLATE, build_context=_receipt_context,)An operational trap, now fixed
Next
Reacting: acting on its own, every morning.