Шаблоны 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. Дальше избавимся от дублирования разметки через наследование шаблонов.