Templates
Flask uses Jinja2 as its templating engine. Templates allow you to separate presentation logic from business logic, making your code more maintainable.
Basic Template Rendering
Render Template
from flask import render_template
@app.route('/user/<name>')
def user(name):
return render_template('user.html', username=name)
Template Directory Structure
project/
├── app.py
└── templates/
├── base.html
├── index.html
└── user.html
Jinja2 Syntax
Variables
<!-- Basic variable -->
<h1>Hello, {{ username }}!</h1>
<!-- With default -->
<h1>Hello, {{ username or 'Guest' }}!</h1>
<!-- Object properties -->
<p>{{ user.email }}</p>
<p>{{ user['email'] }}</p>
Control Structures
If Statements
{% if user %}
<h1>Hello, {{ user.name }}!</h1>
{% else %}
<h1>Hello, Guest!</h1>
{% endif %}
Loops
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% else %}
<li>No items found</li>
{% endfor %}
</ul>
For Loop Variables
{% for user in users %}
<p>{{ loop.index }}: {{ user.name }}</p>
{% if loop.first %}First user{% endif %} {% if loop.last %}Last user{% endif %}
{% endfor %}
Template Inheritance
Base Template (base.html)
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %} - My App</title>
{% block head %}{% endblock %}
</head>
<body>
<nav>
{% block nav %}
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
{% endblock %}
</nav>
<main>{% block content %}{% endblock %}</main>
<footer>
{% block footer %}
<p>© 2024 My App</p>
{% endblock %}
</footer>
</body>
</html>
Child Template
{% extends "base.html" %} {% block title %}Home{% endblock %} {% block content
%}
<h1>Welcome!</h1>
<p>This is the home page.</p>
{% endblock %}
Multiple Inheritance
<!-- intermediate.html -->
{% extends "base.html" %} {% block nav %} {{ super() }}
<li><a href="/admin">Admin</a></li>
{% endblock %}
<!-- page.html -->
{% extends "intermediate.html" %}
Template Includes
Including Templates
<!-- main.html -->
{% include 'sidebar.html' %} {% include 'navigation.html' %}
<!-- With variables -->
{% include 'item.html' with context %} {% include 'button.html' without context
%}
Macros
<!-- macros.html -->
{% macro render_field(field) %}
<dt>{{ field.label }}</dt>
<dd>
{{ field(**kwargs)|safe }} {% if field.errors %}
<ul class="errors">
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %} {% endmacro %}
<!-- Using macros -->
{% from 'macros.html' import render_field %} {{ render_field(form.username) }}
</dd>
Built-in Filters
String Filters
{{ name|upper }}
<!-- JOHN -->
{{ name|lower }}
<!-- john -->
{{ name|title }}
<!-- John -->
{{ name|capitalize }}
<!-- John -->
{{ text|truncate(20) }}
<!-- First 20 chars... -->
{{ text|wordwrap(40) }}
<!-- Word wrap at 40 -->
{{ html|striptags }}
<!-- Remove HTML tags -->
List/Dict Filters
{{ items|length }}
<!-- List length -->
{{ items|first }}
<!-- First item -->
{{ items|last }}
<!-- Last item -->
{{ items|join(', ') }}
<!-- Join with comma -->
{{ items|sort }}
<!-- Sort list -->
{{ dict|dictsort }}
<!-- Sort dict by key -->
Number Filters
{{ price|round(2) }}
<!-- Round to 2 decimals -->
{{ number|abs }}
<!-- Absolute value -->
{{ items|sum }}
<!-- Sum of numbers -->
Date Filters
{{ date|strftime('%Y-%m-%d') }}
<!-- Format date -->
Custom Filters
Creating Custom Filters
@app.template_filter('reverse')
def reverse_filter(s):
return s[::-1]
# Or using decorator
def reverse_filter(s):
return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter
Using Custom Filters
{{ 'hello'|reverse }}
<!-- Output: olleh -->
Template Context
Global Template Variables
@app.context_processor
def inject_user():
return dict(current_user=get_current_user())
# Now available in all templates
{{ current_user.name }}
Template Context Functions
@app.template_global()
def format_price(price):
return f"${price:.2f}"
# Use in templates
{{ format_price(item.price) }}
URL Generation
url_for Function
<a href="{{ url_for('index') }}">Home</a>
<a href="{{ url_for('user', username='john') }}">John's Profile</a>
<a href="{{ url_for('static', filename='style.css') }}">Stylesheet</a>
With Query Parameters
<a href="{{ url_for('search', q='python', page=2) }}"> Search Results </a>
<!-- Generates: /search?q=python&page=2 -->
Static Files
Including Static Files
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/style.css') }}"
/>
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
<img src="{{ url_for('static', filename='images/logo.png') }}" />
Message Flashing
Display Flash Messages
{% with messages = get_flashed_messages(with_categories=true) %} {% if messages
%}
<ul class="flashes">
{% for category, message in messages %}
<li class="flash-{{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %} {% endwith %}
Advanced Features
Template Tests
{% if variable is defined %} Variable is defined {% endif %} {% if user is none
%} No user {% endif %} {% if items is iterable %} Can loop through items {%
endif %}
Assignment
{% set user_count = users|length %}
<p>Total users: {{ user_count }}</p>
{% set nav_items = [ ('/', 'Home'), ('/about', 'About'), ('/contact', 'Contact')
] %}
Template Comments
{# This is a comment #} {# Multi-line comment #}
Security
Auto-escaping
<!-- Automatically escaped -->
{{ user_input }}
<!-- Raw output (dangerous) -->
{{ user_input|safe }}
<!-- In Python -->
from markupsafe import Markup safe_html = Markup('<b>Bold</b>')
Manual Escaping
{{ user_input|e }} {{ user_input|escape }}
Template Performance
Caching Templates
from flask import Flask
from jinja2 import FileSystemLoader, Environment
app = Flask(__name__)
# Enable template caching
app.jinja_env.cache_size = 400
app.jinja_env.auto_reload = False # In production
# Precompile templates
app.jinja_env.compile_templates('templates_compiled')
Template Optimization
# Reduce template size
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True
# Enable optimizations
app.jinja_env.optimized = True
Common Patterns
Form Rendering
<form method="POST">
{{ form.hidden_tag() }} {{ form.username.label }} {{
form.username(class="form-control") }} {% if form.username.errors %}
<div class="error">
{% for error in form.username.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %} {{ form.submit(class="btn btn-primary") }}
</form>
Pagination
{% if pagination.has_prev %}
<a href="{{ url_for(request.endpoint, page=pagination.prev_num) }}">
Previous
</a>
{% endif %} {% for page_num in pagination.iter_pages() %} {% if page_num %} {%
if page_num != pagination.page %}
<a href="{{ url_for(request.endpoint, page=page_num) }}"> {{ page_num }} </a>
{% else %}
<strong>{{ page_num }}</strong>
{% endif %} {% else %}
<span>…</span>
{% endif %} {% endfor %} {% if pagination.has_next %}
<a href="{{ url_for(request.endpoint, page=pagination.next_num) }}"> Next </a>
{% endif %}
Error Handling
<!-- 404.html -->
{% extends "base.html" %} {% block title %}Page Not Found{% endblock %} {% block
content %}
<h1>Oops! Page not found</h1>
<p>The page you're looking for doesn't exist.</p>
<a href="{{ url_for('index') }}">Go back home</a>
{% endblock %}