Skip to main content

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>&copy; 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 %}