Сообщить об ошибке.

Механизм шаблонов Jinja2 в веб-приложении на Flask

Фреймворк Flask использует модуль jinja2 в качестве механизма шаблонов. Можно использовать другой механизм шаблонов, но все равно нужно установить Jinja2 для запуска самого Flask. Это требование необходимо для включения многофункциональных расширений, т.к. расширение может зависеть от присутствия Jinja2 в среде выполнения приложения Flask.

В этом разделе дается очень краткое введение в то, как Jinja2 интегрируется во Flask. Если нужна информация о самом синтаксисе шаблонизатора, то за дополнительной информацией обратитесь к документации шаблонизатора Jinja2.

По умолчанию Flask, настраивает Jinja2 следующим образом:

  • при использовании flask.render_template() - автоматическое экранирование включено для всех шаблонов, оканчивающихся на .html, .htm, .xml, а также .xhtml.
  • при использовании flask.render_template_string() автоматическое экранирование включено для всех строк.
  • в шаблоне есть возможность включить/выключить автоматическое экранирование с помощью тега {% autoescape %}.
  • Flask вставляет пару глобальных функций и помощников в контекст Jinja2 в дополнение к значениям, которые присутствуют по умолчанию.

Содержание:


Стандартный контекст шаблонов приложения Flask.

По умолчанию в шаблонах приложения Jinja2 доступны следующие глобальные переменные:

  • config - текущий объект конфигурации flask.config.
  • request - текущий объект запроса flask.request. Переменная не будет доступна, если шаблон был визуализирован без активного контекста запроса.
  • session - текущий объект сессии/сеанса flask.session. Переменная не будет доступна, если шаблон был визуализирован без активного контекста запроса.
  • g - глобальных объект с привязкой к запросу flask.g. Переменная не будет доступна, если шаблон был визуализирован без активного контекста запроса.
  • url_for() - функция flask.url_for().
  • get_flashed_messages() - функция flask.get_flashed_messages().

Поведение глобальных переменных в контексте шаблонов.

Приведенные выше объекты добавляются в контекст переменных, и они не являются глобальными переменными шаблона. Это говорит о том, что по умолчанию они не отображаются в контексте шаблонов, которые импортируются в вызываемый шаблон из функции-представления. Частично это вызвано соображениями производительности, частично для ясности.

Что это значит? Если есть макрос, который необходимо импортировать, то он должен получить доступ к объекту запроса, для этого есть две возможности:

  • явно передать объект запроса request макросу в качестве параметра или атрибута интересующего объекта запроса.
  • импортировать макрос с контекстом, т.е. при импорте использовать with context.

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

{% from '_helpers.html' import my_macro with context %}

Управление автоэкранированием.

Автоэкранирование - это концепция автоматического экранирования специальных символов HTML. Специальные символы HTML или XML, и, следовательно, XHTML - это &, >, <, " а также '. Так как эти символы, сами по себе, несут определенные значения, то в простом тексте HTML-документа, они должны заменяться, на так называемые "HTML-объекты". Невыполнение этого требования вызовет разочарование пользователя из-за невозможности использовать эти символы в тексте, но также может привести к проблемам с безопасностью веб-приложения на Flask.

Иногда просто необходимо отключить автоматическое экранирование в шаблонах. Это может произойти, если явно внедрить HTML в страницы, например из системы, которая генерирует безопасный HTML, такой как markdown конвертер в HTML.

Этого можно добиться тремя способами:

  • прежде чем передавать HTML в шаблон, который генерируется кодом функцией-представлением, оберните его в объект flask.Markup(). Это рекомендуемый способ.
  • Внутри шаблона используйте фильтр Jinja2 |safe, чтобы явно пометить строку как безопасный HTML. Например {{myvariable | safe}}.
  • Временно полностью отключите систему автоэкранирования.

Для временного отключения системы автоэкранирования в шаблонах, можно использовать блок {% autoescape%}:

{% autoescape false %}
    <p>autoescaping is disabled here
    <p>{{ will_not_be_escaped }}
{% endautoescape %}

Когда, таким образом отключаете автоэкранирование, то будьте очень осторожны с переменными, которые используете в этом блоке!

Регистрация пользовательских фильтров в приложении Flask.

Если необходимо зарегистрировать свои собственные фильтры для использования в шаблонах приложения Flask, то есть два способа сделать это. Можно поместить их вручную в app.jinja_env (это по сути jinja2.Environment) приложения или использовать декоратор @app.template_filter().

Два следующих примера работают одинаково:

@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]

def reverse_filter(s):
    return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter

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

Например, если есть в переданном контексте список с именем mylist:

{% for x in mylist | reverse %}
{% endfor %}

Процессоры контекста Flask и контекст шаблонов.

Чтобы автоматически вводить новые переменные в контекст шаблона, во Flask существуют процессоры контекста. Процессоры контекста запускаются до рендеринга шаблона и имеют возможность вставлять новые значения в контекст шаблона. Обработчик контекста - это функция, возвращающая словарь. Затем ключи и значения этого словаря объединяются с контекстом шаблона для всех шаблонов в приложении:

@app.context_processor
def inject_user():
    return dict(user=g.user)

Вышеупомянутый обработчик контекста делает переменную с именем user доступной в шаблоне со значением g.user. Этот пример не очень интересен, потому что объект flask.g в любом случае доступен в шаблонах, но он дает представление о том, как это работает.

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

@app.context_processor
def utility_processor():
    def format_price(amount, currency="€"):
        return f"{amount:.2f}{currency}"
    return dict(format_price=format_price)

Вышеупомянутый обработчик контекста делает функцию format_price() доступной для всех шаблонов:

{{ format_price(0.33) }}

Этот пример демонстрирует, как передавать функции в обработчик контекста, но также можно создать format_price() как пользовательский фильтр шаблонов (см. "Регистрация пользовательских фильтров").

Наследование шаблонов в приложении Flask.

Самая мощная часть шаблонизатора Jinja2 - это наследование шаблонов. Наследование шаблонов позволяет создать базовый шаблон, который содержит все общие элементы сайта и определяет блоки, которые дочерние шаблоны могут переопределить.

Звучит сложно, но это очень просто.

Базовый шаблон.

Это базовый шаблон layout.html, который определяет скелет HTML-документа, который можно использовать для страницы с двумя столбцами. Задача "дочерних" шаблонов - заполнить пустые блоки контентом:

<!doctype html>
<html>
  <head>
    {% block head %}
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <title>{% block title %}{% endblock %} - My Webpage</title>
    {% endblock %}
  </head>
  <body>
    <div id="content">{% block content %}{% endblock %}</div>
    <div id="footer">
      {% block footer %}
      &copy; Copyright 2021 by <a href="http://domain.name/">you</a>.
      {% endblock %}
    </div>
  </body>
</html>

В этом примере теги {% block %} определяют четыре блока, которые могут заполнять дочерние шаблоны. Все, что делает тег block - это сообщает механизму шаблонов, что дочерний шаблон может переопределить эти части шаблона.

Дочерний шаблон.

Дочерний шаблон может выглядеть так:

{% extends "layout.html" %}
{% block title %}Index{% endblock %}
{% block head %}
  {{ super() }}
  <style type="text/css">
    .important { color: #336699; }
  </style>
{% endblock %}
{% block content %}
  <h1>Index</h1>
  <p class="important">Welcome on my awesome homepage.</p>
{% endblock %}

Здесь тег {% extends%} является ключевым. Он сообщает механизму шаблонов, что этот шаблон "расширяет" другой шаблон. Когда система шаблонов оценивает этот шаблон, то сначала определяет местонахождение родителя layout.html. Тег extends должен быть первым тегом в шаблоне. Чтобы отобразить содержимое блока, который определен в родительском шаблоне, нужно использовать {{ super() }}.