Посмотри на шаблоны, которые мы создали в прошлых уроках. В каждом из них повторяется одно и то же:
<!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>
...
</body>
</html>Навигация, структура HTML, подключение стилей — это одинаково на каждой странице. Если нужно добавить пункт в меню или изменить структуру — придётся редактировать каждый файл отдельно.
Django решает это через наследование шаблонов: один базовый шаблон определяет общую структуру страницы, дочерние шаблоны наследуют её и заполняют только изменяемые части.
Создадим базовый шаблон в общей папке проекта — templates/base.html:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Сайт фильмов{% endblock %}</title>
</head>
<body>
<header>
<nav>
<a href="{% url 'index' %}">Главная</a> |
<a href="{% url 'film_list' %}">Каталог</a> |
<a href="{% url 'about' %}">О сайте</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2024 Сайт фильмов</p>
</footer>
</body>
</html>Ключевая конструкция — {% block %}:
{% block имя_блока %}
содержимое по умолчанию (необязательно)
{% endblock %}Блок — это именованная область, которую дочерний шаблон может переопределить. Блоков может быть сколько угодно. В нашем базовом шаблоне их два:
title— заголовок вкладки браузера, с дефолтным текстомСайт фильмовcontent— основное содержимое страницы, пустое по умолчанию
Дочерний шаблон начинается с тега {% extends %} и содержит только те блоки, которые нужно переопределить:
{% extends 'base.html' %}
{% block title %}Каталог фильмов{% endblock %}
{% block content %}
<h1>Каталог фильмов</h1>
<p>Здесь будет список фильмов.</p>
{% endblock %}Правила наследования:
{% extends %}должен быть первой строкой шаблона — до любого HTML и пробелов- Всё, что написано вне блоков в дочернем шаблоне — игнорируется
- Если блок не переопределён — используется содержимое из базового шаблона
Перепишем все шаблоны приложения.
<!-- films/templates/films/index.html -->
{% extends 'base.html' %}
{% block title %}Главная — Сайт фильмов{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
<p>Добро пожаловать на сайт фильмов!</p>
<a href="{% url 'film_list' %}">Перейти к каталогу →</a>
{% endblock %}<!-- films/templates/films/film_list.html -->
{% extends 'base.html' %}
{% block title %}Каталог фильмов{% endblock %}
{% block content %}
<h1>Каталог фильмов</h1>
{% for film in films %}
<p>
{{ forloop.counter }}.
<a href="{% url 'film_detail' film_id=film.id %}">{{ film.title }}</a>
({{ film.year }})
<br>
{{ film.description|default:"Нет описания"|truncatechars:80 }}
</p>
{% empty %}
<p>В каталоге пока нет фильмов.</p>
{% endfor %}
{% endblock %}<!-- films/templates/films/film_detail.html -->
{% extends 'base.html' %}
{% block title %}{{ film.title }} — Сайт фильмов{% endblock %}
{% block content %}
<h1>{{ film.title }}</h1>
<p>Год выпуска: {{ film.year }}</p>
<p>{{ film.description|default:"Описание отсутствует." }}</p>
<a href="{% url 'film_list' %}">← Назад к каталогу</a>
{% endblock %}Посмотри, насколько чище стали шаблоны. Вся HTML-обвязка живёт в одном месте — base.html.
Иногда нужно не заменить содержимое блока, а дополнить его. Для этого используется {{ block.super }} — он вставляет содержимое блока из родительского шаблона:
{% extends 'base.html' %}
{% block title %}{{ block.super }} — Каталог{% endblock %}Если в base.html блок title содержит Сайт фильмов, результат будет: Сайт фильмов — Каталог.
Это полезно для блоков со скриптами или стилями — добавить что-то к базовому набору, не удаляя его:
{% block extra_css %}
{{ block.super }}
<link rel="stylesheet" href="...film-detail-styles.css">
{% endblock %}Наследование может быть многоуровневым. Например, для раздела администрирования можно создать промежуточный шаблон:
base.html
└── base_films.html ← добавляет боковую панель с жанрами
├── film_list.html
└── film_detail.html
<!-- films/templates/films/base_films.html -->
{% extends 'base.html' %}
{% block content %}
<div class="layout">
<aside>
<h3>Жанры</h3>
<ul>
<li><a href="#">Драма</a></li>
<li><a href="#">Комедия</a></li>
<li><a href="#">Триллер</a></li>
</ul>
</aside>
<div class="main-content">
{% block main_content %}{% endblock %}
</div>
</div>
{% endblock %}<!-- films/templates/films/film_list.html -->
{% extends 'films/base_films.html' %}
{% block title %}Каталог фильмов{% endblock %}
{% block main_content %}
<h1>Каталог фильмов</h1>
...
{% endblock %}Глубокую вложенность без необходимости делать не стоит — три уровня это обычно предел разумного.
Тег {% include %} решает другую задачу: он вставляет один шаблон внутрь другого. В отличие от наследования — это не структурный механизм, а способ переиспользовать небольшие повторяющиеся фрагменты.
Хороший кандидат для {% include %} — карточка фильма. Сейчас она выводится в одном месте, но когда появятся страница режиссёра и страница поиска, одна и та же карточка понадобится в нескольких шаблонах.
Создадим фрагмент films/templates/films/includes/film_card.html:
<!-- films/templates/films/includes/film_card.html -->
<div class="film-card">
<h3>
<a href="{% url 'film_detail' film_id=film.id %}">{{ film.title }}</a>
</h3>
<p>Год: {{ film.year }}</p>
<p>{{ film.description|default:"Нет описания"|truncatechars:80 }}</p>
</div>Подключаем в film_list.html:
{% extends 'base.html' %}
{% block title %}Каталог фильмов{% endblock %}
{% block content %}
<h1>Каталог фильмов</h1>
{% for film in films %}
{% include 'films/includes/film_card.html' %}
{% empty %}
<p>В каталоге пока нет фильмов.</p>
{% endfor %}
{% endblock %}Переменная film из цикла доступна внутри включаемого шаблона автоматически — он видит весь контекст родительского шаблона.
Можно передать дополнительные переменные через with:
{% include 'films/includes/film_card.html' with show_description=False %}<!-- film_card.html -->
<div class="film-card">
<h3><a href="{% url 'film_detail' film_id=film.id %}">{{ film.title }}</a></h3>
<p>Год: {{ film.year }}</p>
{% if show_description %}
<p>{{ film.description|default:"Нет описания" }}</p>
{% endif %}
</div>Чтобы включаемый шаблон видел только переданные переменные и не имел доступа к контексту родителя — добавь only:
{% include 'films/includes/film_card.html' with film=film only %}В уроке 5 мы упоминали, что тег {% url %} может вести себя непредсказуемо, если в разных приложениях есть маршруты с одинаковыми именами. Сейчас — самое время это исправить.
Добавляем app_name в файл маршрутов приложения:
# films/urls.py
from django.urls import path
from . import views
app_name = 'films' # <- пространство имён
urlpatterns = [
path('', views.index, name='index'),
path('about/', views.about, name='about'),
path('films/', views.film_list, name='film_list'),
path('films/<int:film_id>/', views.film_detail, name='film_detail'),
path('films/add/', views.add_film, name='add_film'),
path('films/search/', views.search_film, name='search_film'),
path('directors/<int:director_id>/', views.director_detail, name='director_detail'),
]Теперь в шаблонах и в Python-коде к маршрутам обращаемся через приложение:имя:
<!-- В шаблонах -->
<a href="{% url 'films:index' %}">Главная</a>
<a href="{% url 'films:film_detail' film_id=film.id %}">{{ film.title }}</a># В представлениях
from django.urls import reverse
reverse('films:film_list')
redirect('films:film_list')Обновим base.html с учётом пространства имён:
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Сайт фильмов{% endblock %}</title>
</head>
<body>
<header>
<nav>
<a href="{% url 'films:index' %}">Главная</a> |
<a href="{% url 'films:film_list' %}">Каталог</a> |
<a href="{% url 'films:about' %}">О сайте</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2024 Сайт фильмов</p>
</footer>
</body>
</html>Любой символ до {% extends %} — включая пустую строку или пробел — вызовет ошибку или непредсказуемое поведение:
<!-- Пробел или пустая строка перед extends -->
{% extends 'base.html' %}
<!-- Правильно — extends первой строкой, без отступов -->
{% extends 'base.html' %}В дочернем шаблоне весь HTML вне блоков просто не попадёт в итоговую страницу. Это частая ошибка, когда переносят шаблон на наследование:
{% extends 'base.html' %}
<p>Этот текст никогда не появится на странице.</p> <!-- вне блока -->
{% block content %}
<p>А этот появится.</p> <!-- внутри блока -->
{% endblock %}Каждый {% include %} — это загрузка и рендеринг отдельного файла. Если включать шаблоны внутри цикла с сотнями элементов, это заметно замедлит страницу. В таких случаях лучше вынести содержимое карточки прямо в цикл или использовать кэширование (модуль 8).
filmsite/
├── templates/
│ └── base.html ← общий макет проекта
└── films/
└── templates/
└── films/
├── index.html ← наследует base.html
├── about.html ← наследует base.html
├── film_list.html ← наследует base.html
├── film_detail.html ← наследует base.html
└── includes/
└── film_card.html ← переиспользуемый фрагмент
- В чём принципиальная разница между
{% extends %}и{% include %}? - Что произойдёт, если в дочернем шаблоне написать HTML вне блоков
{% block %}? - Для чего нужен
{{ block.super }}и в каком случае он полезен? - Зачем нужен
app_nameвurls.py? Что изменится в синтаксисе тега{% url %}? - Включаемый шаблон через
{% include %}— имеет ли он доступ к переменным контекста родительского шаблона?
Тип: переведи
Сейчас все шаблоны проекта дублируют HTML-структуру страницы. Переведи их на систему наследования.
Требования:
- Создай базовый шаблон
templates/base.htmlс блокамиtitleиcontent. Добавь в него навигацию с тремя ссылками: главная, каталог, о сайте — используй{% url %}с пространством имёнfilms: - Добавь
app_name = 'films'вfilms/urls.py - Переведи шаблоны
index.html,film_list.html,film_detail.html,about.htmlна наследование отbase.html - Вынеси карточку фильма (название-ссылка, год, обрезанное описание) в отдельный фрагмент
films/includes/film_card.htmlи подключи его вfilm_list.htmlчерез{% include %}
После выполнения задания все страницы должны выглядеть так же, как раньше, но HTML-структура теперь определяется в одном файле.