Шаблонизатор Jinja — это компактный синтаксис, предназначенный для упрощения создания HTML-шаблонов.

Переменные, выражения и вызовы функций

В языке шаблонов Jinja использование двойных фигурных скобок {{ }} позволяет извлечь значение переменной, результат выражения или выполнить вызов функции и вывести это в шаблоне. Например:

Определение выражения
>>> from jinja2 import Template
>>>
>>> Template("{{ 10 + 3 }}").render()
'13'
>>> Template("{{ 10 - 3 }}").render()
'7'
>>>
>>> Template("{{ 10 // 3 }}").render()
'3'
>>> Template("{{ 10 / 3 }}").render()
'3.3333333333333335'
>>>
>>> Template("{{ 10 % 3 }}").render()
'1'
>>> Template("{{ 10 ** 3 }}").render()
'1000'
>>>

Для создания выражений можно применять операторы сравнения, присваивания и логические операторы Python.

Вывод переменных
>>> Template("{{ var }}").render(var=12)
'12'
>>> Template("{{ var }}").render(var="hello")
'hello'
>>>

Шаблоны Jinja могут обрабатывать не только строки и числа Python, но и более сложные структуры данных, такие как списки, словари, кортежи и пользовательские классы.

>>> Template("{{ var[1] }}").render(var=[1,2,3])
'2'
>>> Template("{{ var['profession'] }}").render(var={'name':'tom', 'age': 25, 'profession': 'Manager' })
'Manager'
>>> Template("{{ var[2] }}").render(var=("c", "c++", "python"))
'python'
>>> class Foo:
... def __str__(self):
...     return "This is an instance of Foo class"
...
>>> Template("{{ var }}").render(var=Foo())
'This is an instance of Foo class'
>>>

При попытке доступа к несуществующему индексу, Jinja выдает пустую строку.

>>> Template("{{ var[100] }}").render(var=("c", "c++", "python"))
''
>>>
Вызов функции

В Jinja функцию можно вызвать просто через её имя.

>>> def foo():
...     return "foo() called"
...
>>>
>>> Template("{{ foo() }}").render(foo=foo)
'foo() called'
>>>

Атрибуты и методы

Аксесс к атрибутам и методам объекта осуществляется через оператор доступа (.).

>>> class Foo:
...     def __init__(self, i):
...         self.i = i
...     def do_something(self):
...         return "do_something() called"
...
>>>
>>> Template("{{ obj.i }}").render(obj=Foo(5))
'5'
>>>
>>> Template("{{ obj.do_something() }}").render(obj=Foo(5))
'do_something() called'
>>>

Комментарии

Чтобы оставить комментарий в Jinja, используется следующий синтаксис:

{# комментарий #}

{#
    это
    многострочный
    комментарий
#}
        

Объявление переменных

В шаблонах переменные можно задавать с помощью команды set.

{% set fruit = 'apple' %}

{% set name,age = 'Tom', 20 %}
        

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

Цикл и условные выражения

Управляющие конструкции обеспечивают создание циклов и контроль потока в шаблонах. Они используют разделитель {% … %}, а не скобки {{ ... }}.

Инструкция if

Конструкция if в Jinja работает аналогично Python, определяя выполнение блока кода в зависимости от условия. Пример:

{% if bookmarks %}
    <p>User has some bookmarks</p>
{% endif %}

Если значение переменной bookmarksTrue, тогда выводится строка <p>User has some bookmarks</p>. Если переменная не имеет значения, она будет считаться False.

Вы можете использовать конструкции elif и else, как в Python:

{% if user.newbie %}
    <p>Display newbie stages</p>
{% elif user.pro %}
    <p>Display pro stages</p>
{% elif user.ninja %}
    <p>Display ninja stages</p>
{% else %}
    <p>You have completed all stages</p>
{% endif %}

В Jinja управляющие конструкции могут быть вложенными:

{% if user %}
    {% if user.newbie %}
        <p>Display newbie stages</p>
    {% elif user.pro %}
        <p>Display pro stages</p>
    {% elif user.ninja %}
        <p>Display ninja stages</p>
    {% else %}
        <p>You have completed all states</p>
    {% endif %}
{% else %}
    <p>User is not defined</p>
{% endif %}

В ситуациях, где требуется компактность, используется выражение if, записываемое в одной строке. Оно заключается в двойные скобки {{ … }}:

{{ "User is logged in" if loggedin else "User is not logged in" }}
        

Здесь переменная loggedin, если возвращает True, выведет строку "User is logged in", в противном случае "User is not logged in".

Использование условия else не является обязательным. Без него результат будет undefined.

{{ "User is logged in" if loggedin }}
        

В этом случае будет возвращена строка, когда loggedin истинно, иначе ничего.

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

{# Если user.count ревен 1000, код '<p>User count is 1000</p>' отобразится #}
{% if users.count == 1000 %}
    <p>User count is 1000</p>
{% endif %}

{# Если выражение 10 >= 2 верно, код '<p>10 >= 2</p>' отобразится #}
{% if 10 >= 2 %}
    <p>10 >= 2</p>
{% endif %}

{# Если выражение "car" <= "train" верно, код '<p>car <= train</p>' отобразится #}
{% if "car" <= "train" %}
    <p>car <= train</p>
{% endif %}

{#
    Если user залогинен и superuser, код
    '<p>User is logged in and is a superuser</p>' отобразится
#}
{% if user.loggedin and user.is_superuser %}
    <p>User is logged in and is a superuser</p>
{% endif %}

{#
    Если user является superuser, moderator или author, код
    '<a href="#">Edit</a>' отобразится
#}
{% if user.is_superuser or user.is_moderator or user.is_author %}
    <a href="#">Edit</a>
{% endif %}

{#
    Если user и current_user один и тот же объект, код
    <p>user and current_user are same</p> отобразится
#}
{% if user is current_user %}
    <p>user and current_user are same</p>
{% endif %}

{#
    Если "Flask" есть в списке, код
    '<p>Flask is in the dictionary</p>' отобразится
#}
{% if ["Flask"] in ["Django", "web2py", "Flask"] %}
    <p>Flask is in the dictionary</p>
{% endif %}

Если условия становятся слишком сложными или необходимо изменить приоритет выполнения, можно использовать круглые скобки ():

{% if (user.marks > 80) and (user.marks < 90) %}
    <p>You grade is B</p>
{% endif %}
Цикл for

Цикл for позволяет проходить последовательности, такие как списки и словари.

{% set user_list = ['tom', 'jerry', 'spike'] %}

<ul>
{% for user in user_list %}
    <li>{{ user }}</li>
{% endfor %}
</ul>

Пример:

<ul>

    <li>tom</li>

    <li>jerry</li>

    <li>spike</li>

</ul>

Прохождение по значениям словаря осуществляется следующим образом:

{% set employee = { 'name': 'tom', 'age': 25, 'designation': 'Manager' } %}

<ul>
{% for key in employee.items() %}
<li>{{ key }} : {{ employee[key] }}</li>
{% endfor %}
</ul>

Вывод:

<ul>

    <li>designation : Manager</li>

    <li>name : tom</li>

    <li>age : 25</li>

</ul>

Примечание: элементы словаря в Python (до версии 3.7) не имеют фиксированного порядка, поэтому вывод может варьироваться.

Чтобы одновременно получить ключ и значение словаря, используйте метод items().

{% set employee = { 'name': 'tom', 'age': 25, 'designation': 'Manager' } %}

<ul>
{% for key, value in employee.items() %}
<li>{{ key }} : {{ value }}</li>
{% endfor %}
</ul>

Пример:

<ul>

    <li>designation : Manager</li>

    <li>name : tom</li>

    <li>age : 25</li>

</ul>

Цикл for может также использовать условие else, как и в Python, но есть небольшие отличия. В Python блок else выполняется только если цикл завершен без использования break или последовательность пуста. В Jinja блок else в цикле for срабатывает, когда последовательность пуста или отсутствует:

{% set user_list = [] %}

<ul>
{% for user in user_list %}
    <li>{{ user }}</li>
{% else %}
    <li>user_list is empty</li>
{% endfor %}
</ul>

Пример:

<ul>

    <li>user_list is empty</li>

</ul>

Как и в конструкции if, в Jinja возможны многократные вложенные циклы for, создавая связочные структуры через управляющие конструкции:

{% for user in user_list %}
    <p>{{ user.full_name }}</p>
    <p>
        <ul class="follower-list">
            {% for follower in user.followers %}
            <li>{{ follower }}</li>
            {% endfor %}
        </ul>
    </p>
{% endfor %}

В Jinja цикл for предлагает использование специальной переменной loop для отслеживания текущего состояния итерации:

<ul>
{% for user in user_list %}
    <li>{{ loop.index }} - {{ user }}</li>
{% endfor %}
</ul>

Переменная loop.index в цикле for начинается с 1. В таблице ниже перечислены другие часто используемые атрибуты:

Метод Описание
loop.index0 аналогично loop.index, начиная с 0.
loop.revindex возвращает позицию с конца, начиная с 1.
loop.revindex0 возвращает позицию с конца, начиная с 0.
loop.first возвращает True для первой итерации, в противном случае — False.
loop.last возвращает True для последней итерации, в противном случае — False.
loop.length возвращает количество итераций.

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

Фильтры

Фильтры обрабатывают переменные перед их выводом. Применение фильтров осуществляется по следующему синтаксису:

variable_or_value|filter_name

Пример:

{{ comment|title }}

Фильтр title преобразует первые буквы всех слов в заглавные. Таким образом, если значение переменной comment установлено в "dust in the wind", на выходе будет показано "Dust In The Wind".

Для более точного контроля отображения, возможно использование нескольких фильтров одновременно. Рассмотрим пример:

{{ full_name|striptags|title }}

Фильтр striptags удаляет все HTML-теги из переменной. В приведенном примере сначала используется фильтр striptags, затем применяется title.

Фильтры иногда могут принимать аргументы. Чтобы передать аргумент, вызовите фильтр как функцию. Например:

{{ number|round(2) }}

Фильтр round округляет число до заданного количества десятичных знаков.

В таблице ниже приведен список наиболее популярных фильтров.

Название Описание
upper делает все символы заглавными
lower приводит все символы к нижнему регистру
capitalize делает заглавной первую букву в строке, остальные буквы делает строчными
escape экранирует значение так, чтобы его можно было вывести в HTML
safe предотвращает экранирование значения
length возвращает количество элементов в последовательности
trim удаляет пробелы в начале и в конце строки
random возвращает случайный элемент из последовательности

Примечание: полный список фильтров можно посмотреть здесь.

Макросы

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

{% macro render_posts(post_list, sep=False) %}
<div>
    {% for post in post_list %}
        <h2>{{ post.title }}</h2>
        <article>
            {{ post.html|safe }}
        </article>
    {% endfor %}
    {% if sep %}<hr>{% endif %}
</div>
{% endmacro %}

В данном примере создан макрос render_posts, который принимает обязательный параметр post_list и необязательный параметр sep. Использование макроса происходит так:

{{ render_posts(posts) }}

Макрос должен быть объявлен до его вызова, в противном случае возникнет ошибка. Лучше всего сохранять макросы в отдельном файле для удобства импорта.

Предположим, что в файле macros.html в каталоге templates хранятся все макросы. Чтобы импортировать их, используйте команду import:

{% import "macros.html" as macros %}

Теперь можете обращаться к макросам в файле macros.html через переменную macros. Например:

{{ macros.render_posts(posts) }}

Команда {% import “macros.html” as macros %} импортирует все макросы и переменные из файла macros.html. Можно также импортировать конкретные макросы с помощью from:

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

При использовании макросов могут возникнуть ситуации, когда потребуется передать любое количество параметров. Как и в Python с *args и **kwargs, внутри макросов можно использовать varargs и kwargs.

varargs: хранит дополнительные позиционные параметры как кортеж.

kwargs: предоставляет доступ к дополнительным ключевым параметрам в виде словаря.

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

{% macro custom_renderer(para) %}
<p>{{ para }}</p>
<p>varargs: {{ varargs }}</p>
<p>kwargs: {{ kwargs }}</p>
{% endmacro %}

{{ custom_renderer("some content", "apple", name='spike', age=15) }}

В данном примере "apple" присваивается varargs как дополнительный параметр, а такие аргументы, как name=’spike’ и age=15, попадают в kwargs.

Экранирование

Jinja по умолчанию экранирует вывод переменных для безопасности. Например, если переменная содержит HTML-код "<p>Escaping in Jinja</p>", то после рендеринга получится "&lt;p&gt;Escaping in Jinja&lt;/p&gt;". Это позволяет отображать HTML-код как текст, а не выполнять его. Если вы уверены в безопасности данных и хотите их выполнить, применяйте фильтр safe. Пример:

{% set html = "<p>Escaping in Jinja</p>" %}
{{ html|safe }}

Результат:

<p>Escaping in Jinja</p>

Когда нужно использовать фильтр safe на большом фрагменте кода, удобнее применять оператор autoescape для отключения экранирования. Он принимает значения true или false для включения и отключения экранирования. Пример:

{% autoescape true %}
Escaping включено
{% endautoescape %}

{% autoescape false %}
Escaping выключено
{% endautoescape %}

Код между {% autoescape false %} и {% endautoescape %} будет отображаться без экранирования. Если при отключенном экранировании нужно экранировать определенные символы, используйте фильтр escape. Пример:

{% autoescape false %}
    <div class="post">
        {% for post in post_list %}
            <h2>{{ post.title }}</h2>
            <article>
                {{ post.html }}
            </article>
        {% endfor %}
    </div>
    <div>
        {% for comment in comment_list %}
            <p>{{ comment|escape }}</p> # escaping is on for comments
        {% endfor %}
    </div>
{% endautoescape %}

Вложенные шаблоны

Инструкция include позволяет вставлять один шаблон в другой. Это удобно для повторного использования статических секций в различных местах сайта. Пример синтаксиса include:

Предположим, что навигационное меню хранится в файле nav.html, размещенном в папке templates:

<nav>
    <a href="/home">Home</a>
    <a href="/blog">Blog</a>
    <a href="/contact">Contact</a>
</nav>

Чтобы интегрировать это меню в файл home.html, используйте следующий код:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {# вставляем навигационное меню из nav.html #}
    {% include 'nav.html' %}

</body>
</html>

Вывод:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<nav>
    <a href="/home">Home</a>
    <a href="/blog">Blog</a>
    <a href="/contact">Contact</a>
</nav>

</body>
</html>
Наследование шаблонов

Наследование шаблонов является одним из основных элементов Jinja. Принцип схож с объектно-ориентированным программированием (ООП). Все начинается с создания базового шаблона, который содержит HTML-каркас и маркеры, которые можно переопределять в дочерних шаблонах. Маркеры создаются с помощью конструкции block, а дочерние шаблоны для наследования или расширения используют конструкцию extends. Пример:

{# Это шаблон templates/base.html #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>

{% block nav %}
    <ul>
        <li><a href="/home">Home</a></li>
        <li><a href="/api">API</a></li>
    </ul>
{% endblock %}

{% block content %}

{% endblock %}
</body>
</html>

Это базовый шаблон base.html. Он определяет три блока с помощью команды block, которые могут быть переработаны дочерними шаблонами. Функция block принимает один аргумент — название блока, который должен быть уникальным в пределах одного шаблона, иначе возникнет ошибка.

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

{# Это шаблон templates/child.html #}
{% extends 'base.html' %}

{% block content %}
    {% for bookmark in bookmarks %}
        <p>{{ bookmark.title }}</p>
    {% endfor %}
{% endblock %}

Команда extends сообщает Jinja, что child.html является наследником base.html. При обнаружении конструкции extends, Jinja загружает базовый шаблон и заменяет блоки с теми же именами из дочерних шаблонов. Если блоков с заданными именами нет, используются блоки базового шаблона.

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

<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Default Title</title>
</head>
<body>

<ul>
    <li><a href="/home">Home</a></li>
    <li><a href="/api">API</a></li>
</ul>

<p>Bookmark title 1</p>
<p>Bookmark title 2</p>
<p>Bookmark title 3</p>
<p>Bookmark title 4</p>

</body>
</html>

Вы можете изменить стандартный заголовок, перезаписав блок title в child.html:

{# Это шаблон templates/child.html #}
{% extends 'base.html' %}

{% block title %}
Child Title
{% endblock %}

{% block content %}
    {% for bookmark in bookmarks %}
        <p>{{ bookmark.title }}</p>
    {% endfor %}
{% endblock %}

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

{# Это шаблон templates/child.html #}
{% extends 'base.html' %}

{% block title %}
Child Title
{% endblock %}

{% block nav %}
    {{ super() }} {# ссылка на содержимое из родительского шаблона #}
    <li><a href="/contact">Contact</a></li>
    <li><a href="/career">Career</a></li>
{% endblock %}

{% block content %}
    {% for bookmark in bookmarks %}
        <p>{{ bookmark.title }}</p>
    {% endfor %}
{% endblock %}

Результат:

<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Child Title</title>
</head>
<body>

<ul>
    <li><a href="/home">Home</a></li>
    <li><a href="/api">API</a></li>
    <li><a href="/contact">Contact</a></li>
    <li><a href="/career">Career</a></li>
</ul>

<p>Bookmark title 1</p>
<p>Bookmark title 2</p>
<p>Bookmark title 3</p>
<p>Bookmark title 4</p>

</body>
</html>

Вот основная информация о работе с шаблонами Jinja. В дальнейшем эти знания пригодятся при создании сложных и функциональных шаблонов.

Вопросы для самопроверки:

  1. Каким образом в Jinja можно вызывать функции?
  2. Как можно оставить комментарий в шаблоне Jinja?
  3. Как в Jinja можно одновременно использовать несколько фильтров?
  4. Какие конструкции используются в Jinja для создания условных выражений и циклов?
  5. В чем заключается принцип наследования шаблонов в Jinja?