Шаблоны DTL: переменные, теги, фильтры

Шаблон — это HTML с «дырками» для данных. Django Template Language (DTL) подставляет переменные и добавляет простую логику, не пуская Python в разметку.
Суть: шаблон получает context (словарь данных) из view. Переменные подставляются через {{ }}, логика — через теги {% %}, форматирование — через фильтры. Логику намеренно держат минимальной.

Переменные и context

View передаёт в шаблон словарь — context. Шаблон достаёт из него значения двойными фигурными скобками. Точка работает как доступ к атрибуту, ключу словаря или элементу списка:

<h1>{{ post.title }}</h1>
<p>Автор: {{ post.author.name }}</p>
<p>Просмотров: {{ post.views }}</p>

Связь view и шаблона:

views.py                         template.html
─────────                        ─────────────
context = {                      {{ post.title }}
  "post": post_obj,      ──────▶ {{ post.author.name }}
}                                {% for tag in post.tags.all %}
render(request,                    {{ tag.name }}
  "post.html", context)          {% endfor %}

Теги: логика в шаблоне

Теги в фигурных скобках с процентами добавляют управляющие конструкции. Главные — if и for:

{% if posts %}
  <ul>
  {% for post in posts %}
    <li>{{ post.title }} — {{ post.created_at|date:"d.m.Y" }}</li>
  {% empty %}
    <li>Постов пока нет</li>
  {% endfor %}
  </ul>
{% else %}
  <p>Список пуст</p>
{% endif %}

Обратите внимание на {% empty %} — удобный блок, который покажется, если цикл пуст.

Фильтры: форматирование

Фильтр через вертикальную черту преобразует значение прямо в шаблоне: {{ name|upper }}, {{ text|truncatewords:30 }}, {{ price|floatformat:2 }}, {{ date|date:"d.m.Y" }}, {{ value|default:"—" }}. Фильтры можно соединять цепочкой.

Как это работает под капотом

Движок шаблонов разбирает текст, находит токены {{ }} и {% %}, и строит дерево узлов, которое затем «рендерит» с подстановкой context. По сути это подстановка значений по шаблону строки и применение функций-фильтров — языко-независимая логика. Вот её модель:

# Попробуй сам ▶ — мини-движок шаблонов с фильтрами
import re

filters = {
    "upper":  lambda v, _: str(v).upper(),
    "default": lambda v, arg: v if v else arg,
    "truncate": lambda v, arg: str(v)[:int(arg)] + "…",
}

def render(template, context):
    def replace(match):
        expr = match.group(1).strip()          # напр. "name|upper"
        parts = expr.split("|")
        value = context.get(parts[0].strip(), "")
        for f in parts[1:]:                     # применяем фильтры
            name, _, arg = f.strip().partition(":")
            value = filters[name](value, arg.strip('"'))
        return str(value)
    return re.sub(r"{{(.*?)}}", replace, template)

ctx = {"name": "django", "bio": "Высокоуровневый веб-фреймворк", "nick": ""}
tpl = "Имя: {{ name|upper }}; Био: {{ bio|truncate:10 }}; Ник: {{ nick|default:\"—\" }}"
print(render(tpl, ctx))

Django делает это мощнее (есть теги, наследование, автоэкранирование), но ядро — ровно такая подстановка значений и применение фильтров.

Автоэкранирование и безопасность

Важнейшая деталь: DTL по умолчанию экранирует HTML в переменных. Если в {{ comment }} придёт <script>, он отобразится как текст, а не выполнится. Это встроенная защита от XSS. Отключать её (|safe) можно только для данных, которым вы абсолютно доверяете.

Частые ошибки

  • Тащить сложную логику в шаблон. DTL намеренно ограничен — вычисления делайте во view.
  • Вызывать метод со скобками. В шаблоне пишут post.tags.all без ().
  • Бездумно применять |safe. Это открывает дыру для XSS.
  • Забыть {% endfor %}/{% endif %}. Парные теги обязательно закрывают.

Best practices

  • Готовьте данные во view, в шаблоне — только отображение.
  • Полагайтесь на автоэкранирование; |safe — лишь для доверенного HTML.
  • Используйте {% empty %} для пустых списков и |default для пустых значений.
  • Форматируйте даты и числа фильтрами, а не в Python.

Итоги

Шаблон — это HTML с подстановкой данных из context. Переменные через {{ }}, логика через {% %}, форматирование через фильтры. DTL намеренно прост и по умолчанию экранирует HTML, защищая от XSS. Дальше избавимся от дублирования разметки через наследование шаблонов.

Проверьте себя
1. Почему в шаблоне пишут post.tags.all, а не post.tags.all()?
AСкобки запрещены в HTML
BDTL сам вызывает методы без скобок при доступе через точку
CЭто ускоряет рендеринг
Dall — это атрибут, а не метод
2. Что по умолчанию делает DTL с HTML-тегами внутри переменной?
AВыполняет их
BЭкранирует, отображая как текст — это защита от XSS
CУдаляет переменную
DНичего особенного