Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Шаблоны: DTL
templates_language

Шаблоны: DTL

DTL, наследование, custom tags и template partials Django 6.0

Django Template Language

Шаблон превращает подготовленный контекст в HTML, письмо, XML или другой текст. DTL намеренно не исполняет произвольный Python: сложная выборка, права и бизнес-правила должны быть решены до рендеринга.

Основной синтаксис актуален для Django 5.2 LTS и 6.0. Template partials отдельно помечены как возможность Django 6.0.

#Как Django находит шаблон

Типовая конфигурация ищет проектные шаблоны из DIRS и шаблоны установленных приложений при APP_DIRS=True:

TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [BASE_DIR / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], }, }, ]

Имена внутри приложения помещают в собственную папку: blog/templates/blog/post_list.html. Иначе два файла templates/index.html разных приложений конкурируют по порядку загрузчиков.

Загрузчик возвращает скомпилированный объект шаблона. Начиная с Django 4.1, cached template loader включается автоматически, если OPTIONS["loaders"] не задан. Ручная настройка нужна для нестандартных loaders, а отдельной встроенной команды collecttemplates в Django нет.

#Контекст и точка

View передаёт данные словарём:

return render(request, "blog/post_detail.html", { "post": post, "comments": comments, })

DTL использует {{ post.title }}. Для каждой части после точки движок последовательно пробует:

  1. ключ mapping;
  2. атрибут или метод;
  3. числовой индекс.

Callable без обязательных аргументов может быть вызван автоматически. Поэтому {{ post.comments.count }} способен незаметно выполнить SQL. Шаблонный цикл по непредзагруженным связям создаёт N+1 точно так же, как Python-цикл во view. Подготовьте select_related(), prefetch_related() и annotate() заранее.

Отсутствующая переменная по умолчанию отображается пустой строкой. Это удобно для пользователя, но маскирует опечатки. Тестируйте содержимое важных страниц, а не только статус 200.

#Теги управляют выводом

{% if post.is_published %} <span>Опубликовано</span> {% endif %} {% for comment in comments %} <article>{{ comment.text|linebreaksbr }}</article> {% empty %} <p>Комментариев пока нет.</p> {% endfor %}

У forloop есть counter, first, last и parentloop. Длинные условия и вычисления лучше заменить понятным значением контекста: can_edit, comments_count, status_label.

URL строится по имени:

<a href="{% url 'blog:post_detail' slug=post.slug %}"> {{ post.title }} </a>

Так изменение пути не требует поиска жёстко заданных строк по всем шаблонам.

#Фильтры меняют представление, не данные

{{ post.published_at|date:"d.m.Y H:i" }} {{ post.body|truncatewords:40 }} {{ amount|floatformat:2 }} {{ value|default_if_none:"—" }}

default срабатывает для любого falsy-значения, включая 0, пустую строку и False. default_if_none заменяет только None. Это различие важно для цены, счётчика и boolean-поля.

Встроенный pluralize рассчитан на простое разделение «один/не один» и не выражает три русские числовые формы строкой из трёх окончаний. Для локализованного текста используйте gettext:

{% load i18n %} {% blocktranslate count counter=comments_count %} {{ counter }} комментарий {% plural %} {{ counter }} комментариев {% endblocktranslate %}

В .po-каталоге переводчик задаёт формы согласно plural rules русского языка.

#Автоэкранирование и границы XSS-защиты

В HTML-шаблоне переменная экранируется по умолчанию:

{{ user_input }}

Символы <, >, &, кавычки не станут HTML-разметкой. Но автоэкранирование не является контекстным анализатором JavaScript, CSS и URL. Не вставляйте пользовательское значение внутрь сырого script:

{# небезопасная архитектура #} <script>const name = "{{ user_input }}";</script>

Для передачи JSON используйте json_script и читайте содержимое элемента из JavaScript:

{% load static %} {{ chart_data|json_script:"chart-data" }} <script src="{% static 'charts.js' %}" defer></script>

safe, mark_safe и {% autoescape off %} отключают важную защиту. Они допустимы только для HTML, чья безопасность установлена на доверенной границе. Markdown от пользователя сначала ограничивают и санитизируют разрешающим списком тегов и атрибутов; простое слово «trusted» в имени переменной ничего не гарантирует.

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

#Наследование задаёт каркас страницы

{# templates/base.html #} <!doctype html> <html lang="ru"> <head> <meta charset="utf-8"> <title>{% block title %}Блог{% endblock %}</title> {% block extra_head %}{% endblock %} </head> <body> {% include "partials/navigation.html" %} <main>{% block content %}{% endblock %}</main> {% block scripts %}{% endblock %} </body> </html>
{% extends "base.html" %} {% load static %} {% block title %}Публикации — {{ block.super }}{% endblock %} {% block content %} {% for post in posts %} {% include "blog/_post_card.html" with post=post only %} {% empty %} <p>Публикаций пока нет.</p> {% endfor %} {% endblock %}

extends должен быть первым template tag, но не обязательно первой физической строкой файла: перед ним допустим текст, хотя лишний вывод обычно не нужен. block.super добавляет содержимое родителя. include ... only передаёт фрагменту явный минимальный контекст и делает зависимость видимой.

load действует только в текущем шаблоне. Библиотека, загруженная в дочернем файле, не становится доступной в родительском, и наоборот. Каждый файл должен загружать используемые им нестандартные теги.

#Новое в Django 6.0: template partials

Partial — именованный фрагмент внутри того же файла:

{% partialdef post-card %} <article id="post-{{ post.pk }}"> <h2> <a href="{% url 'blog:post_detail' slug=post.slug %}"> {{ post.title }} </a> </h2> </article> {% endpartialdef %} {% for post in posts %} {% partial post-card %} {% endfor %}

Partial видит текущий контекст и может использоваться несколько раз. Его можно загрузить отдельно по имени template.html#partial-name:

return render( request, "blog/post_list.html#post-card", {"post": post}, )

Это полезно для HTML-фрагмента, который обновляется отдельным запросом. В Django 5.2 встроенных partialdef/partial нет; если код должен работать на 5.2 и 6.0, используйте include или осознанно подключите совместимый сторонний пакет.

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

#Context processors — глобальные зависимости шаблона

Context processor — функция request -> dict, зарегистрированная в TEMPLATES[...]["OPTIONS"]["context_processors"]. Она применяется при рендеринге с RequestContext, в частности через shortcut render().

def product_context(request): return { "support_email": "support@example.com", "release_channel": "stable", }

Не выполняйте там тяжёлый запрос на каждой странице. Данные, нужные одному экрану, передавайте из его view; стабильную глобальную конфигурацию можно кэшировать. Явный контекст view имеет приоритет с учётом порядка применения processors в RequestContext, поэтому не переиспользуйте неоднозначные имена.

#Свои filters и tags

Библиотека находится в templatetags установленного приложения:

blog/ └── templatetags/ ├── __init__.py └── blog_ui.py
from django import template from django.template.defaultfilters import stringfilter register = template.Library() @register.filter @stringfilter def initials(value): return "".join(part[0].upper() for part in value.split() if part) @register.simple_tag(takes_context=True) def active_class(context, view_name): match = context["request"].resolver_match return "is-active" if match and match.view_name == view_name else ""
{% load blog_ui %} <a class="{% active_class 'blog:list' %}" href="{% url 'blog:list' %}"> Публикации </a>

Фильтр должен быть предсказуемым и не обращаться к базе. Tag, который возвращает HTML, строят через format_html(), а не через mark_safe() над строкой с внешними значениями. Inclusion tag с запросом к базе скрывает стоимость от view и затрудняет оптимизацию — использовать его стоит только с ясным кэшированием и тестом числа запросов.

После добавления нового модуля templatetags может понадобиться перезапуск development server, потому что библиотека обнаруживается при загрузке приложений.

#Кэш фрагмента

{% load cache i18n %} {% get_current_language as LANGUAGE_CODE %} {% cache 600 public_sidebar LANGUAGE_CODE %} {% include "partials/public_sidebar.html" only %} {% endcache %}

Ключ должен включать каждую переменную, влияющую на HTML: язык, tenant, роль или пользователя. Нельзя кэшировать персональные данные под общим ключом. Источник данных всё равно готовит view: cache tag не исправляет N+1 внутри шаблона.

#Legacy и актуальность

DTL, наследование, include, custom tags и автоэкранирование актуальны. С Django 4.1 cached loader обычно уже включён автоматически; старые инструкции с обязательной ручной production-настройкой избыточны, если нет custom loaders.

Template partials появились в Django 6.0. В legacy можно встретить сторонний django-template-partials с похожей идеей; при миграции сверяйтесь с его руководством и не удаляйте пакет, пока все шаблоны не переведены на встроенный синтаксис.

#Документация

  • Django Template Language
  • Built-in tags and filters
  • Custom template tags and filters
  • Template loaders

Далее: Формы и Виджеты