Jinja2 Loops
You have a list of tasks in Python. You need to display each one on the page. Jinja2's {% for %} loop handles this.
The syntax
{% for task in tasks %}
<li>{{ task.title }} — {{ task.criterion }}</li>
{% endfor %}
Three things to notice:
{% %}is for logic (loops, conditionals).{{ }}is for output. Different delimiters, different purposes.{% endfor %}is required. Unlike Python, indentation doesn't define blocks in Jinja2. You must explicitly close every loop and every conditional.task.titleuses dot notation to access dictionary keys. In Python you'd writetask["title"]. In Jinja2, both work, but dot notation is the convention.
A complete example
Your route passes the data:
@app.route("/tasks")
def show_tasks():
return render_template("tasks.html", tasks=tasks)
Your template loops over it and builds a table:
<h1>All Tasks</h1>
<table>
<tr>
<th>Title</th>
<th>Criterion</th>
<th>Date</th>
</tr>
{% for task in tasks %}
<tr>
<td>{{ task.title }}</td>
<td>{{ task.criterion }}</td>
<td>{{ task.date }}</td>
</tr>
{% endfor %}
</table>
<p>Total tasks: {{ tasks|length }}</p>
{{ tasks|length }} uses a Jinja2 filter. The |length filter is like Python's len(). Filters are applied with the pipe | character.
What this replaces
Compare this to the Week 1 inline approach:
@app.route("/tasks")
def show_tasks():
html = "<table>"
html += "<tr><th>Title</th><th>Criterion</th></tr>"
for task in tasks:
html += "<tr>"
html += "<td>" + task["title"] + "</td>"
html += "<td>" + task["criterion"] + "</td>"
html += "</tr>"
html += "</table>"
return html
Python and HTML mashed together. Unreadable.
@app.route("/tasks")
def show_tasks():
return render_template("tasks.html", tasks=tasks)
One line of Python. All the HTML is in tasks.html where it belongs.
Handling an empty list
What if there's no data yet? In the flask_v2 Record of Tasks app, the template handles this:
{% if tasks|length < 1 %}
<p>There are no tasks... create one below</p>
{% else %}
<table>
{% for task in tasks %}
<tr>
<td>{{ task.title }}</td>
<td>{{ task.date_started.strftime("%Y-%m-%d") }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
This pattern — check if the list is empty, show a message if so, otherwise loop — appears in almost every CRUD app.
loop.index
Inside a {% for %} block, Jinja2 gives you a special loop variable:
{% for step in steps %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ step.description }}</td>
</tr>
{% endfor %}
loop.index counts from 1. loop.index0 counts from 0. This is useful for numbering rows in a table without needing to track a counter variable yourself.