Самая мощная часть модуля jinja2
- это наследование шаблонов. Наследование шаблонов позволяет создать базовый "скелетный" шаблон, который содержит все общие элементы сайта и определяет блоки {% block ... %}
, которые "дочерние" шаблоны могут переопределить.
Базовый шаблон Jinja2, который назовем base.html
, определяет простой документ-скелет HTML-разметки, который будет использоваться для создания более сложных страниц. Задача "дочерних" шаблонов - заполнить пустые блоки {% block ... %}
контентом:
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
</html>
В этом примере теги {% block ... %} ... {% endblock %}
определяют четыре блока, которые могут заполнять дочерние шаблоны. Все, что делает тег блока block
, - это сообщает механизму шаблонов, что дочерний шаблон может переопределить эти заполнители в шаблоне.
Теги блоков block
могут находиться внутри других блоков, таких как {% if ... %} ... {% endif %}
, но они всегда будут заполняться контентом независимо от того, разрешает ли блок шаблона if/else
в конечном итоге отображается внутреннему блоку block
.
Дочерний шаблон Jinja2 может выглядеть так:
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome to my awesome homepage.
</p>
{% endblock %}
В дочернем шаблоне тег {% extends ... %}
является ключевым. Он сообщает механизму шаблонов, что этот шаблон "расширяет" другой шаблон. Когда система шаблонов Jinja2 оценивает этот шаблон, она сначала находит родителя. Тег extends
должен быть первым тегом в шаблоне.
Jinja2 поддерживает динамическое наследование и не делает различий между родительским и дочерним шаблоном до тех пор, пока рендеру не попадется тег extends
. Это приводит к тому, что все, что было до первого тега extends
, включая текст, пробелы и т.д., будет выводится вместо того, чтобы игнорироваться.
Обратите внимание: т. к. в примере дочерний шаблон не определяет блок нижнего колонтитула, вместо него будет использоваться значение из родительского шаблона.
Путь к имени файла шаблона зависит от загрузчика шаблона. Например, загрузчик FileSystemLoader
определяет путь к папке с шаблонами и позволяет получить доступ к шаблонам, указав просто имя файла. Для получения доступа к шаблонам в подкаталогах можно использовать косую черту:
{% extends "layout/default.html" %}
Нельзя определить несколько тегов {% block name %}
с одним и тем же именем name
в одном шаблоне. Это ограничение существует, потому что тег блока работает "в обоих" направлениях. То есть тег блока block
не просто предоставляет заполнитель, он также определяет содержимое, которое заполняется в родительском элементе. Если бы в шаблоне было два тега {% block name %} с одинаковыми именами
name `, то родительский элемент этого шаблона не знал бы, какой из содержимого блока использовать/заполнять.
Но, если необходимо напечатать блок несколько раз, то можно использовать специальную переменную self
и вызвать блок с этим именем:
<title>{% block title %}{% endblock %}</title>
{# повторный вывод блока с именем `title` #}
<h1>{{ self.title() }}</h1>
{% block body %}{% endblock %}
Можно отобразить содержимое родительского блока, вызвав функцию шаблона {{ super() }}
. Эта функция возвращает результаты родительского блока:
{% block sidebar %}
<h3>Table Of Contents</h3>
...
{{ super() }}
{% endblock %}
В случае нескольких уровней {% extends %}
супер ссылки могут быть связаны (как в super.super()
) для пропуска уровней в дереве наследования.
Например:
# parent.tmpl
body: {% block body %}Hi from parent.{% endblock %}
# child.tmpl
{% extends "parent.tmpl" %}
{% block body %}Hi from child. {{ super() }}{% endblock %}
# grandchild1.tmpl
{% extends "child.tmpl" %}
{% block body %}Hi from grandchild1.{% endblock %}
# grandchild2.tmpl
{% extends "child.tmpl" %}
{% block body %}Hi from grandchild2. {{ super.super() }} {% endblock %}
child.tmpl
выдаст текст: body: Hi from child. Hi from parent.grandchild1.tmpl
выдаст текст: body: Hi from grandchild1.grandchild2.tmpl
выдаст текст: body: Hi from grandchild2. Hi from parent.Jinja позволяет ставить имя блока после закрывающего тега для лучшей читаемости:
{% block sidebar %}
{% block inner_sidebar %}
...
{% endblock inner_sidebar %}
{% endblock sidebar %}
При этом имя после слова endblock
конечного блока обязательно должно совпадать с именем этого блока.
Блоки {% block ... %}{% endblock %}
могут быть вложены для создания более сложных макетов. При этом блоки по умолчанию не могут обращаться к переменным из внешних областей:
{% for item in seq %}
<li>{% block loop_item %}{{ item }}{% endblock %}</li>
{% endfor %}
В этом примере будут выведены пустые элементы <li></li>
, потому что элемент {{ item }}
недоступен внутри блока {% block loop_item %}
. Причина в том, что блок изменяется дочерним шаблоном путем передачи переменной, которая не определена в родительском блоке, а так же не передана в контекст родителя.
Начиная с Jinja 2.2, можно явно указать, что переменные дочернего блока доступны в родительском блоке, добавив модификатор scoped
в объявление блока родителя:
{% for item in seq %}
<li>{% block loop_item scoped %}{{ item }}{% endblock %}</li>
{% endfor %}
При переопределении блока родителя модификатор scoped
указывать не обязательно.