Templates in Flask (Part 2): Control Structures and Filters
We've learned how to render templates and pass variables from our Flask application to our HTML. Now, let's unlock the true power of the Jinja2 templating engine by exploring two of its most important features: control structures and filters.
These features allow you to embed logic directly into your templates, making them truly dynamic and capable of handling complex data presentations without cluttering your Python code.
📚 Prerequisites
You should be comfortable with passing variables from a Flask view function to a template using render_template().
🎯 Article Outline: What You'll Master
In this article, you will learn:
- ✅ Jinja2 Delimiters: A review of the
{{ ... }}and{% ... %}syntax. - ✅ Conditional Logic: How to use
{% if ... %},{% elif ... %}, and{% else %}to conditionally show or hide content. - ✅ Looping: How to use a
{% for ... %}loop to iterate over a list or other iterable from your Python code. - ✅ Filters: How to use filters with the pipe (
|) operator to easily modify variables directly in your template.
🧠 Section 1: A Quick Reminder on Delimiters
Jinja2 uses specific delimiters to distinguish its code from standard HTML:
{{ ... }}: For expressions, usually a variable to be printed to the screen.{% ... %}: For statements, likeifconditions andforloops.{# ... #}: For comments that will not be included in the final HTML output.
💻 Section 2: Control Structures
Conditional Logic with if Statements
You can use if, elif, and else in your templates, just like in Python, to control what gets rendered.
Let's create a view that shows a different message depending on whether a user is logged in.
app.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/dashboard")
def dashboard():
# In a real app, this would come from a database or session
is_logged_in = True
user_name = "Alice"
return render_template("dashboard.html", logged_in=is_logged_in, name=user_name)
templates/dashboard.html:
{% extends "base.html" %} {# Assuming you have a base.html from the last lesson #}
{% block title %}Dashboard{% endblock %}
{% block content %}
{% if logged_in %}
<h1>Welcome back, {{ name }}!</h1>
<p>Here is your personalized dashboard content.</p>
{% else %}
<h1>Welcome, Guest!</h1>
<p>Please <a href="#">log in</a> to see your dashboard.</p>
{% endif %}
{% endblock %}
This is a powerful way to customize the user experience based on application state.
Iteration with for Loops
A for loop is essential for displaying a list of items, like a list of products, blog posts, or users.
app.py:
@app.route("/products")
def product_list():
products = [
{"name": "Laptop", "price": 1200},
{"name": "Mouse", "price": 25},
{"name": "Keyboard", "price": 75}
]
return render_template("products.html", products=products)
templates/products.html:
{% extends "base.html" %}
{% block title %}Our Products{% endblock %}
{% block content %}
<h1>Product Catalog</h1>
<table border="1">
<thead>
<tr>
<th>Product Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{% for product in products %}
<tr>
<td>{{ product.name }}</td>
<td>${{ product.price }}</td>
</tr>
{% else %}
<tr>
<td colspan="2">No products found.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
The for loop iterates over the products list passed from Flask. Jinja2 even provides an optional {% else %} block that will be rendered if the list is empty.
🛠️ Section 3: Modifying Variables with Filters
Filters are a simple way to apply transformations to your variables right inside the template. You use the pipe (|) symbol to apply a filter.
Jinja2 comes with many useful built-in filters.
Common Filters:
capitalize: Capitalizes the first character of a string.upper: Converts a string to uppercase.lower: Converts a string to lowercase.title: Capitalizes each word in a string.length: Returns the number of items in a sequence.default(value): Provides a default value if the variable is undefined or false.safe: Marks a string as "safe," meaning Jinja2 will not escape its HTML content. Use with extreme caution and only on trusted data to avoid security vulnerabilities.
Example: Let's create a user profile page that uses filters.
app.py:
@app.route("/profile/<name>")
def profile(name):
user_details = {
"username": name,
"bio": "a writer and python enthusiast.",
"followers": ["Alice", "Bob", "Charlie"]
}
return render_template("profile.html", user=user_details)
templates/profile.html:
{% extends "base.html" %}
{% block title %}{{ user.username | title }}'s Profile{% endblock %}
{% block content %}
<!-- Use the 'title' filter to capitalize the name -->
<h1>{{ user.username | title }}</h1>
<!-- Use the 'capitalize' filter for the bio -->
<p>Bio: {{ user.bio | capitalize }}</p>
<!-- Use the 'length' filter to get the number of followers -->
<p>Follower count: {{ user.followers | length }}</p>
<!-- You can provide a default value -->
<p>Location: {{ user.location | default("Unknown") }}</p>
{% endblock %}
This is much cleaner than performing all these transformations in your Python code before passing the variables to the template.
✨ Conclusion & Key Takeaways
Control structures and filters are what make Jinja2 a true templating engine. They allow you to build complex, data-driven views while keeping your Python code clean and focused on logic, and your HTML templates focused on presentation.
Let's summarize the key takeaways:
- Use
{% if ... %}for conditional logic in your templates. - Use
{% for ... %}to iterate over lists and other iterables. - Use filters with the pipe (
|) operator to easily format and modify variables for display. - This separation of concerns—logic in Python, presentation in templates—is a cornerstone of good web application design.
➡️ Next Steps
We've now covered the fundamentals of rendering dynamic pages. Next, we'll explore how to handle data submitted from the user to our application, starting with "Handling HTTP Methods: GET and POST."
Happy templating!