Flask Filters and Macros in Jinja2
Flask, a lightweight Python web framework, uses Jinja2 as its templating engine to render dynamic web content. Two powerful Jinja2 features, filters and macros, enhance template flexibility and reusability. Filters transform variable output for display, while macros define reusable template snippets, both of which are invaluable in data-driven applications like dashboards or machine learning (ML) interfaces using Pandas. This guide covers Flask filters and macros, including their syntax, use cases, best practices, and practical examples, with a focus on data-driven scenarios.
01. Overview of Filters and Macros
1.1 Filters
Jinja2 filters are functions that modify or format variables during template rendering. They are applied using the pipe operator (|
) and are ideal for tasks like formatting text, numbers, or dates, escaping HTML, or handling missing data.
- Purpose: Transform variable output for display (e.g., currency formatting, uppercase conversion).
- Syntax:
{{ variable | filter_name(arguments) }}
- Types: Built-in (e.g.,
upper
,default
) and custom filters.
1.2 Macros
Macros are reusable template snippets, similar to functions, that allow developers to define common HTML or logic patterns once and reuse them across templates. They reduce code duplication and improve maintainability.
- Purpose: Encapsulate reusable HTML or logic (e.g., form fields, navigation bars).
- Syntax: Defined with
{% macro %}
, called like a function. - Use Cases: Reusable form inputs, table rows, or UI components.
02. Jinja2 Filters
2.1 Built-in Filters
Jinja2 provides a variety of built-in filters for common formatting tasks. Here are some key examples:
Filter | Description | Example |
---|---|---|
upper |
Converts a string to uppercase. | {{ "hello" | upper }} → HELLO |
lower |
Converts a string to lowercase. | {{ "HELLO" | lower }} → hello |
title |
Capitalizes each word. | {{ "hello world" | title }} → Hello World |
default |
Provides a default if undefined. | {{ name | default("Guest") }} → Guest if undefined |
safe |
Renders HTML without escaping (use cautiously). | {{ "<b>bold</b>" | safe }} → bold |
escape |
Escapes HTML characters. | {{ "<b>bold</b>" | escape }} → <b>bold</b> |
float |
Converts to a float. | {{ "42" | float }} → 42.0 |
round |
Rounds a number. | {{ 42.567 | round(2) }} → 42.57 |
2.2 Custom Filters
Custom filters can be defined in Flask to handle specific formatting needs, such as currency or date formatting, using the @app.template_filter
decorator.
Example: Custom Currency Filter
File: app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.template_filter('currency')
def currency_filter(value):
try:
return f"${float(value):,.2f}"
except (ValueError, TypeError):
return value
@app.route('/')
def home():
return render_template('currency.html', price=1234.5678)
if __name__ == '__main__':
app.run(debug=True)
File: templates/currency.html
<!DOCTYPE html>
<html>
<head><title>Currency</title></head>
<body>
<p>Price: {{ price | currency }}</p>
</body>
</html>
Output (/):
<p>Price: $1,234.57</p>
Explanation:
@app.template_filter('currency')
- Registers the custom filter.| currency
- Formatsprice
as a currency string.- Error handling - Ensures robustness for invalid inputs.
2.3 Chaining Filters
Filters can be chained to apply multiple transformations sequentially.
Example: Chaining Filters
File: app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return render_template('chain.html', value=" hello world ")
if __name__ == '__main__':
app.run(debug=True)
File: templates/chain.html
<!DOCTYPE html>
<html>
<head><title>Chain</title></head>
<body>
<p>Result: {{ value | trim | title }}</p>
</body>
</html>
Output (/):
<p>Result: Hello World</p>
Explanation:
| trim
- Removes whitespace.| title
- Capitalizes words after trimming.
03. Jinja2 Macros
3.1 Defining and Using Macros
Macros are defined using {% macro %}
and called like functions within templates. They can accept parameters and include Jinja2 logic (e.g., loops, conditionals).
Example: Basic Macro for Form Input
File: templates/macros.html
{% macro input_field(name, label, type='text') %}
<div class="form-group">
<label>{{ label }}</label>
<input type="{{ type }}" name="{{ name }}" class="form-control" required>
</div>
{% endmacro %}
File: templates/form.html
<!DOCTYPE html>
<html>
<head>
<title>Form</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>User Form</h1>
{% import 'macros.html' as macros %}
<form method="post">
{{ macros.input_field('username', 'Username') }}
{{ macros.input_field('email', 'Email', type='email') }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</body>
</html>
File: app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/form', methods=['GET', 'POST'])
def form():
return render_template('form.html')
if __name__ == '__main__':
app.run(debug=True)
Output (/form):
A Bootstrap-styled form with username and email input fields.
Explanation:
{% macro input_field %}
- Defines a reusable input field template.{% import 'macros.html' as macros %}
- Imports the macro for use.{{ macros.input_field(...) }}
- Calls the macro with parameters.
3.2 Macros with Logic
Macros can include Jinja2 logic, such as conditionals or loops, for dynamic rendering.
Example: Macro with Conditional Logic
File: templates/macros.html
{% macro render_status(status) %}
<span class="badge {% if status == 'active' %}badge-success{% elif status == 'inactive' %}badge-warning{% else %}badge-secondary{% endif %}">
{{ status | title }}
</span>
{% endmacro %}
File: templates/status.html
<!DOCTYPE html>
<html>
<head>
<title>Status</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>User Status</h1>
{% import 'macros.html' as macros %}
<p>User 1: {{ macros.render_status('active') }}</p>
<p>User 2: {{ macros.render_status('inactive') }}</p>
<p>User 3: {{ macros.render_status('pending') }}</p>
</div>
</body>
</html>
File: app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/status')
def status():
return render_template('status.html')
if __name__ == '__main__':
app.run(debug=True)
Output (/status):
A page with Bootstrap-styled badges: green for "Active", yellow for "Inactive", and gray for "Pending".
Explanation:
{% if %}
- Applies conditional Bootstrap classes based onstatus
.| title
- Formats the status text.
04. Combining Filters and Macros
Filters and macros can be combined to create powerful, reusable templates for data-driven applications, such as rendering Pandas DataFrames or ML outputs.
Example: Dashboard with Filters and Macros
File: app.py
from flask import Flask, render_template
import pandas as pd
app = Flask(__name__)
@app.template_filter('currency')
def currency_filter(value):
try:
return f"${float(value):,.2f}"
except (ValueError, TypeError):
return value
@app.route('/dashboard')
def dashboard():
df = pd.DataFrame({
'Name': ['Alice', 'Bob', 'Charlie'],
'Salary': [50000.123, 60000.456, 55000.789],
'Status': ['active', 'inactive', 'active']
})
return render_template('dashboard.html', data=df.to_dict(orient='records'))
if __name__ == '__main__':
app.run(debug=True)
File: templates/macros.html
{% macro render_row(row) %}
<tr>
<td>{{ row.Name | title }}</td>
<td>{{ row.Salary | currency }}</td>
<td>
<span class="badge {% if row.Status == 'active' %}badge-success{% elif row.Status == 'inactive' %}badge-warning{% else %}badge-secondary{% endif %}">
{{ row.Status | title }}
</span>
</td>
</tr>
{% endmacro %}
File: templates/dashboard.html
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>Data Dashboard</h1>
{% import 'macros.html' as macros %}
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Salary</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for row in data %}
{{ macros.render_row(row) }}
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
Output (/dashboard):
A Bootstrap-styled table with formatted data (e.g., Alice
, $50,000.12
, green "Active" badge).
Explanation:
currency_filter
- Formats salaries as currency.render_row
- Macro encapsulates table row rendering with filters (| title
,| currency
) and conditional badge styling.to_dict(orient='records')
- Converts DataFrame for iteration.
4.1 ML Form with Filters and Macros
Filters and macros can streamline ML input forms and result displays.
Example: ML Prediction Form
File: app.py
from flask import Flask, render_template, request
app = Flask(__name__)
@app.template_filter('probability')
def probability_filter(value):
try:
return f"{float(value) * 100:.1f}%"
except (ValueError, TypeError):
return value
@app.route('/predict', methods=['GET', 'POST'])
def predict():
if request.method == 'POST':
prediction = float(request.form.get('prediction', 0.75))
return render_template('result.html', prediction=prediction)
return render_template('form.html')
if __name__ == '__main__':
app.run(debug=True)
File: templates/macros.html
{% macro input_field(name, label, type='number', step='0.01') %}
<div class="form-group">
<label>{{ label }}</label>
<input type="{{ type }}" name="{{ name }}" step="{{ step }}" class="form-control" required>
</div>
{% endmacro %}
File: templates/form.html
<!DOCTYPE html>
<html>
<head>
<title>Predict</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>Enter Prediction</h1>
{% import 'macros.html' as macros %}
<form method="post">
{{ macros.input_field('prediction', 'Prediction (0-1)') }}
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</body>
</html>
File: templates/result.html
<!DOCTYPE html>
<html>
<head>
<title>Result</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>Prediction Result</h1>
<p>Probability: {{ prediction | probability }}</p>
<a href="{{ url_for('predict') }}" class="btn btn-secondary">Try Again</a>
</div>
</body>
</html>
Output (/predict, POST with prediction=0.75):
<h1>Prediction Result</h1>
<p>Probability: 75.0%</p>
Explanation:
input_field
- Macro creates a reusable form input with Bootstrap styling.| probability
- Custom filter formats the ML prediction as a percentage.
05. Best Practices for Filters and Macros
5.1 Recommended Practices
- Use Built-in Filters First: Prefer built-in filters for common tasks before creating custom ones.
- Secure Output: Use
| safe
only for trusted HTML (e.g., Pandasto_html
); use| escape
for user input. - Keep Macros Simple: Design macros for specific, reusable components; avoid complex logic.
- Handle Errors in Filters: Include try-except blocks in custom filters for robustness.
- Organize Macros: Store macros in a dedicated
macros.html
file or folder (e.g.,templates/macros/
). - Use Descriptive Names: Name filters and macros clearly (e.g.,
currency
,input_field
). - Integrate with url_for: Use
url_for
in macros for navigation links.
5.2 Practices to Avoid
- Avoid Untrusted HTML: Don’t use
| safe
with user-generated content to prevent XSS. - Avoid Complex Logic: Keep filters and macros focused on presentation; move business logic to Python.
- Avoid Overusing Filters: Apply filters only when necessary to maintain performance.
- Avoid Inline Macros: Define macros in separate files for reusability.
Example: Insecure Filter Usage
File: app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/comment/<comment>')
def comment(comment):
return render_template('comment.html', comment=comment)
if __name__ == '__main__':
app.run(debug=True)
File: templates/comment.html
<!DOCTYPE html>
<html>
<head><title>Comment</title></head>
<body>
<p>{{ comment | safe }}</p> <!-- Insecure -->
</body>
</html>
Input: /comment/<script>alert('XSS')</script>
Output: Executes the script (XSS vulnerability).
Correct:
<!DOCTYPE html>
<html>
<head><title>Comment</title></head>
<body>
<p>{{ comment | escape }}</p> <!-- Safe -->
</body>
</html>
Output: Displays <script>alert('XSS')</script>
safely.
Explanation:
- Insecure -
| safe
with user input enables XSS. - Correct -
| escape
ensures safe rendering.
06. Conclusion
Jinja2 filters and macros in Flask provide powerful tools for creating dynamic, reusable, and maintainable templates, especially for data-driven applications with Pandas or ML workflows. Key takeaways:
- Filters transform variable output (e.g., formatting currency, percentages) using built-in or custom functions.
- Macros encapsulate reusable HTML or logic, reducing duplication (e.g., form fields, table rows).
- Combining filters and macros enhances rendering of Pandas DataFrames or ML results.
- Follow best practices like securing output, keeping logic simple, and organizing code.
By mastering filters and macros, you can build clean, secure, and scalable web interfaces for your Flask applications, improving usability in data-driven contexts!
Comments
Post a Comment