Наследование шаблонов: base, block, extends

Наследование шаблонов — это базовый «скелет» сайта (шапка, меню, подвал) с дырками-блоками, которые каждая страница заполняет по-своему. Один каркас — десятки страниц.
Без наследования шапку и подвал пришлось бы копировать в каждый шаблон. Базовый шаблон описывает общую структуру и оставляет блоки; дочерние шаблоны наследуют его через extends и переопределяют нужные блоки. Это самая мощная часть Jinja.

Любой сайт имеет общие части: меню, футер, подключение CSS. Дублировать их в каждом файле — кошмар поддержки (поменял меню — правь в двадцати местах). Наследование решает это: общее живёт в base.html, страницы лишь подставляют своё содержимое.

Базовый шаблон templates/base.html:

<!DOCTYPE html>
<html>
<head><title>{% block title %}Сайт{% endblock %}</title></head>
<body>
  <nav>Меню</nav>
  {% block content %}{% endblock %}
  <footer>Подвал</footer>
</body>
</html>

Дочерний шаблон templates/page.html:

{% extends "base.html" %}

{% block title %}Главная{% endblock %}

{% block content %}
  <h1>Добро пожаловать</h1>
{% endblock %}

Директива {% extends %} говорит «возьми каркас base.html», а блоки переопределяют дырки. Внутри блока можно вызвать {{ super() }} — он вставит содержимое родительского блока, чтобы дополнить, а не заменить его.

Наследование шаблонов — это применение принципа DRY (не повторяйся) к вёрстке. Один base.html описывает общий каркас, и любая правка шапки, меню или подключения стилей делается в одном файле, мгновенно отражаясь на всех страницах. На больших проектах строят иерархию в несколько уровней: глобальный base.html, под ним layout раздела (свой сайдбар у админки, свой — у блога), под ним конкретные страницы. К наследованию примыкают два соседних инструмента: {% include %} вставляет переиспользуемый фрагмент (карточка товара, форма поиска), а макросы — это «функции для разметки», принимающие параметры. Вместе они полностью устраняют копипасту HTML.

Как работает под капотом

При extends Jinja берёт структуру родителя как основу, а из дочернего шаблона использует только содержимое блоков с совпадающими именами. Дочерний шаблон не «дописывается снизу» — он встраивается в дырки родителя.

  base.html                page.html (extends base)
  ┌───────────────┐        переопределяет:
  │ title: [блок] │◀──────── title  = "Главная"
  │ nav           │        content= "<h1>..."
  │ content:[блок]│◀────────┘
  │ footer        │
  └───────────────┘
       │ слияние
       ▼
  итог: nav+footer из base, title+content из page

Смоделируем слияние «скелет + блоки» обычным Python.

base_blocks = {"title": "Сайт", "content": "(пусто)"}
base_skeleton = "[head:{title}] [nav] [{content}] [footer]"

def render_child(child_blocks):
    blocks = dict(base_blocks)        # берём дефолты родителя
    blocks.update(child_blocks)       # переопределяем своими
    return base_skeleton.format(**blocks)

print(render_child({"title": "Главная", "content": "Привет"}))
print(render_child({"title": "О нас"}))   # content остался дефолтным

Запусти: дочерние блоки переопределяют дырки родителя, а нетронутые блоки берут значение по умолчанию. nav и footer всегда из base — их не копируем. Так наследование убивает дублирование.

Частые ошибки

  • Контент вне блоков в дочернем шаблоне. Всё, что не в {% block %}, при extends игнорируется.
  • extends не первой строкой. Директива extends должна идти в начале файла.
  • Забыть super() и затереть родительский блок. Если хочешь дополнить, а не заменить — вызови {{ super() }}.

Best practices

  • Один base.html на проект (или несколько уровней: base → layout раздела → страница).
  • Выноси повторяющиеся куски в {% include %} (карточка, меню) и макросы.
  • Имена блоков осмысленные: content, title, sidebar, scripts.

Что запомнить

  • base.html задаёт общий каркас сайта с блоками-дырками.
  • Дочерний шаблон через extends переопределяет нужные блоки.
  • {{ super() }} вставляет содержимое родительского блока для дополнения.
  • include и макросы дополняют наследование для переиспользования фрагментов.

Итог: наследование задаёт общий каркас в base.html с блоками, а страницы через extends заполняют только свои части. super() позволяет дополнять родителя. Это устраняет дублирование разметки. Дальше — безопасность шаблонов и автоэкранирование.

Проверьте себя
1. Что произойдёт с текстом в дочернем шаблоне, написанным вне любого {% block %}?
AОн добавится в конец страницы
BПри extends он игнорируется
CВызовет ошибку
DЗаменит весь base
2. Зачем нужен {{ super() }} внутри блока?
AЧтобы вызвать view-функцию
BЧтобы вставить содержимое родительского блока и дополнить его
CЧтобы пропустить блок
DЧтобы создать новый шаблон