Наследование шаблонов Django
Рассмотренные до сих пор примеры шаблонов представляли собой крохотные фрагменты HTML, но в настоящих приложениях с помощью системы шаблонов Django создаются полномасштабные HTML-страницы. В результате встает типичный для веб-разработки вопрос: как устранить дублирование общих областей, например, встречающейся на всех страницах сайта области навигации?
Классически эта проблема решалась с помощью включения на стороне сервера, то есть размещения на HTML-странице директив, требующих включения других страниц. Как было описано выше, Django поддерживает такой подход с помощью шаблонного тега {% include %}. Но предпочтительным является более элегантное решение, называемое наследованием шаблонов.
Смысл механизма наследования шаблонов в том, чтобы создать базовый «шаблон-скелет», который содержит общие части сайта и определяет «блоки», переопределяемые в дочерних шаблонах.
Рассмотрим, как это делается, на примере более полного шаблона для нашего представления current_datetime, для чего отредактируем файл
current_datetime.html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//ENM>
<html lang="ru">
<head>
<Ш1е>Текущее время<ДШе> </head> <body>
<h 1 >Мой сайт точного времени</М>
<р>Сейчас {{ current_date >>.</р>
<hr>
<р>Спасибо, что посетили мой сайт.</р> </body> </html>
Вроде бы неплохо, но что если мы захотим создать шаблон еще для одного представления, например, hours_ahead из главы 3? Чтобы получить корректный и полный HTML-шаблон, нам пришлось бы написать что-то в этом роде:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="ru">
<head>
<title>BpeMA в будущем<ДШе> </head> <body>
<И1>Мой сайт точного времени</1п1>
<р>Через {{ hour_offset }} часов будет {{ next_time >}.</р>
<hr>
<р>Спасибо, что посетили мой сайт.</р> </body> </html>
Видно, что значительная часть HTML-разметки просто продублирована. В случае типичного сайта с панелью навигации, несколькими таблицами стилей, быть может, JavaScript-сценариями каждый шаблон содержал бы разнообразный повторяющийся код.
Если решать эту проблему путем включения на стороне сервера, то следовало бы вынести общие фрагменты из обоих шаблонов, поместить их в отдельные фрагментарные шаблоны и затем включать их в каждый шаблон. Наверное, вы сохранили бы начало шаблона в файле header, html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="ru">
<head>
А конец, скорее всего, поместили бы в файл footer.html:
<hr>
<р>Спасибо, что посетили мой сайт.</р> </body> </html>
Стратегия включения хорошо подходит для верха и низа страницы. А вот с серединой придется повозиться. В данном примере обе страницы имеют заголовок - <1л1>Мой сайт точного времени</1л1>, но включить его в файл header.html нельзя, потому что тег <title> на этих страницах различается. Если бы мы перенесли <h1> в header.html, то пришлось бы переносить и <title>, но тогда мы не смогли бы сделать разные заголовки на разных страницах. Понимаете, к чему все идет?
Механизм наследования шаблонов в Django решает такого рода проблемы. Можно считать, что это вывернутая наизнанку идея включения на стороне сервера. Вместо того чтобы определять общие фрагменты, мы определяем различающиеся.
Первым делом нужно определить базовый шаблон - скелет страницы, в который позже будут вставлены дочерние шаблоны. Вот как выглядит базовый шаблон в нашем примере:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
chtml lang="ru">
<head>
<title>{% block title %}{% endblock %}</title> </head> <body>
<М>Мой сайт точного времени</М> {% block content %}{% endblock %} {% block footer %} <hr>
<р>Спасибо, что посетили мой сайт.</р> {% endblock %} </body> </html>
В этом шаблоне, который мы назовем base.html, определен простой HTML-документ, описывающий структуру всех страниц сайта. Дочерние шаблоны могут переопределить некоторые блоки, дополнить их или оставить без изменения. (Если вы выполняете приведенные в тексте упражнения, сохраните этот файл в каталоге шаблонов под именем base.html.)
Здесь мы воспользовались не встречавшимся ранее тегом {% block %}. Он просто сообщает системе о том, что некоторая часть шаблона может быть переопределена в дочернем шаблоне.
Имея базовый шаблон, мы можем соответствующим образом модифицировать шаблон current_datetime.html:
{% extends "base.html" %}
{% block title %}Текущее время{% endblock %}
{% block content %}
<р>Сейчас {{ current_date }}.</p>
{% endblock %}
Заодно давайте уж создадим шаблон для представления hours_ahead из главы 3. (Оставляем в качестве упражнения модификацию hours_ahead так, чтобы вместо «зашитой» HTML-разметки в нем использовались шаблоны.) Вот как он выглядит:
{% extends "base.html" %}
{% block title %}Время в будущем{% endblock %} {% block content %}
<р>Через {{ hour_offset }} часов будет {{ next_time >>.</p> {% endblock %}
Ну не прелесть ли? Каждый шаблон содержит только данные, уникальные для него самого. Никакого дублирования. Чтобы изменить общий дизайн сайта, достаточно модифицировать только base.html, и изменения немедленно отразятся на всех страницах.
Объясним, как это работает. Во время загрузки шаблона current_datetime. 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 работает в обоих направлениях. Иначе говоря, блок - не просто дыра, которую нужно заполнить, а еще и содержимое, которым эта дыра заполняется в родительском шаблоне. Если бы в одном шаблоне встретились несколько одноименных тегов {% block %}, родитель этого шаблона не знал бы, содержимое какого блока использовать.
• Шаблон, имя которого задано в теге {% extends %}, загружается так же, как это делает функция get_template(), то есть имя шаблона конкатенируется с путем, заданным параметром TEMPLATE_DIRS.
• Обычно аргументом {% extends %} является строка, но может быть и переменная, если имя родительского шаблона становится известно только на этапе выполнения. Это открывает возможность для различных хитроумных динамических трюков.
Источник: Головатый А., Каплан-Мосс Дж. Django. Подробное руководство, 2-е издание. - Пер. с англ. - СПб.: Символ- Плюс, 2010. - 560 е., ил.