Язык разметки шаблонов

Контекст — данные, используемые для отображения шаблона. Это словарь, значениями которого могут быть словари, которые в свою очередь обязаны быть контекстами (рекурсия!), списками или скалярами (строками, числами, булевыми значениями).

Синтаксис

Шаблоны состоят из разметки, переменных, выражений и управляющих конструкций. Переменные и выражения заключаются в двойные фигурные скобки {{ }}, управляющие конструкции — в {% %}.

Доступ к переменным контекста

Top-level переменные контекста доступны просто по их именам: шаблон {{ data }} в контексте {"data": "Hello!"} выведет Hello!.

Для доступа к элементам словаря может использоваться точка (dict.field) или квадратные скобки со строковым ключом (dict['field']). В случае, если dict не содержит ключа field, будет выведена пустая строка.

Доступ к элементам списка осуществляется через квадратные скобки с целочисленным ключом (list[0], list[-1] для доступа к последнему элементу списка). В случае, если индекс выходит за пределы списка, будет выброшено исключение.

Пример

Контекст

{
  "name": "John Doe",
  "news": [{
    "header": "Header",
    "content": "Content"
  }]
}

может быть выведен, например, с помощью такого шаблона:

{{ name }}
{{ news[0].header }}
{{ news[0]['content'] }}

Управляющие конструкции

For

Предназначен для итерации по спискам:

<ul>
{% for piece in news %}
  <li>{{ piece }}</li>
{% endfor %}
</ul>

Внутри цикла доступны следующие специальные переменные:

  • loop.index: номер текущей итерации цикла (считая от 1);
  • loop.index0: номер текущей итерации цикла (считая от 0);
  • loop.first: True, если цикл находится в первой итерации;
  • loop.last: True, если цикл находится в первой итерации;
  • loop.length: длина списка, по которому проходит цикл.

If

Обычный условный переход:

{% if kenny.sick %}
    Kenny is sick.
{% elif kenny.dead %}
    You killed Kenny!  You bastard!!!
{% else %}
    Kenny looks okay — so far
{% endif %}

Пустые строки, списки и словари расцениваются как ложное значение. Это полезно для проверки переменных на пустоту, например:

{% if users %}
<ul>
{% for user in users %}
    <li>{{ user.username }}</li>
{% endfor %}
</ul>
{% endif %}

В выражениях внутри if можно использовать операторы and, or и not, операторы сравнения >, <, <=, >=, ==, !=, а также круглые скобки:

{% for user in users %}
    {% if loop.length > 5 and x == y %}
        {{ user }}
    {% endif %}
{% endfor %}

Block

Определяет блок. Блоки могут быть вложенными. Например:

{% block head %}
  <link rel="stylesheet" href="style.css" />
  <title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}

Подробнее о том, зачем нужны блоки, написано в разделе Наследование.

Операторы

Внутри {{ }} могут находиться не только переменные, но и выражения с использованием переменных. В выражениях можно использовать следующие операторы:

  • ~: конкатенация строк. Например: {{ "Hello " ~ name ~"!" }};

  • (): вызов. Например: {{ wrap_href(link) }}. Список функций приведён в разделе Функции;

  • []: обращение к элементу списка (если ключ является целым числом) или к ключу словаря (если ключ является строкой). Например: {{ list[0] }};

  • |: применение фильтра. Список доступных фильтров см. ниже. Например: {{ href|safe }};

  • Арифметические операции:
    • +;
    • -;
    • *;
    • / (деление, {{ 1 / 2 }} равно 0.5);
    • // (целочисленное деление, {{ 20 // 7 }} равно 2);
    • % (вычисление остатка от деления);
    • ** (возведение в степень).

Фильтры

  • safe: отключает автоматическое экранирование;
  • capitalize: приводит первый символ строки к верхнему регистру; все остальные — к нижнему;
  • escape: преобразует символы &, <, >, ‘, и ” к их HTML-кодам;
  • length: возвращает длину списка;
  • lower: приводит строку к нижнему регистру;
  • upper: приводит строку к верхнему регистру.

Функции

  • super(): выводит содержимое родительского блока. См. раздел Наследование;
  • wrap_href(url, conversion_target=None): заменяет url на URL Mailtank-а, который регистрирует факт перехода по ссылке, а затем перенаправляет на url. Опциональный аргумент conversion_target позволяет задать строку, задающую цель конверсии; статистика по разным целям конверсии считается раздельно.
  • wrap_src(url): скачивает файл по адресу url и закачивает его в Unistorage, возвращая новый URL.

Наследование

Базовый шаблон выступает в роли “скелета” для всех своих дочерних шаблонов. Предполагается, что он содержит некоторую общую для них разметку, а также определяет блоки, которые дочерние шаблоны могут переопределять по собственному усмотрению.

Базовый шаблон может выглядеть следующим образом:

{% block header %}
  <h1>{% block title %}Рассылка!{% endblock %}</h1>
{% endblock %}
<div style="background: red">
  {% block body %}{% endblock %}
</div>
{% block footer %}
  &copy;
{% endblock %}

Дочернему шаблону достаточно лишь переопределить необходимые блоки:

{% block title %}Новости!{% endblock %}
{% block body %}
  {% for piece in news %} {{ piece }} {% endfor %}
{% endblock %}

Вывести содержимое родительского блока можно с помощью специального выражения {{ super() }}.

Ограничения на использование переменных

Существуют ограничения на то, как переменные могут использоваться в шаблоне.

  1. Если переменная используется как {{ var }}, считается, что она используется как строка;
  2. Если переменная var встречается в выражениях вида {{ var.field }}, {{ var[‘field’] }}, считается, что она используется как словарь;
  3. Если var встречается в цикле ({% for el in var %}) или выражении типа {{ var[0] }}, считается, что она используется как список.

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

Переменная не может быть использована в нескольких ролях одновременно. Так, следующий шаблон является некорректным, так как переменная var используется в нём в качестве списка и в качестве строки одновременно.

{% for el in var %}
  {{ el }}
{% endfor %}
{{ var }}

Не является корректным и такой шаблон, так как элементы списка xs в первом цикле используется как строки, а во втором — как словари:

{% for x in xs %}
  {{ x }}
{% endfor %}
{% for person in xs %}
  {{ person.name }}
{% endfor %}

Если базовый шаблон использует переменную x в роли, например, строки в некотором множестве блоков, и дочерний шаблон переопределяет все эти блоки без использования {{ super() }}, используя переменную x в другой роли — такая ситуация считается абсолютно нормальной.

Если базовый шаблон содержит разметку {% block body %} {{ xs }} {% endblock %}, то:

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

{% block body %}
  {% for x in xs %}
    {{ x }}
  {% endfor %}
{% endblock %}

корректен;

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

{% block body %}
  {{ super() }}
  {% for x in xs %}
    {{ x }}
  {% endfor %}
{% endblock %}

некорректен из-за наличия {{ super() }}.

Стоит учитывать, что система проверяет совместимость разметок (каждую с каждой) тем и тел всех вариантов и текстовой версии шаблона. То есть, нельзя использовать переменную x в качестве строки в одном варианте шаблона и в качестве списка — в другом.