Введение в шаблоны
Шаблоны
Jinja шаблоны это еще один язык с которым мы познакомимся. Синтаксис языка был специально разработан чтобы делать текстовые документы динамическими. HTML — в своей основе текстовый документ и использование шаблонов на серверной стороне позволяет сильно упростить жизнь программиста.
Шаблон создается как часть кода проекта. Он имеет расширение .html и кладется в специальной директории проекта. Обычно это директория templates. Для больших проектов есть возможность структурировать большое количество шаблонов создавая множество таких директорий, в будущем мы рассмотрим примеры как это можно сделать.
Flask имеет встроенну поддержку Jinja. Но шаблоны получили популярность как отдельный проект и используются множеством других проектов.
Процесс превращения исходного шаблона в конечный документ называется рендерингом, этот термин уже устоялся и происходит от слова render.
Две главные функции для работы с шаблонами в Flask render_template и render_template_string:
def render_template(template_name, **context):
...
def render_template_string(source, **context):
...
Первая функция получает на вход файл шаблона и любое количество именнованных переменных. Вторая строку с шаблоном и именованные переменные. На выходе возвращают строку.
Обратите внимание, что функции возвращают не обязательно HTML. Это может быть и другой текстовый формат. Например CSV или YAML. Даже JSON, но для JSON'а у Flask есть и другие полезные функции.
Использование шаблонов
Давайте создадим простейший Flask проект и создадим для него шаблон.
Создайте проект со следующей структурой:
- /<project_name>:
- hello.py
- templates/
- index.html
# Содержимое hello.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def frontpage():
context = {
'name': 'Пользователь',
'age': 30
}
return render_template("index.html", **context)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Демо сайт</title>
</head>
<body>
<h1>Пользователь {{ name }}!</h1>
<h2>Возраст {{ age }}</h2>
</body>
</html>
Если запустить этот проект и открыть страницу в браузере, то вам появится страница которая выведет информацию о пользователе и его возрасте. Обратите внимание, что в коде мы создаем переменные name и age и они становятся доступными в коде.
Основы синтаксиса Jinja:
{{ ... }}— вывод значений и блоков{# ... #}— комментарии{% ... %}— выражения и теги{{ |<filter> }}— фильтры
С ними мы сейчас разберемся.
Переменные
В предыдущем примере мы воспользовались возможностью шаблонов подставлять значения переменны. В Python есть похожий механизм, он позволяет создавать очень похожие шаблоны. Например, этот же пример можно было бы записать как-то так:
@app.route("/")
def frontpage():
name = 'Автор сайта'
age = 30
return f""" ...
<h1>Пользователь {name}!</h1>
<h2>Возраст {age}</h2>
...
"""
Пока разница не сильно очевидна, но достаточно запомнить, что вызов переменной обозначается двойными фигурными скобками {{ и }}:
{{ variable }}
На выходе переменная будет преобразована в значение variable. Внутри HTML кода это будет выглядеть приблизительно так:
<h1>{{ title }}</h1>
<p>{{ body }}</p>
В качестве переменной можно передать не только строку или число, а так же и словарь или объект. В таком случае у них появится возможность использовать методы, свойства или ключи:
{{ foo.bar }} - обращение к словарю по ключу, или свойству объекта
{{ foo['bar'] }} - этот код выдаст тот же результат
Оба варианта дадут одинаковый результат. Они сделаны для удобства написания шаблонов. Потому что шаблоны могут писать и редактировать не только программисты.
Да, в отличии от Python оба варианта работают. С небольшими отличиями в том, что второй будет незначительно быстрее со словарями, а первый со свойствами объектов. А на самом деле используйте как вам удобнее.
Комментарии
Комментарии в шаблонах очень удобно использовать для разработки. Обратите внимание, что Jinja не разделяет конечные форматы и не понимает что такое HTML комментарии. Хотя ваш редактор скорее всего будет их подсвечивать как часть которая не используется.
Блок заключеный в {# и #} считается комментарием:
{# Этот комментарий скрывает следующий блок и он не будет виден после рендеренга шаблона
{% for user in users %}
...
{% endfor %}
#}
<!-- Это HTML комментарий и он будет виден в исходнике страницы {{ value }} -->
В Jinja нет возможности использовать вложенные шаблоны. Блок начинающийся с первого {# заканчивается первым #}.
Выражения и Теги
В отличии от f-строк в Python в Jinja шаблонах можно использовать и более сложные конструкции. Вы не сможете реализовать сложную логику программы (для этого лучше использовать код), но вы можете управлять процессом генерации шаблона.
Конструкции которые начинаются и заканчиваются с {% и %} позволяют управлять логикой шаблона.
Примеры обработки итератора (например списка) в цикле:
<ul>
{% for user in users %}
<li><a href="{{ user.profile_url }}">{{ user.name }}</a></li>
{% endfor %}
</ul>
Или ветвление:
<div>
{% if age < 10 +%}
Привет
{% elif age >= 10 and < 20 }
Здравствуй
{% else %}
Здравствуйте
{% endif %}
</div>
Полный список доступных выражений можно найти в документации.
Фильтры
Одно из важных удобств при работе с Jinja заключается в том, что в систему шаблонов встроены фильтры. Даже базовых хватает для большого количества ситуаций.
Фильтры позволяют "перенаправить" переменную в функцию с помощью краткого синтаксиса.
{{ variable|filter }}
Например если вы хотите преобразовать строку в верхний регистр:
{{ name|upper }}
Список встроенных фильтров. С некоторыми мы еще познакомимся в лекциях.
Фильтры можно выстраивать в цепочку, результат выражения слева будет попадать на вход фильтру справа и так по цепочке.
{{ data | selectattr('name', '==', 'Jinja') | list | last }}
Например это выражение накладывает цепочку фильтров на переменную data:
selectattrполучает на вход итератор, делает проверку есть ли у элементов итератора атрибут name равный значению Jinjalistпреобразует входящее значение в списокlastоставляет только последний элемент из списка
Аналог этого кода коде был бы чем-то приблизительно таким:
data = ... # итератор
temp_list = []
for item in data:
if hasattr(item, 'name') and getattr(item, 'name') == 'Jinja':
temp_list.append(item)
if temp_list:
return_value = temp_list[-1]
Код с фильтрами гораздо изящнее.
Фильтры можно создавать самому и тем самым расширять возможности сайта.