Наследование шаблонов (extends). Тег include

Курс по Django: https://stepik.org/a/183363

Архив проекта: 14_sitewomen.zip

На предыдущих занятиях мы с вами подробно описали шаблон index.html для главной страницы сайта. Давайте отредактируем еще один about.html с выводом информации о сайте. В самом простом варианте его можно представить следующим образом:

<!DOCTYPE html>
<html>
<head>
         <title>{{title}}</title>
</head>
<body>
 
<ul>
<li><a href="{% url 'home' %}">Главная</a></li>
{% for m in menu %}
{% if not forloop.last %}<li>{% else %}<li class="last">{% endif %}
         <a href="{% url m.url_name %}">{{m.title}}</a>
</li>
{% endfor %}
</ul>
 
<h1>{{title}}</h1>
</body>
</html>

То есть, я просто скопировал начало шаблона из index.html и вставил в файл about.html.

Отредактируем функцию about в файле views.py, добавив передачу списка пунктов menu:

def about(request):
    return render(request, 'women/about.html', {'title': 'О сайте', 'menu': menu})

После запуска тестового веб-сервера и перехода по URL-адресу:

http://127.0.0.1:8000/about/

увидим страницу «О сайте» с выводом главного меню.

Однако, как вы уже догадались, дублирование информации в разных шаблонах нарушает известный принцип:

DRY – don’t repeat yourself (не повторяйся).

Для устранения этого недостатка, обычно создается базовый (общий) шаблон, содержащий общие элементы типовых страниц сайта, а уже потом он расширяется (дополняется деталями) шаблонами отдельных страниц. Такое расширение называется наследованием шаблонов. И сейчас мы на конкретном примере посмотрим, как это можно сделать.

Если перейти в документацию по фильтрам и тегам шаблонов фреймворка Django, то среди тегов можно встретить следующие:

{% block [наименование] %} … {% endblock %}

{% extends <базовый шаблон> %}

Я подробно о них рассказывал в курсе по шаблонизатору Jinja2:

https://www.youtube.com/watch?v=cFJqMXxVNsI&list=PLA0M1Bcd0w8wfmtElObQrBbZjY6XeA06U

Но здесь повторюсь, так как абсолютно ничего сложного в наследовании шаблонов нет.

Итак, вначале мы с вами определим базовый шаблон. Его, как правило, создают на уровне всего проекта. Поэтому создадим каталог templates в папке sitewomen, а в нем файл с именем base.html. Однако если мы сейчас попробуем обратиться к этому шаблону, например, в функции представления about:

def about(request):
    return render(request, 'base.html', {'title': 'О сайте', 'menu': menu})

то увидим ошибку, что шаблон не был найден. Это связано с тем, что маршрут templates/base.html не стандартный и его нужно явно прописать для шаблонизатора. Для этого нужно перейти в файл settings.py пакета конфигурации, найти параметр TEMPLATES и в коллекции DIRS прописать путь к каталогу templates следующим образом:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / 'templates',
        ],
        'APP_DIRS': True,
        ...
    },
]

Все, теперь при обновлении страницы «О сайте» мы увидим отображение базового шаблона.

Вернем прежний шаблон в функции about:

def about(request):
    return render(request, 'women/about.html', {'title': 'О сайте', 'menu': menu})

Давайте теперь общие элементы страниц сайта вынесем в базовый шаблон base.html. Для этого я скопирую все из файла about.html и посмотрим, что здесь у нас общее. Очевидно, все начало, включая отображение главного меню. А вот заголовок h1 и остальную часть страницы внутри тега body будем считать изменяемой. Поэтому вместо тега h1 мы пропишем специальный шаблонный тег block следующим образом:

{% block content %} {% endblock %}

Здесь content – это название блока, вместо которого будет подставляться содержимое из наследуемых шаблонов.

Все, на этом базовый шаблон у нас с вами завершен. Осталось лишь расширить его для шаблонов index.html и about.html. Сначала перейдем в файл index.html и самой первой строчкой пропишем еще один шаблонный тег extends:

{% extends 'base.html' %}

А ниже все уберем, кроме отображения заголовка 1-го уровня и списка статей. Эта информация должна быть размещена в блоке content базового шаблона. Поэтому здесь, в дочернем нам следует переопределить этот блок content следующим образом:

{% block content %}
<h1>{{title}}</h1>
 
<ul>
         {% for p in posts %}
         {% if p.is_published %}
         <li>
                   <h2>{{p.title}}</h2>
                   <p >{{p.content}}</p>
                   <p ><a href="{% url 'post' p.id %}">Читать пост</a></p>
                   {% if not forloop.last %}
                   <hr>
                   {% endif %}
         </li>
         {% endif %}
         {% endfor %}
</ul>
{% endblock %}

То есть, мы просто заключили в этот тег то, что должно быть помещено в блок content базового шаблона.

Давайте посмотрим, как это будет работать. Перейдем на главную страницу сайта и видим полное содержимое HTML-документа, который мы с вами определили в базовом шаблоне base.html и в дочернем index.html.

По аналогии опишем расширение в шаблоне about.html:

{% extends 'base.html' %}
 
{% block content %}
<h1>{{title}}</h1>
{% endblock %}

Переходим по ссылке на страницу «О сайте» и видим полноценную HTML-страницу с заголовком 1-го уровня. В результате мы с вами ушли от дублирования в отдельных шаблонах и можем достаточно просто определять страницы сайта, просто расширяя базовый шаблон base.html.

Тег include

Помимо расширения шаблонов можно еще делать включение одного шаблона в другой. О чем здесь речь и для чего это нужно? Давайте представим, что мы бы хотели на главной странице нашего сайта (в шаблоне index.html) дополнительно отображать рубрики по известным женщинам в виде следующей навигационной панели из ссылок:

<nav>
         <a href="#">Актрисы</a> |
         <a href="#">Певицы</a> | 
         <a href="#">Спортсменки</a>
</nav>

Причем делать это и сверху перед списком, и внизу после списка. В результате у нас получается дублирование фрагмента. Как раз чтобы этого избежать, применяется тег include, который позволяет добавлять в шаблон данные из другого шаблона. Давайте это сделаем.

Вначале мы создадим подкаталог includes в папке templates/women нашего проекта для лучшей организации структуры файлов шаблонов. Внутри каталога includes разместим файл nav.html и скопируем в него тег nav. А в шаблоне index.html подключим этот файл с помощью тега include следующим образом:

{% extends 'base.html' %}
 
{% block content %}
{% include 'women/includes/nav.html' %}
 
<h1>{{title}}</h1>
 
<ul>
         {% for p in posts %}
         {% if p.is_published %}
         <li>
                   <h2>{{p.title}}</h2>
                   <p >{{p.content}}</p>
                   <p ><a href="{% url 'post' p.id %}">Читать пост</a></p>
                   {% if not forloop.last %}
                   <hr>
                   {% endif %}
         </li>
         {% endif %}
         {% endfor %}
</ul>
 
{% include 'women/includes/nav.html' %}
{% endblock %}

Причем сделали это в двух местах. При обновлении главной страницы увидим тот же результат, что и раньше, но при этом устранили дублирование фрагментов в шаблоне index.html.

Следует отметить, что при включении, шаблон nav.html имеет доступ ко всем параметрам, которые передаются в шаблон index.html. Например, в нем можно вывести заголовок:

<p >{{title}}</p>

Если же нам нужно запретить передачу переменных, то после пути к шаблону в теге include следует дополнительно прописать ключевое слово only:

{% include 'women/includes/nav.html' only %}

А если нужно при этом передать отдельные параметры, то это можно сделать с помощью ключевого слова with следующим образом:

{% include 'women/includes/nav.html' only with title='заголовок' %}

В итоге получаем довольно гибкий инструмент включения фрагментов в наши шаблоны.

Курс по Django: https://stepik.org/a/183363

Видео по теме