admin管理员组

文章数量:1122826

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?

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?

Share Improve this question edited Nov 21, 2024 at 13:46 mawimawi asked Nov 21, 2024 at 13:33 mawimawimawimawi 4,3334 gold badges34 silver badges52 bronze badges 5
  • Please provide Minimal, Reproducible Example – matszwecja Commented Nov 21, 2024 at 13:39
  • 1 @matszwecja I have no clue how to minimize this even further. Do you have any ideas about it? – mawimawi Commented Nov 21, 2024 at 13:43
  • I'm more concerned about the reproducible part, not minimal. environment is not defined. – matszwecja Commented Nov 21, 2024 at 13:46
  • 1 got it. I included the necessary imports etc. if you create the .txt files in your current directory, then you can execute everything in a python console. thank you! – mawimawi Commented Nov 21, 2024 at 14:05
  • why don't you put the content of your include in the for loop? i don't think that's how includes are supposed to work. – folen gateis Commented Nov 21, 2024 at 14:25
Add a comment  | 

1 Answer 1

Reset to default 2

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.

本文标签: pythonAlternative to quotwithquot template command for loopsStack Overflow