Skip to content

Latest commit

 

History

History
426 lines (319 loc) · 13.3 KB

File metadata and controls

426 lines (319 loc) · 13.3 KB

Урок 5. Фильтры, теги if/for и тег url

Два синтаксических элемента DTL

В прошлом уроке мы познакомились с {{ переменная }} для вывода данных и мельком увидели {% 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 }}

Фильтр default

Очень полезный фильтр — подставляет значение по умолчанию, если переменная пустая или None:

{{ film.description|default:"Описание отсутствует" }}
{{ film.rating|default:0 }}

Фильтр safe

По умолчанию DTL экранирует HTML в переменных — защита от XSS. Если переменная содержит доверенный HTML, который нужно отрендерить:

{{ film.description|safe }}

Используй safe только для данных, которые точно не содержат пользовательского ввода.


Тег if

Условный тег работает так же, как 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

Тег {% for %} перебирает любой итерируемый объект: список, QuerySet, словарь.

{% for элемент in коллекция %}
    ...
{% endfor %}

Переменная forloop

Внутри цикла {% 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

{% 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 %}

Тег url

В уроке 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

Если циклы вложены, forloop относится к ближайшему {% for %}. Чтобы получить доступ к счётчику внешнего цикла — используй forloop.parentloop:

{% for genre in genres %}
    {% for film in genre.films %}
        {{ forloop.parentloop.counter }}.{{ forloop.counter }} {{ film.title }}
    {% endfor %}
{% endfor %}

Тег url и пространства имён

Если в проекте несколько приложений с маршрутами одного имени, {% url 'index' %} может сработать непредсказуемо. Решение — пространства имён (app_name), которые мы добавим в следующем уроке при настройке наследования шаблонов.


Вопросы

  1. В чём разница между {{ }} и {% %} в DTL?
  2. Что выведет {{ film.description|truncatechars:20|upper }}, если description = 'История о надежде и свободе'?
  3. Чем {% empty %} внутри {% for %} удобнее, чем связка {% if %} + {% for %}?
  4. Зачем использовать {% url %} вместо жёстко заданных путей в href?
  5. Что содержит forloop.first и когда это полезно?

Практическая задача

Тип: расширь проект

Улучши шаблон film_list.html.

Требования:

  1. Пронумеруй фильмы в списке — используй forloop.counter
  2. Если фильм вышел до 1990 года включительно — добавь рядом с названием пометку (Золотая классика)
  3. Если фильм вышел после 2000 года — добавь пометку (Современный)
  4. Описание каждого фильма выводи обрезанным до 30 символов. Если описания нет — выводи текст Нет описания
  5. Последний фильм в списке выдели отдельной пометкой ← последний в списке

Предыдущий урок | Следующий урок