Django Book: наследование в шаблонах

Автор: | 18/02/2015

django_logo_2Предыдущая часть.

Наши предыдущие примеры шаблонов были небольшими фрагментами HTML-кода, однако в реальной ситуации вы будете использовать Django для создания больших страниц. Отсюда возникает один из наиболее существенных вопросов веб-разработки – как уменьшить количество повторяющегося и избыточного кода в общих частях страниц, таких как навигация по сайту?

Классическое решение этой проблемы заключается в использовании инклюдов (includes), или “вложений” – особых директив в коде HTML, которые позволяют включать одну HTML-страницу в код другой. Действительно, Django поддерживает такой подход с помощью тега шаблона {% include %}, который мы рассмотрели в статье Django Book: глава 2 – загрузка шаблонов. Но имеется и другой, более предпочтительный, способ решения, который называется Наследование шаблонов.

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

Давайте рассмотрим пример реализации этого, создав более полный шаблон для нашего преставления current_datetime, отредактировав файл timetemplate.html:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>The current time</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    <p>It is now {{ current_date }}.</p>

    <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

Он выглядит достаточно хорошо, но что будет, если мы захотим создать шаблон для другого представления, например – hours_ahead из статьи Django Book: третье представление – динамические URL-ы? Если мы опять хотим создать красивый и валидный HTML-шаблон – нам придётся делать что-то вроде такого:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>Future time</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>

    <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

Очевидно, что мы дублируем много HTML-кода. Представьте, что у нас есть типичный сайт, с панелью навигации, несколько CSS, возможно JavaScript – нам придётся добавлять весь избыточный код в каждый шаблон.

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

Например, вы могли бы сохранить заголовок страницы в шаблоне с именем header.html:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">

<html lang="en">
<head>

А окончание страницы – в файле footer.html:

    <hr>
    <p>Thanks for visiting my site.</p>
</body>
</html>

С использованием инклюдов такое реализовать очень просто. Однако, тут есть несколько проблем. Например, обе страницы используют заголовок страницы –  <h1>My helpful timestamp site</h1>, но он не может быть записан в header.html, так как <title> на страницах разный. Если мы включим <h1> в заголовок – нам придётся включить туда и <title>, и это не позволит нам настраивать страницы.

Система наследования в шаблонах Django решает эту проблему. Вместо определения того, как части страницы будут общими – вы можете определить какие из них будут разными.

Первый шаг – определить базовый шаблон – скелет страницы, который потом будет заполнен дочерними шаблонами. Вот пример нашего текущего шаблона:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <h1>My helpful timestamp site</h1>
    {% block content %}{% endblock %}
    {% block footer %}
    <hr>
    <p>Thanks for visiting my site.</p>
    {% endblock %}
</body>
</html>

Этот шаблон мы назовём  base.html, и он описывает простой HTML-скелет документа, который мы будем использовать для всех страниц сайта. Далее уже наши дочерние шаблоны будут перезаписывать, добавлять или удалять содержимое блоков. Сохраните его в вашей директории templates с именем base.html.

Тут мы использовали тег шаблона, который вы раньше не встречали – {% block %}. Всё, что он делает – это указывает системе шаблонов, что дочерний шаблон может перезаписать этот участок шаблона.

Теперь, мы можем отредактировать наш шаблон timetemplate.html, что бы использовать эту возможность:

{% extends "base.html" %}

{% block title %}The current time{% endblock %}

{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}

Теперь, давайте создадим шаблон для представления hours_ahead (мы предлагаем вам самим изменить само представление, что бы оно использовало систему шаблонов, вместо кода непосредственно в коде представления). Вот как он может выглядеть:

{% extends "base.html" %}

{% block title %}Future time{% endblock %}

{% block content %}
<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
{% endblock %}

Правда – замечательно? Каждый шаблон только свой уникальный код, больше нет никакой избыточности. Если вам необходимо сделать изменения, касающиеся всего сайта – достаточно отредактировать файл base.html, и все шаблоны немедленно их отобразят.

Вот как это работает. Когда загружается шаблон timetemplate.html, система шаблонов встречает тег {% extends %}, который означает что это дочерний шаблон. Система сразу же подгружает родительский шаблон, в данном случае это base.html.

Далее, система находит три тега {% block %} в шаблоне base.html, и заменяет их значение блоками из дочерней страницы. Таким образом, мы заменяем блоки {% block title %}  и {% block content %}.

Заметьте, что так как дочерний шаблон не определяет блок footer – система шаблонов использует значение из родительского шаблона. Содержимое блока {% block %} в родительском шаблоне всегда используется как “запасной”, если не указано ничего в дочернем.

Наследование не затрагивает контекст шаблона. Другими словами, каждый шаблон в дереве наследования будет иметь доступ к переменным вашего шаблона их контекста.

Вы можете использовать любое количество уровней наследования. Обычно используют трёх-уровневый подход:

  1. Базовый шаблон base.html, который описывает общий вид вашего сайта. Он меняется редко – или даже вообще никогда.
  2. Создаются base_SECTION.html шаблоны, один для каждой “секции” сайта (например – base_photos.html и base_forum.html). Эти шаблоны дополняют base.html и содержат в себе специфичные для раздела стили и дизайн.
  3. Создаются отдельные шаблоны для каждого типа страницы, таких как форум или фотогалерея, которые дополняют шаблон секции.

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

Вот несколько указаний для работы с наследованием шаблонов:

  • При использовании тега {% extends %} – он должен быть первым тегом в шаблоне. Иначе наследование не будет работать.
  • Как правило, чем больше тегов {% block %} в базовом шаблоне – тем лучше. Помните, что дочерние шаблоны не обязательно должны заполнять все родительские блоки, поэтому вы можете создать их заранее в необходимых местах и количестве, а затем заполнять по мере необходимости из дочерних шаблонов.
  • Если вы видите, что повторяете один и тот же код во многих шаблонах – возможно вам стоит переместить этот код в блок {% block %} родительского шаблона.
  • Если вам необходимо использовать код из блока родительского шаблона – используйте тег {{ block.super }}, который является особой переменной, позволяющей обратиться к родительскому шаблону. Это может быть полезно, если вы хотите дополнить родительских блок, а не полностью его перезаписывать.
  • Нельзя использовать тег {% block %} с одним и тем же именем несколько раз в одном шаблоне. Это ограничение вызвано тем, что блоки работают  в “обоих направлениях”. Т.е., тег {% block %} не только указывает место, в котором будет располагаться содержимое, но и выполняет его наполнение в родительском шаблоне. Поэтому, если у вас будет два блока с одним именем в шаблоне – родительский шаблон просто не будет знать, какой из них необходимо использовать.
  • Шаблон, который вы передаёте с помощью {% extends %} загружается используя тот же метод, который используется get_template(). Таким образом, имя шаблона добавляется к параметру TEMPLATE_DIRS.
  • В большинстве случаев, аргументом к {% extends %} будет строка, но вы так же можете использовать переменную, если не знаете имя родильского шаблона до момента запуска. Это позволяет вам делать классные и динамические сайты.

Конец второй главы.

Третья глава

Оглавление