Перейти к содержанию

Введение в шаблоны

Шаблоны

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 равный значению Jinja
  • list преобразует входящее значение в список
  • 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]

Код с фильтрами гораздо изящнее.

Фильтры можно создавать самому и тем самым расширять возможности сайта.