Ask Anvil

Answers to questions about automating PDFs, e-signatures, Webforms, and other paperwork problems.
PDFs
Categories

How do I add a repeating list of line items to a generated PDF?

Dropping a single value into a template is easy. A list of line items (invoice rows, order lines, a payment schedule) is harder, because you do not know how many rows there will be until you have the data. Hardcoding the rows breaks the moment the list changes length. The fix is to loop over the list inside the template so the table grows to match your data.

from jinja2 import Template

# Your data: a variable-length list, e.g. from a database query
line_items = [
    {"description": "Design work", "qty": 10, "rate": 120},
    {"description": "Development", "qty": 32, "rate": 150},
    {"description": "Project management", "qty": 6, "rate": 110},
]

# Compute amounts and the total in code, not in the template
for item in line_items:
    item["amount"] = item["qty"] * item["rate"]
total = sum(item["amount"] for item in line_items)

# autoescape=True protects the layout if any field holds user text
template = Template("""
<table>
  <tr><th>Description</th><th>Qty</th><th>Rate</th><th>Amount</th></tr>
  {% for item in line_items %}
  <tr>
    <td>{{ item.description }}</td>
    <td>{{ item.qty }}</td>
    <td>{{ "%.2f"|format(item.rate) }}</td>
    <td>{{ "%.2f"|format(item.amount) }}</td>
  </tr>
  {% endfor %}
  <tr><td colspan="3">Total</td><td>{{ "%.2f"|format(total) }}</td></tr>
</table>
""", autoescape=True)

html = template.render(line_items=line_items, total=total)
print(html)

How the loop works

The for block repeats its row markup once per item, so a three item list renders three rows and a thirty item list renders thirty. Render the template to HTML, then convert that HTML to a PDF with a renderer such as WeasyPrint (or any HTML to PDF engine you already use). The same loop idea exists in other template engines: Handlebars, for example, uses an each block.

Two things to watch

First, compute money in your application code, not in the template. Do the quantity times rate math and the running total in code so rounding stays consistent and the logic is not copied into every template. Second, turn on autoescaping (autoescape=True above) whenever a field can hold user entered text, so a stray angle bracket or ampersand cannot break the table layout or inject markup.

Back to All Questions

The fastest way to build software for documents

Anvil Document SDK is a comprehensive toolbox for product teams launching document flows where PDF filling, signing, and complex conditional scenarios are necessary.
Explore Anvil
Anvil Webforms