В прошлом уроке мы познакомились с {{ переменная }} для вывода данных и мельком увидели {% for %}. Теперь разберём оба механизма DTL подробно.
В Django Template Language есть два вида конструкций:
{{ }}— вывод значения. Выводит переменную или результат применения фильтра.{% %}— тег. Управляет логикой шаблона: циклы, условия, включения, загрузка ресурсов.
Фильтр — это преобразование значения переменной прямо в шаблоне. Применяется через символ |:
{{ переменная|фильтр }}
{{ переменная|фильтр:аргумент }}Фильтры можно выстраивать в цепочку — каждый следующий применяется к результату предыдущего:
{{ title|lower|truncatechars:20 }}<!-- Привести к верхнему регистру -->
{{ film.title|upper }}
<!-- КРЁСТНЫЙ ОТЕЦ -->
<!-- Привести к нижнему регистру -->
{{ film.title|lower }}
<!-- крёстный отец -->
<!-- Сделать первую букву заглавной -->
{{ film.title|capfirst }}
<!-- Крёстный отец -->
<!-- Обрезать до N символов, добавить многоточие -->
{{ film.description|truncatechars:50 }}
<!-- История о надежде и свободе... -->
<!-- Обрезать до N слов -->
{{ film.description|truncatewords:10 }}
<!-- Длина строки или списка -->
{{ film.title|length }}
<!-- 14 --><!-- Форматировать число: 1000000 → 1,000,000 -->
{{ film.budget|floatformat:2 }}
<!-- 1000000.00 -->
<!-- Дата в нужном формате (появится в модуле 3, когда добавим DateField) -->
{{ film.release_date|date:"d.m.Y" }}
<!-- 14.03.1972 --><!-- Первый элемент списка -->
{{ films|first }}
<!-- Последний элемент -->
{{ films|last }}
<!-- Количество элементов -->
{{ films|length }}
<!-- Перемешать в случайном порядке -->
{{ films|random }}Очень полезный фильтр — подставляет значение по умолчанию, если переменная пустая или None:
{{ film.description|default:"Описание отсутствует" }}
{{ film.rating|default:0 }}По умолчанию DTL экранирует HTML в переменных — защита от XSS. Если переменная содержит доверенный HTML, который нужно отрендерить:
{{ film.description|safe }}Используй safe только для данных, которые точно не содержат пользовательского ввода.
Условный тег работает так же, как if в Python, но с синтаксисом DTL:
{% if условие %}
...
{% elif другое_условие %}
...
{% else %}
...
{% endif %}Каждый открывающий тег требует закрывающего — {% endif %}, {% endfor %} и т.д.
<!-- Проверка на истинность -->
{% if films %}
<p>В каталоге есть фильмы.</p>
{% else %}
<p>Каталог пуст.</p>
{% endif %}
<!-- Сравнение -->
{% if film.year > 2000 %}
<span>Современный фильм</span>
{% else %}
<span>Классика</span>
{% endif %}
<!-- Проверка на равенство -->
{% if film.rating == 10 %}
<span>⭐ Шедевр</span>
{% endif %}
<!-- Оператор in -->
{% if 'драма' in film.genres %}
<span>Драма</span>
{% endif %}
<!-- Логические операторы -->
{% if film.year > 1990 and film.rating > 8 %}
<span>Отличный современный фильм</span>
{% endif %}
{% if not film.description %}
<p>Описание скоро появится.</p>
{% endif %}Обновим шаблон film_list.html — добавим сообщение, если список пустой, и пометку для фильмов с высоким рейтингом:
<!-- films/templates/films/film_list.html -->
<h1>Каталог фильмов</h1>
{% if films %}
<p>Всего фильмов: {{ films|length }}</p>
<ul>
{% for film in films %}
<li>
{{ film.title }} ({{ film.year }})
{% if film.year < 2000 %}
— классика
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>В каталоге пока нет фильмов.</p>
{% endif %}Тег {% for %} перебирает любой итерируемый объект: список, QuerySet, словарь.
{% for элемент in коллекция %}
...
{% endfor %}Внутри цикла {% for %} доступна специальная переменная forloop с полезными атрибутами:
| Атрибут | Что содержит |
|---|---|
forloop.counter |
Номер итерации, начиная с 1 |
forloop.counter0 |
Номер итерации, начиная с 0 |
forloop.revcounter |
Обратный счётчик до конца |
forloop.first |
True для первого элемента |
forloop.last |
True для последнего элемента |
Полезно, когда нужно особым образом оформить первый элемент списка — например, добавить CSS-класс active к первому пункту навигации.
<ul>
{% for film in films %}
<li>
{{ forloop.counter }}. {{ film.title }}
{% if forloop.first %} — первый в списке{% endif %}
{% if forloop.last %} — последний в списке{% endif %}
</li>
{% endfor %}
</ul>{% empty %} выполняется, если коллекция пустая — лаконичная альтернатива связке {% if %} + {% for %}:
<ul>
{% for film in films %}
<li>{{ film.title }}</li>
{% empty %}
<li>Фильмы не найдены.</li>
{% endfor %}
</ul>{% for key, value in film.items %}
<p>{{ key }}: {{ value }}</p>
{% endfor %}В уроке 3 мы узнали, что reverse() строит URL по имени маршрута в Python-коде. В шаблонах для этого есть тег {% url %}:
{% url 'имя_маршрута' %}
{% url 'имя_маршрута' параметр1 параметр2 %}Без тега {% url %} ссылки в шаблонах выглядели бы так:
<!-- Жёстко закодированный URL — сломается при изменении маршрута -->
<a href="/films/">Каталог</a>
<a href="/films/{{ film.id }}/">{{ film.title }}</a>С тегом {% url %}:
<!-- URL строится по имени маршрута -->
<a href="{% url 'film_list' %}">Каталог</a>
<a href="{% url 'film_detail' film_id=film.id %}">{{ film.title }}</a>Если маршрут изменится в urls.py — ссылки во всех шаблонах обновятся автоматически.
Обновим все шаблоны, заменив жёстко заданные пути на {% url %}.
films/templates/films/index.html:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Сайт фильмов</title>
</head>
<body>
<nav>
<a href="{% url 'index' %}">Главная</a> |
<a href="{% url 'film_list' %}">Каталог</a> |
<a href="{% url 'about' %}">О сайте</a>
</nav>
<h1>{{ title }}</h1>
<p>Добро пожаловать на сайт фильмов!</p>
</body>
</html>films/templates/films/film_list.html:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Каталог фильмов</title>
</head>
<body>
<nav>
<a href="{% url 'index' %}">Главная</a> |
<a href="{% url 'film_list' %}">Каталог</a> |
<a href="{% url 'about' %}">О сайте</a>
</nav>
<h1>Каталог фильмов</h1>
{% if films %}
<p>Всего фильмов: {{ films|length }}</p>
<ul>
{% for film in films %}
<li>
<a href="{% url 'film_detail' film_id=film.id %}">
{{ film.title }}
</a>
({{ film.year }})
{% if film.year < 2000 %}— классика{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>В каталоге пока нет фильмов.</p>
{% endif %}
</body>
</html>films/templates/films/film_detail.html:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>{{ film.title }}</title>
</head>
<body>
<nav>
<a href="{% url 'index' %}">Главная</a> |
<a href="{% url 'film_list' %}">Каталог</a> |
<a href="{% url 'about' %}">О сайте</a>
</nav>
<h1>{{ film.title }}</h1>
<p>Год выпуска: {{ film.year }}</p>
<p>{{ film.description|default:"Описание отсутствует." }}</p>
<a href="{% url 'film_list' %}">← Назад к каталогу</a>
</body>
</html>DTL требует явного закрытия каждого блочного тега. Пропущенный {% endif %} или {% endfor %} вызовет TemplateSyntaxError:
<!-- Забыли закрыть for -->
{% for film in films %}
<li>{{ film.title }}</li>
<!-- Правильно -->
{% for film in films %}
<li>{{ film.title }}</li>
{% endfor %}Если циклы вложены, forloop относится к ближайшему {% for %}. Чтобы получить доступ к счётчику внешнего цикла — используй forloop.parentloop:
{% for genre in genres %}
{% for film in genre.films %}
{{ forloop.parentloop.counter }}.{{ forloop.counter }} {{ film.title }}
{% endfor %}
{% endfor %}Если в проекте несколько приложений с маршрутами одного имени, {% url 'index' %} может сработать непредсказуемо. Решение — пространства имён (app_name), которые мы добавим в следующем уроке при настройке наследования шаблонов.
- В чём разница между
{{ }}и{% %}в DTL? - Что выведет
{{ film.description|truncatechars:20|upper }}, еслиdescription = 'История о надежде и свободе'? - Чем
{% empty %}внутри{% for %}удобнее, чем связка{% if %}+{% for %}? - Зачем использовать
{% url %}вместо жёстко заданных путей вhref? - Что содержит
forloop.firstи когда это полезно?
Тип: расширь проект
Улучши шаблон film_list.html.
Требования:
- Пронумеруй фильмы в списке — используй
forloop.counter - Если фильм вышел до 1990 года включительно — добавь рядом с названием пометку
(Золотая классика) - Если фильм вышел после 2000 года — добавь пометку
(Современный) - Описание каждого фильма выводи обрезанным до 30 символов. Если описания нет — выводи текст
Нет описания - Последний фильм в списке выдели отдельной пометкой
← последний в списке