Шаблонизатор 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 %}
Если значение переменной bookmarks — True, тогда выводится строка <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>"
, то после рендеринга получится
"<p>Escaping in Jinja</p>"
. Это позволяет
отображать 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. В дальнейшем эти знания пригодятся при создании сложных и функциональных шаблонов.