Содержание
  1. Основы работы с Flask и Jinja
  2. Дополнительные макросы
  3. Преимущества использования макросов в Jinja
  4. Radio-макрос Jinja: сохранить checked после отправки формы
  5. FAQ
  6. Частые вопросы
  7. Заключение
  8. Материал входит в подборки
ГайдыОбновлено · 10.2026

Python и Flask: создание пользовательских макросов в Jinja для форм

13 марта 2024 · 13 минут чтения

В современной веб-разработке, использование фреймворков, таких как Flask для Python, значительно упрощает создание и обслуживание веб-приложений. Flask предлагает гибкую архитектуру, включая мощные возможности для шаблонизации с помощью движка Jinja2. Рассмотрим разработку пользовательских макросов Jinja для работы с формами. Макросы в Jinja могут сократить повторяющийся код и упростить обслуживание шаблонов, особенно когда речь идет о формах.

Основы работы с Flask и Jinja

Прежде чем перейти к макросам, кратко освежим в памяти, как работать с Flask и Jinja. Flask — это легковесный веб-фреймворк для Python, который позволяет быстро создавать веб-приложения. Jinja — это система шаблонов для Python, используемая по умолчанию в Flask для рендеринга HTML-страниц с динамическим контентом.

Создание мини-проекта

Для наглядности создадим мини-проект веб-приложения на Flask, в котором продемонстрируем использование макроса для формы.

Шаг 1: установка и настройка Flask

Установите Flask через пакетный менеджер:

pip install Flask

Создайте основной файл приложения, например, app.py, и добавьте в него базовый код для запуска Flask-приложения:

from flask import Flask, render_template
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
    return render_template('form.html')
if __name__ == '__main__':
    app.run(debug=True)

Шаг 2: создание HTML-шаблона с формой

В папке templates вашего проекта создайте файл form.html. Это HTML-шаблон формы:

{% from "macros.html" import form_macro %}
<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Форма</title>

</head>

<body>

<form method="post">

{{ form_macro('email', 'Электронная почта') }}

{{ form_macro('password', 'Пароль', type='password') }}

<button type="submit">Отправить</button>

</form>

</body>

</html>

Шаг 3: создание макроса для формы

В том же каталоге templates создайте файл macros.html. В этом файле определите макрос для элементов формы:

{% macro form_macro(name, label, type='text') %}

<div>

<label for="{{ name }}">{{ label }}:</label>

<input type="{{ type }}" id="{{ name }}" name="{{ name }}">

</div>

{% endmacro %}

Файл macros.html содержит только определение макроса. Импорт нужен в том шаблоне, который вызывает макрос, то есть в form.html; в самом macros.html импортировать себя не нужно. Строка импорта для form.html:

{% from "macros.html" import form_macro %}

Дополнительные макросы

рассмотрим создание макросов для более сложных элементов форм, таких как выпадающие списки (select), радиокнопки и чекбоксы. Эти макросы позволят разработчикам еще более эффективно использовать возможности Jinja в Flask, делая код более читаемым и удобным для повторного использования.

Выпадающий список (Select)

Создание выпадающего списка часто требуется для того, чтобы предложить пользователю выбрать один из нескольких вариантов. Вот пример макроса для создания такого списка:

{% macro select_macro(name, label, choices, selected=None) %}

<div>

<label for="{{ name }}">{{ label }}:</label>

<select name="{{ name }}" id="{{ name }}">

{% for value, label in choices %}

<option value="{{ value }}" {% if selected == value %}selected{% endif %}>{{ label }}</option>

{% endfor %}

</select>

</div>

{% endmacro %}

Принимает имя поля, метку, список вариантов выбора и необязательный аргумент, указывающий на уже выбранный вариант. Пример использования:

{% from "macros.html" import select_macro %}

{{ select_macro('fruit', 'Выберите фрукт', [('apple', 'Яблоко'), ('banana', 'Банан'), ('cherry', 'Вишня')]) }}

Радиокнопки

Радиокнопки позволяют выбрать один из нескольких предложенных вариантов. Вот как может выглядеть макрос для радиокнопок:

{% macro radio_macro(name, label, options) %}

<fieldset>

<legend>{{ label }}</legend>

{% for value, description in options %}

<div>

<input type="radio" id="{{ name }}_{{ loop.index }}" name="{{ name }}" value="{{ value }}">

<label for="{{ name }}_{{ loop.index }}">{{ description }}</label>

</div>

{% endfor %}

</fieldset>

{% endmacro %}

Он создает группу радиокнопок с общей меткой. Пример использования:

{% from "macros.html" import radio_macro %}

{{ radio_macro('gender', 'Пол', [('male', 'Мужской'), ('female', 'Женский')]) }}

Чекбоксы

Чекбоксы используются, когда необходимо предоставить возможность выбрать несколько вариантов из предложенных. Вот пример макроса для чекбоксов:

{% macro checkbox_macro(name, label, values, selected=[]) %}

<div>

<label>{{ label }}</label>

{% for value in values %}

<div>

<input type="checkbox" id="{{ name }}_{{ loop.index }}" name="{{ name }}" value="{{ value }}" {% if value in selected %}checked{% endif %}>

<label for="{{ name }}_{{ loop.index }}">{{ value }}</label>

</div>

{% endfor %}

</div>

{% endmacro %}

Он позволяет создать несколько чекбоксов с общей меткой. Пример использования:

{% from "macros.html" import checkbox_macro %}

{{ checkbox_macro('hobbies', 'Хобби', ['Рисование', 'Пение', 'Кодирование'], ['Кодирование']) }}

Добавление этих макросов в вашу библиотеку Jinja значительно облегчит работу с формами, делая ваш код более организованным и удобным для внесения изменений.

Преимущества использования макросов в Jinja

Макросы Jinja предлагают множество преимуществ для разработчиков:

  • Повторное использование кода: определяйте элементы формы один раз и используйте их в разных местах вашего приложения.
  • Упрощение обслуживания: изменения в структуре или стиле элементов формы нужно вносить только в одном месте.
  • Повышение читаемости шаблонов: макросы делают шаблоны более организованными и легкими для понимания.

Radio-макрос Jinja: сохранить checked после отправки формы

Макросы Jinja принимают аргументы и вызываются как функции, а макрос из другого шаблона перед вызовом нужно импортировать (Jinja: Templates). Поэтому расширить radio_macro аргументом selected несложно: остаётся сравнить его со значением варианта через == (Jinja: Templates) и поставить checked только той радиокнопке, которая совпала — в одной группе выбирается лишь одна такая кнопка (MDN: input type=radio).

Почему текущий radio_macro не помнит выбор

Существующий макрос принимает name, label и options, но нигде не выводит checked. Серверный HTML, который возвращается после POST, не восстанавливает выбор: атрибут checked в нём отсутствует у всех вариантов. Поэтому выбранное значение нужно явно передать в макрос из обработчика. При этом отсутствие checked в серверном HTML не означает, что во всех браузерах визуально не будет отмечено ни одной кнопки: по документации MDN Firefox по умолчанию сохраняет динамическое состояние checked между загрузками страницы, и этим поведением можно управлять атрибутом autocomplete.

selected=None и checked по selected == value

Добавьте необязательный аргумент selected=None. В цикле для каждого варианта проверяйте selected == value — оператор == сравнивает два объекта на равенство (Jinja: Templates). Импортируйте макрос в шаблон через {% from "macros.html" import radio_macro %}. В приведённом form.html четвёртый аргумент передаётся при каждом рендере: при первом обычном GET это None, при допустимом POST — выбранная строка.

POST route: request.form.get(channel), allowlist options

По умолчанию маршрут Flask отвечает только на GET, а методы задаются параметром methods (Flask: Quickstart). Чтобы принять POST, укажите methods=["GET", "POST"]. Внутри читайте request.form.get("channel"), сверяйте со списком допустимых значений (allowlist) и возвращайте найденное строковое значение в шаблон как selected_channel. Если значение пустое или не входит в allowlist, передавайте None.

Редакционный пример: готовый артефакт для копирования

Ниже — согласованный редакционный пример из трёх файлов: маршрут Flask, макрос Jinja и шаблон формы. Пример подготовлен редакцией как шаблон для адаптации; он не запускался и не проверялся в живом приложении. Имена поля channel, вариантов options и allowlist согласованы между файлами.

Файл app.py — маршрут с импортами и передачей выбранного значения в шаблон:

КОД20 строк
# Редакционный пример; не запускался.
from flask import Flask, render_template, request

app = Flask(__name__)

ALLOWED = {"email", "push", "sms"}
OPTIONS = [("email", "Email"), ("push", "Push"), ("sms", "SMS")]

@app.route("/", methods=["GET", "POST"])
def home():
    posted = request.form.get("channel")
    selected = posted if posted in ALLOWED else None
    return render_template(
        "form.html",
        options=OPTIONS,
        selected_channel=selected,
    )

if __name__ == "__main__":
    app.run(debug=True)

Файл templates/macros.html — макрос radio_macro с аргументом selected=None и условным checked:

КОД16 строк
# Редакционный пример; не запускался.
{% macro radio_macro(name, label, options, selected=None) %}
<fieldset>
  <legend>{{ label }}</legend>
  {% for value, description in options %}
  <div>
    <input type="radio"
           id="{{ name }}_{{ loop.index }}"
           name="{{ name }}"
           value="{{ value }}"
           {% if selected == value %}checked{% endif %}>
    <label for="{{ name }}_{{ loop.index }}">{{ description }}</label>
  </div>
  {% endfor %}
</fieldset>
{% endmacro %}

Файл templates/form.html — форма импортирует и вызывает макрос с selected_channel:

КОД15 строк
# Редакционный пример; не запускался.
{% from "macros.html" import radio_macro %}
<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>Форма</title>
</head>
<body>
  <form method="post">
    {{ radio_macro("channel", "Канал", options, selected_channel) }}
    <button type="submit">Отправить</button>
  </form>
</body>
</html>

Статически ожидаемый результат этого кода: если posted входит в ALLOWED, в шаблон уходит эта строка и checked выводится только у совпавшего варианта; если posted пусто или не входит в allowlist, уходит None, и тогда атрибут checked отсутствует у всех вариантов в серверном HTML. Сверка опирается на allowlist; доверие телу запроса здесь не используется. Пример не запускался. Поэтому речь идёт о выводе checked в сгенерированной разметке. Фактическое состояние выбора в браузере не гарантировано: по документации MDN Firefox по умолчанию сохраняет динамическое состояние checked между загрузками.

Условный пример: три варианта email, push, sms; POST приносит push — тогда selected == value истинно только для варианта push, и в сгенерированном HTML атрибут checked присутствует только у него. Проверка выбора должна опираться на сравнение со allowlist. Доверять телу запроса нельзя: условная таблица ниже показывает результат сверки. Это вывод по коду примера. Проверенное универсальное визуальное состояние браузера здесь не утверждается.

Случай (условно) POST channel В allowlist? selected_channel Атрибут checked в HTML
Совпадение push да push 1
Нет совпадения fax нет None 0
Пусто "" нет None 0

Поведение браузера и движка в этих трёх случаях остаётся теоретическим сценарием: редакция его не запускала и не проверяла в живом приложении. Формулировки и код выше — шаблон, который надо адаптировать под собственные имена полей и структуру приложения.

FAQ

Вопрос: Что проверяет тест boolean в Jinja и как он связан с Flask?

Ответ: Тест boolean возвращает истину, если объект является булевым значением (Jinja: Templates). Он добавлен в Jinja 2.11 и проверяет именно тип bool. «Истинность» значения вообще здесь не проверяется. Поэтому условный фрагмент {% if value is boolean %} success! {% endif %} сработает только тогда, когда value — это Python-объект True или False. Строки, пришедшие из формы, булевыми не являются: request.form.get("channel") возвращает строку, и даже строка "True" не пройдёт тест boolean, потому что это str. Тип bool здесь не подходит. Если нужно привести значение из формы к булеву типу, преобразование делайте явно в обработчике и передавайте в шаблон уже настоящий True/False; тогда тест boolean будет вести себя предсказуемо.

Вопрос: Почему после отправки формы ни одна радиокнопка не выбрана?

Ответ: Потому что макрос не принимает ранее выбранное значение и не выводит checked в серверном HTML. Сервер не восстанавливает выбор в возвращённой разметке. При этом отсутствие checked в HTML не гарантирует одинакового визуального состояния во всех браузерах: по документации MDN Firefox по умолчанию сохраняет динамическое состояние checked между загрузками. Чтобы выбор восстанавливался на сервере, значение нужно передать в макрос явно.

Вопрос: Как передать выбранное значение обратно в макрос?

Ответ: Обработчик читает значение поля из отправленных данных, сверяет его со списком допустимых вариантов и передаёт найденную строку в шаблон как значение для сравнения с каждым вариантом.

Вопрос: Что делать, если отправлено значение, которого нет в списке?

Ответ: Сверяйте его со списком допустимых вариантов и при отсутствии совпадения передавайте пустое значение, чтобы ни одна кнопка не была отмечена.

Частые вопросы

Как в Flask вывести данные в label при изменении выбора радиокнопки? Flask и Jinja формируют HTML на сервере до отправки страницы в браузер, поэтому после загрузки страницы они уже не могут обновить label сами по себе. Изменение выбора радиокнопки — это событие в браузере: у <input type="radio"> есть события change и input (MDN: input type=radio). Чтобы label менялся сразу при переключении, нужен небольшой клиентский скрипт, который слушает change на группе радиокнопок и подставляет текст в связанный label. Пример ниже условный и не запускался редакцией; он показывает структуру, где id радиокнопки и for у label совпадают, а отдельный label с id="live-label" служит местом вывода.

КОД22 строки
<!-- Условный пример; редакция его не запускала. -->
<fieldset>
  <legend>Канал</legend>
  <div>
    <input type="radio" id="channel_email" name="channel" value="email">
    <label for="channel_email">Email</label>
  </div>
  <div>
    <input type="radio" id="channel_push" name="channel" value="push">
    <label for="channel_push">Push</label>
  </div>
</fieldset>
<p>Выбрано: <label id="live-label" for="channel_push">—</label></p>
<script>
  const group = document.querySelectorAll('input[name="channel"]');
  const out = document.getElementById('live-label');
  group.forEach(function (radio) {
    radio.addEventListener('change', function () {
      out.textContent = radio.value;
    });
  });
</script>

Если обновление на клиенте не требуется, достаточно показать выбор после отправки формы. Тогда серверный путь остаётся прежним: обработчик читает значение, сверяет его со списком допустимых и передаёт в шаблон, а макрос ставит checked по совпадению. Оба подхода решают разные задачи и могут использоваться вместе.

Почему после отправки формы ни одна радиокнопка не выбрана? Потому что макрос не принимает ранее выбранное значение и не выводит checked в серверном HTML: сервер не восстанавливает выбор в возвращённой разметке. Отсутствие checked в HTML не гарантирует одинакового визуального состояния во всех браузерах — по документации MDN Firefox по умолчанию сохраняет динамическое состояние checked между загрузками. Чтобы выбор восстанавливался на сервере, значение нужно передать в макрос явно.

Как передать выбранное значение обратно в макрос? Обработчик читает значение поля из отправленных данных, сверяет его со списком допустимых вариантов и передаёт найденную строку в шаблон как значение для сравнения с каждым вариантом.

Что делать, если отправлено значение, которого нет в списке? Сверяйте его со списком допустимых вариантов и при отсутствии совпадения передавайте пустое значение, чтобы ни одна кнопка не была отмечена.


Заключение

Использование пользовательских макросов в Jinja для работы с формами в Flask упростит создание веб-приложений и их поддержку. Создавая макросы для повторяющихся элементов формы, вы экономите время на написании кода и делаете его более читаемым и легким для поддержки.

Читайте также

3 материала