Шпаргалка по Jinja2: шаблонизатор Python
Jinja2 — быстрый и выразительный шаблонизатор для Python. Используется в FastApi, Flask, Django, Ansible, SaltStack, генераторах статических сайтов. Отделяет HTML-вёрстку от логики приложения: шаблон получает данные и рендерит готовую страницу.
Быстрый старт
pip install jinja2
from jinja2 import Environment, FileSystemLoader, Template
# Способ 1: из файла (для веб-приложений)
env = Environment(loader=FileSystemLoader("templates"))
template = env.get_template("hello.html")
html = template.render(name="Мир", items=["яблоко", "банан"])
# Способ 2: из строки (для простых случаев)
template = Template("Привет, {{ name }}!")
html = template.render(name="Мир") # "Привет, Мир!"
Структура проекта:
project/
templates/
base.html
index.html
main.py
Базовый синтаксис
Три типа разделителей: {{ }} — выражение (выводит значение), {% %} — инструкция (управление потоком), {# #} — комментарий (не попадает в вывод).
{# Это комментарий — не появится в HTML #}
<!-- Переменная -->
<p>Привет, {{ user.name }}!</p>
<!-- Выражение -->
<p>Через 5 лет вам будет {{ user.age + 5 }}</p>
<!-- Обращение к элементам -->
{{ user["name"] }} <!-- как к словарю -->
{{ user.name }} <!-- как к атрибуту (одинаково) -->
{{ items[0] }} <!-- элемент списка -->
Переменные и фильтры
Фильтры модифицируют значение перед выводом. Применяются через |. Можно соединять цепочкой: value|filter1|filter2.
Встроенные фильтры
<!-- Строки -->
{{ name|upper }} <!-- "АННА" -->
{{ name|lower }} <!-- "анна" -->
{{ name|capitalize }} <!-- "Анна" -->
{{ name|title }} <!-- "Анна Мария" -->
{{ text|trim }} <!-- убрать пробелы по краям -->
{{ text|truncate(100) }} <!-- обрезать до 100 символов + "..." -->
{{ "Hello"|replace("Hello", "Привет") }} <!-- замена -->
{{ "hello"|wordcount }} <!-- 1 — количество слов -->
<!-- Числа -->
{{ price|round(2) }} <!-- 3.14 -->
{{ 42|abs }} <!-- абсолютное значение -->
{{ items|length }} <!-- длина списка/строки -->
{{ users|count }} <!-- то же самое -->
{{ 1000000|intcomma }} <!-- "1,000,000" (требует jinja2-humanize-extension) -->
<!-- Даты (datetime объект) -->
{{ user.created_at|datetimeformat('%d.%m.%Y') }}
<!-- Значения по умолчанию -->
{{ bio|default("Не указано") }} <!-- если bio — None/пусто/не определено -->
{{ bio|default("Не указано", true) }} <!-- считать undefined и пустую строку -->
<!-- HTML -->
{{ html|safe }} <!-- НЕ экранировать HTML (осторожно!) -->
{{ "<b>text</b>"|e }} <!-- экранировать → <b>text</b> -->
<!-- Работа со списками -->
{{ items|first }} <!-- первый элемент -->
{{ items|last }} <!-- последний -->
{{ items|random }} <!-- случайный -->
{{ items|join(", ") }} <!-- "яблоко, банан" -->
{{ [1, 2, 2, 3]|unique|list }} <!-- [1, 2, 3] (list нужен для вывода) -->
<!-- Формат (как в Python) -->
{{ "Имя: %s, Возраст: %d"|format(name, age) }}
<!-- JSON -->
{{ data|tojson }} <!-- сериализация в JSON -->
{{ data|tojson(indent=2) }} <!-- с отступами -->
Цепочки фильтров
{{ text|trim|lower|truncate(50) }}
{{ user.name|capitalize|default("Гость") }}
Управляющие конструкции
if/elif/else — условия
{% if user.is_admin %}
<a href="/admin">Панель управления</a>
{% elif user.is_authenticated %}
<a href="/profile">{{ user.name }}</a>
{% else %}
<a href="/login">Войти</a>
{% endif %}
<!-- Тесты (is) -->
{% if user is defined %} <!-- переменная определена? -->
{% if user is none %} <!-- None -->
{% if name is string %} <!-- тип str -->
{% if items is iterable %} <!-- итерируемый объект -->
{% if score is number %} <!-- int или float -->
{% if value is even %} <!-- чётное -->
{% if value is odd %} <!-- нечётное -->
{% if text is upper %} <!-- верхний регистр -->
{% if text is lower %} <!-- нижний регистр -->
for — циклы
<!-- Простой цикл -->
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
<!-- else — если список пуст -->
{% for post in posts %}
<article>{{ post.title }}</article>
{% else %}
<p>Постов пока нет</p>
{% endfor %}
<!-- loop — специальная переменная внутри цикла -->
{% for user in users %}
<p>
{{ loop.index }}. <!-- номер с 1 -->
{{ loop.index0 }} <!-- номер с 0 -->
{{ loop.revindex }} <!-- с конца, с 1 -->
{{ loop.first }} <!-- True для первого -->
{{ loop.last }} <!-- True для последнего -->
{{ loop.length }} <!-- общая длина -->
{% if not loop.last %} | {% endif %}
</p>
{% endfor %}
<!-- Цикл по словарю -->
{% for key, value in user.items() %}
<dt>{{ key }}</dt><dd>{{ value }}</dd>
{% endfor %}
Наследование (extends/block)
Главный механизм переиспользования вёрстки. Базовый шаблон задаёт скелет страницы, дочерние — заполняют блоки.
base.html — базовый шаблон
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>{% block title %}Мой сайт{% endblock %}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<header>
{% block header %}
<nav>{% include "nav.html" %}</nav>
{% endblock %}
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% block footer %}
<p>© {{ current_year }}</p>
{% endblock %}
</footer>
{% block scripts %}{% endblock %}
</body>
</html>
index.html — дочерний шаблон
{% extends "base.html" %}
{% block title %}Главная — Мой сайт{% endblock %}
{% block content %}
<h1>Последние посты</h1>
{% for post in posts %}
<article>
<h2><a href="/post/{{ post.id }}">{{ post.title }}</a></h2>
<p>{{ post.body|truncate(150) }}</p>
</article>
{% endfor %}
{% endblock %}
super() — содержимое родительского блока
{% block header %}
{{ super() }} <!-- содержимое header из base.html -->
<div class="banner">Специальное предложение!</div>
{% endblock %}
Include — вставка фрагментов
include вставляет содержимое другого шаблона в текущий. В отличие от extends, не меняет структуру — просто «вклеивает».
<!-- nav.html -->
<nav>
<a href="/">Главная</a>
<a href="/about">О нас</a>
{% if user %}
<a href="/profile">{{ user.name }}</a>
<a href="/logout">Выйти</a>
{% else %}
<a href="/login">Войти</a>
{% endif %}
</nav>
<!-- base.html -->
<header>
{% include "nav.html" %}
{# или с ignore missing: #}
{% include "sidebar.html" ignore missing %}
</header>
Макросы — переиспользуемые компоненты
Макрос — как функция в Python, но для HTML. Принимает аргументы, возвращает HTML-строку.
Определение макроса
{% macro input(name, label="", value="", type="text", required=false) %}
<div class="form-group">
{% if label %}
<label for="{{ name }}">{{ label }}</label>
{% endif %}
<input
type="{{ type }}"
id="{{ name }}"
name="{{ name }}"
value="{{ value|e }}"
{% if required %}required{% endif %}
>
</div>
{% endmacro %}
<!-- Использование -->
{{ input("username", label="Имя пользователя", required=true) }}
{{ input("email", type="email", label="Email") }}
{{ input("bio", type="textarea") }}
Импорт макросов из другого файла
<!-- forms.html — файл с макросами -->
{% macro input(name, label="", type="text") %}...{% endmacro %}
{% macro button(text, type="submit") %}...{% endmacro %}
<!-- index.html — используем -->
{% from "forms.html" import input, button %}
{% import "forms.html" as forms %}
{{ input("username", label="Логин") }}
{{ input("password", type="password", label="Пароль") }}
{{ forms.button("Войти") }}
Caller — передача содержимого в макрос
{% macro card(title) %}
<div class="card">
<h3>{{ title }}</h3>
<div class="card-body">{{ caller() }}</div>
</div>
{% endmacro %}
{% call card("Заголовок карточки") %}
<p>Это содержимое карточки</p>
<a href="/more">Подробнее →</a>
{% endcall %}
Whitespace control — управление пробелами
По умолчанию Jinja2 сохраняет пробелы и переводы строк вокруг тегов. - убирает их.
<!-- Без контроля: много пустых строк в HTML -->
{% for item in items %}
{{ item }}
{% endfor %}
<!-- С контролем: компактный вывод -->
{% for item in items -%}
{{ item }}
{%- endfor %}
<!-- Применить к одной стороне -->
{{- item }} <!-- убрать пробелы слева -->
{{ item -}} <!-- убрать пробелы справа -->
Raw, filter и другие специальные блоки
{# raw — вывести как есть, без обработки #}
{% raw %}
Здесь можно писать {{ переменные }} и {% инструкции %}
без их обработки Jinja2. Полезно для документации.
{% endraw %}
{# filter — применить фильтр ко всему содержимому блока #}
{% filter upper %}
Весь этот текст
станет заглавными буквами.
{% endfilter %}
{# set — присвоить значение переменной в шаблоне #}
{% set page_title = "Главная страница" %}
{% set items = items|sort|unique %}
{% set ns = namespace(count=0) %} {# для изменения в циклах #}
{% for item in items %}
{% set ns.count = ns.count + 1 %}
{% endfor %}
Кастомные фильтры и глобальные переменные
Расширение Jinja2 из Python-кода — собственные фильтры и функции, доступные во всех шаблонах.
Кастомный фильтр
from jinja2 import Environment, FileSystemLoader
from datetime import datetime
env = Environment(loader=FileSystemLoader("templates"))
def datetime_format(value, fmt="%d.%m.%Y"):
if isinstance(value, str):
value = datetime.fromisoformat(value)
return value.strftime(fmt)
def ru_plural(value, forms):
"""Склонение: {{ 5|ru_plural("пользователь,пользователя,пользователей") }}"""
n = abs(int(value)) % 100
if n >= 11 and n <= 19:
return f"{value} {forms.split(',')[2]}"
n %= 10
if n == 1:
return f"{value} {forms.split(',')[0]}"
if 2 <= n <= 4:
return f"{value} {forms.split(',')[1]}"
return f"{value} {forms.split(',')[2]}"
env.filters["datetimeformat"] = datetime_format
env.filters["ru_plural"] = ru_plural
{{ post.created_at|datetimeformat('%d.%m.%Y в %H:%M') }}
{{ comments_count|ru_plural("комментарий,комментария,комментариев") }}
Глобальные переменные и функции
from datetime import datetime
env.globals["current_year"] = datetime.now().year
env.globals["site_name"] = "Мой блог"
env.globals["is_production"] = False
<footer>© {{ current_year }} {{ site_name }}</footer>
{% if not is_production %}
<div class="debug">Режим разработки</div>
{% endif %}
Контекстный процессор (как в Flask)
# Для FastAPI/Flask — добавить переменные во все шаблоны
@app.context_processor
def inject_globals():
return {
"current_year": datetime.now().year,
"site_name": "Мой блог",
"current_user": get_current_user(),
}
Безопасность: автоэкранирование
Jinja2 по умолчанию экранирует HTML (< → <) для защиты от XSS-атак. Если переменная содержит доверенный HTML — используйте |safe.
{# Опасно — если user_input = "<script>alert('xss')</script>" #}
{{ user_input|safe }} <!-- исполнится как JS! -->
{# Безопасно — экранирование по умолчанию #}
{{ user_input }} <!-- выведет <script> как текст -->
{# safe только для ЗАВЕДОМО доверенного контента #}
{{ article.html_content|safe }} <!-- контент от админа — ок -->
# Отключить автоэкранирование (не рекомендуется)
env = Environment(loader=FileSystemLoader("templates"), autoescape=False)
Частые ошибки
| Ошибка | Причина | Решение |
|---|---|---|
UndefinedError: 'x' is undefined |
Переменная не передана в контекст | Передать в render() или использовать default() |
TemplateNotFound: base.html |
Неправильный путь к шаблонам | Проверить FileSystemLoader("templates") |
| HTML рендерится как текст | Забыт ` | safe` для доверенного HTML |
| XSS-уязвимость | ` | safe` на пользовательском вводе |
| Многострочный вывод с лишними пробелами | Jinja2 сохраняет whitespace | {%- ... -%} для удаления пробелов |
loop вне цикла |
loop доступен только внутри {% for %} |
Использовать только внутри for |
Ресурсы
- Документация Jinja — официальный сайт
- Jinja на GitHub — исходный код
- Template Designer Documentation — полный справочник по синтаксису
Что дальше
- Шпаргалка по Python: от основ до профи
- Шпаргалка по FastAPI: REST API, Pydantic, WebSocket
- Шпаргалка по Django: модели, views, ORM, админка
- Шпаргалка по SQLAlchemy: ORM, сессии, запросы
Связанные шпаргалки: Python, FastAPI, Django, SQLAlchemy