Язык шаблонов Django
В этом документе описан синтаксис языка системы шаблонов Django. Если вас интересует более технический взгляд на то, как она работает и как её расширять, см. Язык шаблонов Django: для программистов на Python.
Язык шаблонов Django призван сочетать гибкость и простоту. Он создан так, чтобы быть удобным для тех, кто привык работать с HTML. Если вы знакомы с другими текстовыми языками шаблонов, например Smarty или Jinja2, шаблоны Django покажутся вам привычными.
Философия
Если вы знакомы с программированием или привыкли к языкам, в которых программный код непосредственно смешан с HTML, помните, что система шаблонов Django — это не просто Python, встроенный в HTML. Это сделано намеренно: система шаблонов предназначена для описания представления, а не программной логики.
Система шаблонов Django предоставляет теги, похожие по назначению на некоторые конструкции языков программирования: тег if для проверки логических условий, тег for для циклов и т. д. Но они не выполняются как соответствующий код Python, а система шаблонов не выполняет произвольные выражения Python. По умолчанию поддерживаются только перечисленные ниже теги, фильтры и синтаксические конструкции (при необходимости вы можете добавить собственные расширения языка шаблонов).
Шаблоны
Шаблон — это текстовый файл. Он может создавать текст в любом формате (HTML, XML, CSV и т. д.).
Шаблон содержит переменные, значения которых подставляются при обработке шаблона, и теги, управляющие логикой шаблона.
Ниже приведён минимальный шаблон, иллюстрирующий несколько основных понятий. Каждый элемент будет описан далее в этом документе.
{% extends "base_generic.html" %}
{% block title %}{{ section.title }}{% endblock %}
{% block content %}
<h1>{{ section.title }}</h1>
{% for story in story_list %}
<h2>
<a href="{{ story.get_absolute_url }}">
{{ story.headline|upper }}
</a>
</h2>
<p>{{ story.tease|truncatewords:"100" }}</p>
{% endfor %}
{% endblock %}
Философия
Зачем использовать текстовый шаблон, а не шаблон на основе XML (например, Zope TAL)? Мы хотели, чтобы язык шаблонов Django можно было использовать не только для шаблонов XML/HTML. Его можно применять для любого текстового формата, например для электронных писем, JavaScript и CSV.
Переменные
Переменные выглядят так: {{ variable }}. Когда обработчик шаблонов встречает переменную, он вычисляет её и подставляет результат. Имена переменных могут состоять из любых сочетаний буквенно-цифровых символов и символа подчёркивания ("_"), но не могут начинаться с символа подчёркивания и не могут быть числами. Точка (".") также используется в выражениях с переменными, но имеет особое значение, описанное ниже. Важно: имена переменных не могут содержать пробелы и знаки пунктуации.
Используйте точку (.), чтобы получить доступ к атрибутам переменной.
Что происходит «под капотом»
Технически, когда система шаблонов встречает точку, она выполняет следующие проверки в указанном порядке:
- Поиск в словаре
- Поиск атрибута или метода
- Поиск по числовому индексу
Если полученное значение можно вызвать, оно вызывается без аргументов. Результат вызова становится значением шаблона.
Порядок поиска может привести к неожиданному поведению объектов, переопределяющих поиск в словаре. Например, рассмотрим следующий фрагмент кода, который пытается пройти циклом по collections.defaultdict:
{% for k, v in defaultdict.items %}
Do something with k and v here...
{% endfor %}
Поскольку поиск в словаре выполняется первым, срабатывает это поведение и возвращается значение по умолчанию вместо вызова предполагаемого метода .items(). В таком случае сначала преобразуйте объект в словарь.
В приведённом выше примере {{ section.title }} будет заменено атрибутом title объекта section.
Если вы используете несуществующую переменную, система шаблонов подставит значение параметра string_if_invalid, которому по умолчанию присвоено значение '' (пустая строка).
Обратите внимание: «bar» в выражении шаблона вида {{ foo.bar }} будет интерпретироваться как строковый литерал, а не как значение переменной «bar», если такая переменная есть в контексте шаблона.
Доступ к атрибутам переменных, начинающимся с символа подчёркивания, запрещён, поскольку обычно они считаются закрытыми.
Фильтры
Вы можете изменять отображаемые переменные с помощью фильтров.
Фильтры выглядят так: {{ name|lower }}. Это выражение выводит значение переменной {{ name }} после обработки фильтром lower, который переводит текст в нижний регистр. Для применения фильтра используйте вертикальную черту (|).
Фильтры можно объединять в цепочки: результат одного фильтра передаётся следующему. {{ text|escape|linebreaks }} — распространённый способ экранировать текст, а затем преобразовать символы новой строки в теги <p>.
Некоторые фильтры принимают аргументы. Аргумент фильтра выглядит так: {{
bio|truncatewords:30 }}. Это выражение выведет первые 30 слов переменной bio.
Аргументы фильтров, содержащие пробелы, необходимо заключать в кавычки; например, чтобы объединить список, вставив между элементами запятые и пробелы, используйте {{ list|join:", " }}.
Django предоставляет около шестидесяти встроенных фильтров шаблонов. Подробнее о них читайте в справочнике по встроенным фильтрам. Вот несколько наиболее часто используемых фильтров:
-
default -
Если переменная имеет значение false или пуста, используется заданное значение по умолчанию. В противном случае используется значение переменной. Например:
{{ value|default:"nothing" }}Если
valueне задана или пуста, будет выведено «nothing». -
length -
Возвращает длину значения. Работает как со строками, так и со списками. Например:
{{ value|length }}Если
valueимеет значение['a', 'b', 'c', 'd'], результатом будет4. -
filesizeformat -
Форматирует значение как размер файла в «удобочитаемом» виде (например,
'13 KB','4.1 MB','102 bytes'и т. д.). Например:{{ value|filesizeformat }}Если
valueравно 123456789, результатом будет117.7 MB.
Это лишь несколько примеров; полный список см. в справочнике по встроенным фильтрам.
Вы также можете создавать собственные фильтры шаблонов; см. Как создавать собственные теги и фильтры шаблонов.
См. также
Интерфейс администратора Django может содержать полный справочник всех тегов и фильтров шаблонов, доступных на данном сайте. См. Генератор документации администратора Django.
Комментарии
Чтобы закомментировать часть строки в шаблоне, используйте синтаксис комментариев: {# #}.
Например, результат обработки этого шаблона будет таким: 'hello':
{# greeting #}hello
Комментарий может содержать любой код шаблона, допустимый или нет. Например:
{# {% if foo %}bar{% else %} #}
Этот синтаксис можно использовать только для однострочных комментариев (между разделителями {# и #} не допускаются символы новой строки). Чтобы закомментировать многострочный фрагмент шаблона, см. тег comment.
Наследование шаблонов
Самая мощная и, следовательно, самая сложная часть обработчика шаблонов Django — это наследование шаблонов. Наследование позволяет создать базовый шаблон-«каркас», содержащий общие элементы сайта и определяющий блоки, которые дочерние шаблоны могут переопределять.
Рассмотрим наследование шаблонов на примере:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<title>{% block title %}My amazing site{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
Этот шаблон, который мы назовём base.html, определяет HTML-каркас документа для страницы с двумя колонками. Задача «дочерних» шаблонов — заполнить пустые блоки содержимым.
В этом примере тег block определяет три блока, которые дочерние шаблоны могут заполнить. Тег block лишь сообщает обработчику шаблонов, что дочерний шаблон может переопределить соответствующие части шаблона.
Дочерний шаблон может выглядеть так:
{% extends "base.html" %}
{% block title %}My amazing blog{% endblock %}
{% block content %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
Ключевую роль здесь играет тег extends. Он сообщает обработчику шаблонов, что этот шаблон «наследует» другой шаблон. При обработке шаблона система сначала находит родительский шаблон — в данном случае «base.html».
Затем обработчик шаблонов находит три тега block в base.html и заменяет соответствующие блоки содержимым дочернего шаблона. В зависимости от значения blog_entries результат может выглядеть так:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<title>My amazing blog</title>
</head>
<body>
<div id="sidebar">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
</ul>
</div>
<div id="content">
<h2>Entry one</h2>
<p>This is my first entry.</p>
<h2>Entry two</h2>
<p>This is my second entry.</p>
</div>
</body>
</html>
Обратите внимание: поскольку дочерний шаблон не определяет блок sidebar, вместо него используется значение из родительского шаблона. Содержимое внутри тега {% block %} в родительском шаблоне всегда используется как значение по умолчанию.
Можно использовать столько уровней наследования, сколько необходимо. Один из распространённых вариантов — подход с тремя уровнями:
- Создайте шаблон
base.html, задающий основной внешний вид сайта. - Создайте шаблон
base_SECTIONNAME.htmlдля каждого «раздела» сайта. Например,base_news.html,base_sports.html. Все эти шаблоны наследуютbase.htmlи содержат стили и оформление, характерные для раздела. - Создайте отдельные шаблоны для каждого типа страницы, например для новостной статьи или записи в блоге. Эти шаблоны наследуют соответствующий шаблон раздела.
Такой подход максимально повышает повторное использование кода и упрощает добавление элементов в общие области содержимого, например навигации раздела.
Вот несколько советов по работе с наследованием:
- Если в шаблоне используется тег
{% extends %}, он должен быть первым тегом шаблона. В противном случае наследование шаблонов работать не будет. - Чем больше тегов
{% block %}в базовых шаблонах, тем лучше. Помните, что дочерним шаблонам не обязательно определять все блоки родительского шаблона. Поэтому можно задать разумные значения по умолчанию для нескольких блоков и переопределить только те, которые нужны. Лучше иметь больше точек расширения, чем меньше. - Если вы обнаружили, что дублируете содержимое в нескольких шаблонах, вероятно, его следует перенести в
{% block %}родительского шаблона. - Чтобы получить содержимое блока из родительского шаблона, используйте переменную
{{ block.super }}. Это удобно, если вы хотите дополнить содержимое родительского блока, а не полностью его переопределить. Данные, вставленные с помощью{{ block.super }}, не будут автоматически экранированы (см. следующий раздел), поскольку в родительском шаблоне они уже были экранированы, если это было необходимо. - Если указать то же имя шаблона, от которого выполняется наследование, тег
{% extends %}позволит наследовать шаблон и одновременно переопределять его. В сочетании с{{ block.super }}это может стать эффективным способом внести небольшие изменения. Полный пример см. в разделе Расширение переопределённого шаблона руководства Переопределение шаблонов. -
Переменные, созданные вне тега
{% block %}с помощью синтаксиса тега шаблонаas, нельзя использовать внутри блока. Например, этот шаблон ничего не отобразит:{% translate "Title" as title %} {% block content %}{{ title }}{% endblock %} -
Для удобства чтения тегу
{% endblock %}можно указать имя. Например:{% block content %} ... {% endblock content %}В больших шаблонах этот приём помогает определить, какие теги
{% block %}закрываются. -
Теги
{% block %}обрабатываются в первую очередь. Поэтому содержимое блока всегда переопределяется независимо от истинности окружающих тегов. Например, этот шаблон всегда переопределит содержимое блокаtitle:{% if change_title %} {% block title %}Hello!{% endblock title %} {% endif %}
Наконец, обратите внимание: в одном шаблоне нельзя определять несколько тегов block с одинаковым именем. Это ограничение существует потому, что тег блока работает в «обоих» направлениях. Иными словами, тег блока не просто задаёт место для вставки содержимого — он также определяет содержимое, заполняющее это место в родительском шаблоне. Если бы в шаблоне было два тега block с одинаковыми именами, родительский шаблон не знал бы, содержимое какого из блоков использовать.
Фрагменты шаблонов
Фрагменты шаблонов определяют повторно используемые части шаблона внутри файла шаблона. Это автономные компоненты, которые можно отображать несколько раз в одном шаблоне, избегая повторов и обеспечивая единообразный результат.
Основной синтаксис
Фрагмент можно определить с помощью тега partialdef:
authors.html{% partialdef user-info %}
<div id="user-info-{{ user.username }}">
<h3>{{ user.name }}</h3>
<p>{{ user.bio }}</p>
</div>
{% endpartialdef %}
Для удобства чтения имя можно указать в закрывающем теге {% endpartialdef %}:
{% partialdef user-info %}
{# ... #}
{% endpartialdef user-info %}
Фрагмент шаблона можно отобразить с помощью тега partial:
{% partial user-info %}
Повторное использование фрагментов
Фрагмент шаблона можно использовать несколько раз:
authors.html{% block content %}
<h2>Authors</h2>
{% for user in authors %}
{% partial user-info %}
{% endfor %}
<h2>Editors</h2>
{% for user in editors %}
{% partial user-info %}
{% endfor %}
{% endblock %}
Содержимое фрагмента отображается при каждом использовании фрагмента с указанным именем в текущем контексте шаблона.
Встроенные фрагменты
Фрагмент шаблона можно определить и отобразить на месте с помощью аргумента inline. Это позволяет определить фрагмент для последующего повторного использования и одновременно сразу отобразить его в месте определения:
{# Define and render immediately. #}
{% partialdef user-info inline %}
<div id="user-info-{{ user.username }}">
<h3>{{ user.name }}</h3>
<p>{{ user.bio }}</p>
</div>
{% endpartialdef %}
{# Other page content here. #}
{# Reuse later elsewhere in the template. #}
<section class="featured-authors">
<h2>Featured Authors</h2>
{% for user in featured %}
{% partial user-info %}
{% endfor %}
</section>
Прямой доступ к фрагментам
К фрагментам шаблонов, определённым с помощью partialdef, можно обращаться напрямую при загрузке или включении шаблона, используя синтаксис template.html#partial_name.
Например, с помощью сокращённой функции render() следующий код отображает только фрагмент с именем user-info, определённый в шаблоне authors.html:
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404, render
def user_info_partial(request, user_id):
user = get_object_or_404(User, id=user_id)
return render(request, "authors.html#user-info", {"user": user})
Этот подход особенно полезен для запросов в стиле AJAX, которые обновляют лишь отдельные части страницы с помощью отрисованного фрагмента шаблона.
Фрагменты шаблонов также можно включать с помощью тега шаблона include с той же директивой #:
{% include "authors.html#user-info" %}
Работа с контекстом
Фрагменты шаблонов отображаются в текущем контексте шаблона. Они работают ожидаемым образом в циклах и с контекстными переменными:
{% for user in users %}
{% partial user-info %}
{% endfor %}
Контекстные переменные можно настроить с помощью тега with:
{% with user=featured_author %}
<h2>Featured Author of the Month</h2>
{% partial user-info %}
{% endwith %}
Автоматическое экранирование HTML
При создании HTML с помощью шаблонов всегда существует риск, что переменная содержит символы, влияющие на результирующий HTML. Например, рассмотрим следующий фрагмент шаблона:
Hello, {{ name }}
На первый взгляд это безобидный способ вывести имя пользователя, но представьте, что произойдёт, если пользователь введёт такое имя:
<script>alert('hello')</script>
При таком значении имени результат обработки шаблона будет выглядеть так:
Hello, <script>alert('hello')</script>
…и браузер выведет диалоговое окно JavaScript alert!
А что, если имя содержит символ '<', например так?
<b>username
В результате обработки шаблона получится следующее:
Hello, <b>username
…а оставшаяся часть веб-страницы будет отображаться полужирным шрифтом!
Очевидно, нельзя безоговорочно доверять данным, введённым пользователем, и напрямую вставлять их на веб-страницы: злоумышленник может воспользоваться этой уязвимостью и причинить вред. Такая уязвимость называется межсайтовым скриптингом (XSS).
Чтобы избежать этой проблемы, есть два варианта:
- Во-первых, можно обязательно применять к каждой недоверенной переменной фильтр
escape(описан ниже), который заменяет потенциально опасные символы HTML безопасными. В первые годы существования Django это было решением по умолчанию, но проблема в том, что ответственность за экранирование всего содержимого возлагается на вас — разработчика или автора шаблонов. Легко забыть экранировать данные. - Во-вторых, можно воспользоваться автоматическим экранированием HTML в Django. В оставшейся части этого раздела описано, как оно работает.
По умолчанию Django автоматически экранирует результат вывода каждой переменной в каждом шаблоне. В частности, экранируются следующие пять символов:
-
<преобразуется в< -
>преобразуется в> -
'(одинарная кавычка) преобразуется в' -
"(двойная кавычка) преобразуется в" -
&преобразуется в&
Ещё раз подчеркнём: такое поведение включено по умолчанию. При использовании системы шаблонов Django ваши шаблоны защищены.
Как отключить экранирование
Если вы не хотите, чтобы данные экранировались автоматически на уровне сайта, шаблона или отдельной переменной, это можно сделать несколькими способами.
Зачем отключать экранирование? Иногда переменные шаблона содержат данные, которые должны выводиться как необработанный HTML, и в таком случае их не нужно экранировать. Например, вы можете хранить блок HTML в базе данных и вставлять его непосредственно в шаблон. Или использовать систему шаблонов Django для создания текста, который не является HTML, например электронного письма.
Для отдельных переменных
Чтобы отключить автоматическое экранирование отдельной переменной, используйте фильтр safe:
This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}
Считайте, что safe означает «безопасно для дальнейшего экранирования» или «можно безопасно интерпретировать как HTML». В этом примере, если data содержит '<b>', результат будет таким:
This will be escaped: <b> This will not be escaped: <b>
Для блоков шаблона
Чтобы управлять автоматическим экранированием в шаблоне, заключите шаблон (или его отдельный фрагмент) в тег autoescape, например так:
{% autoescape off %}
Hello {{ name }}
{% endautoescape %}
Тег autoescape принимает в качестве аргумента значение on или off. Иногда может потребоваться принудительно включить автоматическое экранирование, даже если в противном случае оно было бы отключено. Пример шаблона:
Auto-escaping is on by default. Hello {{ name }}
{% autoescape off %}
This will not be auto-escaped: {{ data }}.
Nor this: {{ other_data }}
{% autoescape on %}
Auto-escaping applies again: {{ name }}
{% endautoescape %}
{% endautoescape %}
Тег автоматического экранирования распространяет своё действие на шаблоны, наследующие текущий, а также на шаблоны, включённые с помощью тега include, как и все блочные теги. Например:
base.html{% autoescape off %}
<h1>{% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
{% endautoescape %}
child.html{% extends "base.html" %}
{% block title %}This & that{% endblock %}
{% block content %}{{ greeting }}{% endblock %}
Поскольку в базовом шаблоне автоматическое экранирование отключено, оно будет отключено и в дочернем шаблоне. Если переменная greeting содержит строку <b>Hello!</b>, результатом будет следующий HTML:
<h1>This & that</h1> <b>Hello!</b>
Примечания
Как правило, авторам шаблонов не нужно особенно беспокоиться об автоматическом экранировании. Разработчикам на стороне Python (тем, кто пишет представления и собственные фильтры) нужно учитывать случаи, когда данные не следует экранировать, и правильно помечать такие данные, чтобы в шаблоне всё работало как надо.
Если вы создаёте шаблон, который может использоваться в ситуациях, когда неизвестно, включено ли автоматическое экранирование, применяйте фильтр escape ко всем переменным, которые нужно экранировать. Если автоматическое экранирование включено, фильтр escape не будет опасно экранировать данные повторно: фильтр escape не влияет на автоматически экранируемые переменные.
Строковые литералы и автоматическое экранирование
Как упоминалось ранее, аргументы фильтров могут быть строками:
{{ data|default:"This is a string literal." }}
Все строковые литералы вставляются в шаблон без автоматического экранирования — они обрабатываются так, как если бы к ним применили фильтр safe. Это объясняется тем, что автор шаблона контролирует содержимое строкового литерала и может правильно экранировать текст при написании шаблона.
Это означает, что нужно писать так:
{{ data|default:"3 < 2" }}
…а не так:
{{ data|default:"3 < 2" }} {# Bad! Don't do this. #}
Это не влияет на обработку данных, полученных из самой переменной. Содержимое переменной по-прежнему автоматически экранируется, если это необходимо, поскольку оно находится вне контроля автора шаблона.
Доступ к вызовам методов
Большинство методов объектов также доступны из шаблонов. Это означает, что шаблоны могут обращаться не только к атрибутам классов (например, именам полей) и переменным, переданным из представлений. Например, ORM Django предоставляет синтаксис «entry_set» для получения коллекции объектов, связанных внешним ключом. Поэтому, если модель «comment» связана внешним ключом с моделью «task», можно пройти циклом по всем комментариям, связанным с определённой задачей:
{% for comment in task.comment_set.all %}
{{ comment }}
{% endfor %}
Аналогично, QuerySets предоставляют метод count() для подсчёта содержащихся в них объектов. Поэтому количество комментариев, связанных с текущей задачей, можно получить так:
{{ task.comment_set.all.count }}
Также можно обращаться к методам, явно определённым в собственных моделях:
models.pyclass Task(models.Model):
def foo(self):
return "bar"
template.html{{ task.foo }}
Поскольку Django намеренно ограничивает возможности обработки логики в языке шаблонов, передавать аргументы методам, вызываемым из шаблонов, нельзя. Данные следует вычислять в представлениях, а затем передавать в шаблоны для отображения.
Библиотеки пользовательских тегов и фильтров
Некоторые приложения предоставляют библиотеки пользовательских тегов и фильтров. Чтобы использовать их в шаблоне, убедитесь, что приложение включено в INSTALLED_APPS (в этом примере мы добавим 'django.contrib.humanize'), а затем используйте в шаблоне тег load:
{% load humanize %}
{{ 45000|intcomma }}
В приведённом выше примере тег load загружает библиотеку тегов humanize, после чего становится доступен фильтр intcomma. Если вы включили django.contrib.admindocs, вы можете найти список пользовательских библиотек в своей установке в разделе документации административного сайта.
Тег load может принимать несколько имён библиотек, разделённых пробелами. Например:
{% load humanize i18n %}
Информацию о том, как создавать собственные библиотеки шаблонов, см. в разделе Как создавать пользовательские теги и фильтры шаблонов.
Пользовательские библиотеки и наследование шаблонов
При загрузке библиотеки пользовательских тегов или фильтров эти теги и фильтры становятся доступны только в текущем шаблоне, но не в родительских или дочерних шаблонах в цепочке наследования.
Например, если шаблон foo.html содержит {% load humanize %}, дочерний шаблон (например, тот, в котором есть {% extends "foo.html" %}) не получит доступ к тегам и фильтрам шаблонов humanize. Дочерний шаблон должен самостоятельно выполнить {% load humanize %}.
Это сделано для удобства сопровождения и здравого смысла.
См. также
- Справочник по шаблонам
-
Здесь рассматриваются встроенные теги, встроенные фильтры, использование альтернативного языка шаблонов и многое другое.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/ref/templates/language/