I have a template file letter.txt
in the format
Hello {{ first_name }} {{ last_name }}!
(In reality there are many more variables than these two, but for simplicity I'll stick with only first_name
and last_name
.)
This works nicely with a call like
from jinja2 import Environment, FileSystemLoader
environment = Environment(loader=FileSystemLoader("./"))
template = environment.get_template("letter.txt")
template.render(first_name='Jane', last_name='Doe')
Now if I have a list of names that should appear, like in a form letter, I need to do the following VERY verbose with
statement in the form_letter.txt
template file which includes the original untouched letter.txt
template.
{% for person in people %}
{% with first_name=person.first_name, last_name=person.last_name %}
{% include 'letter.txt' %}
{% endwith %}
------8<---cut here------------
{% endfor %}
and rendering the template via:
template = environment.get_template("form_letter.txt")
template.render(people=[{'first_name': 'Jane', 'last_name': 'Doe'},
{'first_name': 'Bob', 'last_name': 'Builder'},
{'first_name': 'Sara', 'last_name': 'Sample'}])
Creating and possibly having to expand the with
statement by hand is cumbersome and error prone. Is there a better way to "create a namespace inside a template" automatically from key/value of a dict like object? Like {% with **person %}
or something similar?
Here's a slightly less verbose version, using set
keyword:
{% for person in people %}
{% set first_name, last_name = person.values() %}
{% include 'letter.txt' %}
------8<---cut here------------
{% endfor %}
You could instead refer to values directly: {% set first_name, last_name = person['first_name'], person['last_name'] %}
but that does not make it much more concise than your original code.
Another approach could be to change the data structure and infer the semantics based on the structure itself
template = environment.get_template("form_letter.txt")
print(template.render(people=[('Jane', 'Doe'),
('Bob', 'Builder'),
('Sara', 'Sample')]))
{% for person in people %}
{% set first_name, last_name = person %}
{% include 'letter.txt' %}
------8<---cut here------------
{% endfor %}
The structure is trivial enough that it is obvious what each field represents and tuples guarantee order regardless of version. For more complex objects you might consider named tuples, dataclasses etc:
from jinja2 import Environment, FileSystemLoader
from collections import namedtuple
environment = Environment(loader=FileSystemLoader("./"))
Person = namedtuple('Person', ['first_name', 'last_name'])
template = environment.get_template("form_letter.txt")
print(template.render(people=[Person(**el) for el in [{'first_name': 'Jane', 'last_name': 'Doe'},
{'first_name': 'Bob', 'last_name': 'Builder'},
{'first_name': 'Sara', 'last_name': 'Sample'}]]))
Ultimately there is no single solution that is good for all purposes.