Рендеринг шаблонов Flask

Конкатенация HTML-строк в коде Python — это и утомительно, и опасно (подвержено XSS-уязвимостям). Flask интегрирует шаблонизатор Jinja2, чтобы решить эту проблему.

Шаблоны позволяют отделить бизнес-логику от представления страницы, писать более безопасный и удобный в поддержке код.


Основы render_template

render_template()Это наиболее часто используемая функция рендеринга шаблонов во Flask.

Её первый параметр — имя файла шаблона, а следующие именованные аргументы передаются в шаблон как переменные.

Пример

# Путь к файлу: app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    # Отрендерить шаблон templates/index.html, передав переменные title и name
    return render_template("index.html", title=EXAMPLE Home, name="World")

@app.route("/user/<username>")
def profile(username):
    # Передать username из URL в шаблон
    return render_template("user.html", username=username, posts=[
        {"title": Введение во Flask, "date": "2026-05-01"},
        {"title": Шаблон Jinja2, "date": "2026-05-10"},
    ])

Файлы шаблонов размещаются в каталоге проектаtemplates/В папке:

example-flask-test/
├── app.py
└── templates/
    ├── index.html
    └── user.html

Содержимое index.html:

Пример (index.html)

<!-- Путь к файлу: templates/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }} - ПРИМЕР</title>
</head>
<body>
    <h1>{{ title }} </h1>
     <p>Привет {{ name }}。</p>
</body>
</html>

Посетитеhttp://127.0.0.1:5000/, результат вывода:


Базовый синтаксис Jinja2

Три основных синтаксиса в шаблонах Jinja2:

Синтаксис Назначение Пример
{{ ... }} Вывод значений переменных, автоматическое экранирование HTML {{ username }}
{% ... %} Управляющие операторы (if, for, block и т. д.) {% if user %}
{# ... #} Комментарии, не появятся в отрендеренном результате {# Это комментарий #}

Пример шаблона

Пример (user.html)

<!-- Путь к файлу: templates/user.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ username }} - ПРИМЕР</title>
</head>
<body>
    <h1>Личная домашняя страница {{ username }}</h1>

    <!-- Условие: если список posts пуст, вывести подсказку -->
    {% if posts %}
        <h2>Последние статьи:</h2>
        <ul>
        <!-- Цикл по списку posts -->
        {% for post in posts %}
            <!-- loop.index — встроенная переменная Jinja2, начинается с 1 -->
            <li>{{ loop.index }}. {{ post.title }} ({{ post.date }})</li>
        {% endfor %}
        </ul>
    {% else %}
        <p>Пока нет статей</p>
    {% endif %}

    <!-- Использование фильтра: upper преобразует текст в верхний регистр -->
    <p>Имя пользователя в верхнем регистре: {{ username|upper }}</p>
</body>
</html>

Общие фильтры

Фильтры используются для изменения формата вывода переменных с помощью символа вертикальной черты.|Вызов.

Filter Функция Пример Вывод
upper Преобразовать в верхний регистр {{ "hello"|upper }} HELLO
lower Преобразовать в нижний регистр {{ "HELLO"|lower }} привет
title Сделать первую букву заглавной {{ "hello world"|title }} Hello World
length Получить длину {{ [1,2,3]|length }} 3
default Установить значение по умолчанию {{ name|default("anonymous") }} Аноним (когда name пусто)
safe Пометить как безопасный HTML (без экранирования) {{ "Жирный"|safe }} Отображается как жирный текст
join Объединить список {{ ["a","b"]|join(",") }} a,b

Предупреждение о безопасности:safeФильтр safe отключает автоэкранирование; используйте его только тогда, когда вы абсолютно доверяете источнику данных. Никогда не используйте его для пользовательского ввода.safe。


Автоэкранирование — защита от XSS

Jinja2 по умолчанию экранирует HTML во всех выводимых переменных; это ключевая защита для веб-безопасности.

Например, если имя пользователя, отправленное пользователем, —<script>alert("xss")</script>, то при отрисовке шаблона оно автоматически преобразуется в безопасный текст:

<!-- 模板中直接使用变量 -->
<p>{{ username }}</p>

<!-- 渲染结果(HTML 特殊字符已被转义) -->
&lt;p&gt;&amp;lt;script&amp;gt;alert(&amp;quot;xss&amp;quot;)&amp;lt;/script&amp;gt;&lt;/p&gt;

Экранирование применяется к.html、.htm、.xml、.xhtml、.svgФайл шаблона в конце.


Наследование шаблонов — устранение дублирования кода

Наследование шаблонов — одна из самых мощных возможностей Jinja2. Оно позволяет определить "базовый макет", а затем заполнять содержимое через дочерние шаблоны.

Это позволяет избежать повторного написания общих частей, таких как заголовки, навигация, нижние колонтитулы и т. д., на каждой странице.

Базовый шаблон (родительский шаблон)

Пример

<!-- Путь к файлу: templates/base.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>{% block title %}ПРИМЕР: Руководство по Flask{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <header>
        <h1>ПРИМЕР: Руководство по Flask</h1>
        <nav>
            <a href="/">Главная</a> |
            <a href="/about">О нас</a>
        </nav>
    </header>

    <main>
    <!-- block — это область-заполнитель, которую дочерние шаблоны могут заполнять или переопределять -->
    {% block content %}{% endblock %}
    </main>

    <footer>
        <p>&copy; 2026 example.com</p>
    </footer>
</body>
</html>

Дочерний шаблон (наследует родительский шаблон)

Пример

<!-- Путь к файлу: templates/index.html -->
{% extends "base.html" %}

<!-- Переопределяет блок title родительского шаблона -->
{% block title %}EXAMPLE Tutorial - Главная{% endblock %}

<!-- Заполняет блок content родительского шаблона -->
{% block content %}
    <h2>Добро пожаловать в EXAMPLE</h2>
    <p>{{ greeting }}</p>

    {% if user %}
        <p>Текущий пользователь: {{ user }}</p>
    {% else %}
        <p><a href="/login">Пожалуйста, сначала войдите в систему</a></p>
    {% endif %}
{% endblock %}
Теги Функция
{% extends "base.html" %} Объявите, что текущий шаблон наследуется от base.html (должно быть в первой строке)
{% block name %}...{% endblock %} Определите блок, который может быть переопределён дочерними шаблонами
{{ super() }} Вызвать содержимое одноимённого блока родительского шаблона внутри блока дочернего шаблона.

После использования наследования шаблонов вам нужно изменить толькоbase.htmlв одном месте, и общие части всех страниц (такие как навигация, нижний колонтитул) будут автоматически обновляться.


Встроенные объекты в шаблонах

Следующие объекты Flask можно использовать непосредственно в шаблонах, не передавая их черезrender_template():

Object Описание Пример использования в шаблоне
request Текущий объект запроса {{ request.path }}
session Текущие данные сессии {{ session.get("username") }}
g Глобальные переменные уровня запроса {{ g.user }}
config Конфигурация приложения {{ config["APP_NAME"] }}
url_for() Функция генерации URL {{ url_for("index") }}
get_flashed_messages() Получить flash-сообщения {% for msg in get_flashed_messages() %}...

Включение других шаблонов — include

Использование{% include %}Вы можете встроить другой шаблон внутри одного шаблона:

Пример

<!-- Путь к файлу: templates/_navbar.html (префикс с подчёркиванием обозначает частичный шаблон) -->
<nav style="background:#f0f0f0;padding:10px;">
    <a href="/">Главная</a> |
    <a href="/posts">Статьи</a> |
    <a href="/about">О нас</a>
</nav>

Пример

<!-- Включите панель навигации в любой шаблон -->
<!DOCTYPE html>
<html>
<head><title>Страница</title></head>
<body>
    {% include "_navbar.html" %}

    <h1>Содержимое страницы</h1>
    <p>Это основное содержимое страницы.</p>
</body>
</html>

Лучшие практики: Наследование шаблонов (extends) используется для повторного использования макета уровня страницы,includeИспользуется для повторного использования кода на уровне компонентов. Повторно используемые компоненты, такие как панели навигации и боковые панели, подходят для include.


Полный пример: Главная страница блога

Примените вышеописанные знания в реальном сценарии:

Пример

# Путь к файлу: app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    # Имитация данных статей в базе данных.
    articles = [
        {"id": 1, "title": Руководство по началу работы с Flask, "author": "example", "views": 1024},
        {"id": 2, "title": Шаблоны Jinja2: объяснение, "author": "admin", "views": 512},
        {"id": 3, "title": Проектирование RESTful API, "author": "example", "views": 256},
    ]
    return render_template("blog.html", articles=articles)

Пример

<!-- Путь к файлу: templates/blog.html -->
{% extends "base.html" %}

{% block title %}ПРИМЕР Блог{% endblock %}

{% block content %}
    <h2>Последние статьи</h2>

    {% if articles %}
        {% for article in articles %}
            <!-- Добавить разные стили в зависимости от количества просмотров -->
            <div class="article" {% if article.views > 1000 %}style="background:#fff3cd;"{% endif %}>
                <h3>{{ article.title }}</h3>
                <p>Автор: {{ article.author }} | Просмотры: {{ article.views }}</p>
                <!-- Используйте переменную цикла для создания разделителя -->
                {% if not loop.last %}
                    <hr>
                {% endif %}
            </div>
        {% endfor %}
    {% else %}
        <p>Пока нет статей, пожалуйста, заходите позже.</p>
    {% endif %}
{% endblock %}

Специальные переменные Jinja2

Переменная Описание
loop.index Текущий индекс цикла, начинается с 1
loop.index0 Текущий индекс цикла, начинается с 0
loop.first Является ли текущая итерация первой в цикле
loop.last Является ли текущая итерация последней в цикле
loop.length Общая длина последовательности
Другие расширения