Самая мощная часть модуля 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
указывать не обязательно.