本文档解释了 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。
Variables look like this: {{ variable }}
. When the template engine
encounters a variable, it evaluates that variable and replaces it with the
result. Variable names consist of any combination of alphanumeric characters
and the underscore ("_"
) but may not start with an underscore, and may not
be a number. The dot ("."
) also appears in variable sections, although that
has a special meaning, as indicated below. Importantly, you cannot have spaces
or punctuation characters in variable names.
使用点号(.
)来访问一个变量的属性。
幕后
从技术上讲,当模板系统遇到一个点时,它会按照以下顺序尝试进行查找:
如果产生的值是可调用对象,则调用时不含参数。调用的结果成为模板值。
This lookup order can cause some unexpected behavior with objects that
override dictionary lookup. For example, consider the following code snippet
that attempts to loop over a collections.defaultdict
:
{% for k, v in defaultdict.items %}
Do something with k and v here...
{% endfor %}
因为字典查找是先发生的,这种行为会启动并提供一个默认值,而不是使用预期的 .items()
方法。在这种情况下,可以考虑先转换为字典。
在上面的例子中,{{ section.title }}
将被 section
对象的 title
属性所取代。
如果你使用一个不存在的变量,模板系统会插入 string_if_invalid
选项的值,默认设置为 ''
(空字符串)。
请注意,像 {{ foo.bar }}
这样的模板表达式中的“bar”将被解释为一个字面字符串,而不是使用变量“bar”的值,如果模板上下文中存在的话。
以下划线开头的变量属性可能不会被访问,因为它们通常被认为是私有的。
你可以通过使用 过滤器 修改显示的变量。
过滤器是这样的: {{ name|lower }}
。这将显示 lower
过滤器过滤后的 {{ name }}
变量的值,该过滤器将文本转换为小写。使用管道(|
)来应用过滤器。
过滤器可以“链式的”。一个过滤器的输出被应用到下一个过滤器。{{ text|escape|linebreaks }}
是一个常用的成语,用于转义文本内容,然后将换行符转换为 <p>
标签。
有些过滤器需要参数。一个过滤器的参数是这样的: {{ bio|truncatewords:30 }}
。这将显示 bio
变量的前 30 个字。
包含空格的过滤器参数必须加引号;例如,要连接一个包含逗号和空格的列表,你可以使用 {{ list|join:", " }}
。
Django 提供了大约 60 个内置的模板过滤器。你可以在 内置过滤器参考 中阅读它们。为了让你了解这些模板过滤器,这里有一些比较常用的模板过滤器:
default
If a variable is false or empty, use given default. Otherwise, use the value of the variable. For example:
{{ value|default:"nothing" }}
如果 value
没有提供或者为空,那么他将显示为“nothing
”。
length
Returns the length of the value. This works for both strings and lists. For example:
{{ value|length }}
如果 value
为 ['a', 'b', 'c', 'd']
, 那么他将被显示为 4
。
filesizeformat
Formats the value like a "human-readable" file size (i.e. '13 KB'
,
'4.1 MB'
, '102 bytes'
, etc.). For example:
{{ value|filesizeformat }}
如果 value
是 123456789,则输出为 117.7 MB
。
这些只是几个例子,请参阅 内置过滤器参考 的完整列表。
你还可以创建自己的自定义模板过滤器,请参见 如何编写自定义的模板标签和过滤器。
参见
Django 的管理界面可以包含一个完整的给定网站的所有模板标签和过滤器的参考。参见 Django 管理文档生成器。
标签是这样的: {% tag %}
。标签比变量更复杂。有的在输出中创建文本,有的通过执行循环或逻辑来控制流程,有的将外部信息加载到模板中,供以后的变量使用。
有些标签要求有开始和结束标签(即 {% tag %} ... tag contents ... {% endtag %}
)。
Django 有二十多个内置的模板标签。你可以在 内置标签参考 中阅读所有关于它们的信息。为了让你了解这些标签,这里有一些比较常用的标签。
for
Loop over each item in an array. For example, to display a list of athletes
provided in athlete_list
:
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
if
、elif
和 else
Evaluates a variable, and if that variable is "true" the contents of the block are displayed:
{% if athlete_list %}
Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
Athletes should be out of the locker room soon!
{% else %}
No athletes.
{% endif %}
在上述情况下,如果 athlete_list
不为空,则会通过 { athlete_list|length }}
变量显示运动员的数量。否则,如果 athlete_in_locker_room_list
不为空,则会显示“Athletes should be out...”的消息。如果两个列表都是空的,将显示“No athletes.”。
You can also use filters and various operators in the if
tag:
{% if athlete_list|length > 1 %}
Team: {% for athlete in athlete_list %} ... {% endfor %}
{% else %}
Athlete: {{ athlete_list.0.name }}
{% endif %}
虽然上面的例子是可行的,但要注意大多数模板过滤器都会返回字符串,所以使用过滤器进行数学比较一般不会像你预期的那样。
block
和 extends
同样,以上只是整个列表的一部分,完整的列表请参见 内置标签参考。
你也可以创建你自己的模板标签;参见 如何编写自定义的模板标签和过滤器。
参见
Django 的管理界面可以包含一个完整的给定网站的所有模板标签和过滤器的参考。参见 Django 管理文档生成器。
要对模板中的部分行进行注释,请使用注释语法。{# #}
。
For example, this template would render as 'hello'
:
{# greeting #}hello
A comment can contain any template code, invalid or not. For example:
{# {% if foo %}bar{% else %} #}
这种语法只能用于单行注释(在 {#
和 #}
定界符之间不允许使用换行)。如果你需要对模板的多行部分进行注释,请参见 comment
标签。
Django 的模板引擎中最强大的——也是最复杂的——部分是模板继承。模板继承允许你建立一个基本的“骨架”模板,它包含了你网站的所有常用元素,并定义了子模板可以覆盖的 块。
Let's look at template inheritance by starting with an example:
<!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
标签所做的就是告诉模板引擎,子模板可以覆盖模板的这些部分。
A child template might look like this:
{% 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”。
At that point, the template engine will notice the three block
tags
in base.html
and replace those blocks with the contents of the child
template. Depending on the value of blog_entries
, the output might look
like:
<!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 }}
,这可以成为一种强大的小规模定制方式。完整的例子请参见 扩展复写模板 中的 Overriding templates How-to。
Variables created outside of a {% block %}
using the template
tag as
syntax can't be used inside the block. For example, this template
doesn't render anything:
{% translate "Title" as title %}
{% block content %}{{ title }}{% endblock %}
For extra readability, you can optionally give a name to your
{% endblock %}
tag. For example:
{% block content %}
...
{% endblock content %}
在较大的模板中,这种技术可以帮助你看到哪些 {% block %}
标签正在被关闭。
{% block %}
tags are evaluated first. That's why the content
of a block is always overridden, regardless of the truthiness of surrounding
tags. For example, this template will always override the content of the
title
block:
{% if change_title %}
{% block title %}Hello!{% endblock title %}
{% endif %}
最后,请注意,你不能在同一个模板中定义多个同名的 block
标签。之所以有这种限制,是因为块标签可以“双向”工作。也就是说,一个块标签不只是提供一个洞来填补——它还定义了填补 父 模板中洞的内容。如果一个模板中有两个名称相似的 block
标签,那么该模板的父标签就不知道该使用哪一个块的内容。
When generating HTML from templates, there's always a risk that a variable will include characters that affect the resulting HTML. For example, consider this template fragment:
Hello, {{ name }}
At first, this seems like a harmless way to display a user's name, but consider what would happen if the user entered their name as this:
<script>alert('hello')</script>
With this name value, the template would be rendered as:
Hello, <script>alert('hello')</script>
...这意味着浏览器会弹出一个 JavaScript 提示框!
同样,如果名称中包含一个 '<'
符号,像这样呢?
<b>username
That would result in a rendered template like this:
Hello, <b>username
...which, in turn, would result in the remainder of the web page being in bold!
Clearly, user-submitted data shouldn't be trusted blindly and inserted directly into your web pages, because a malicious user could use this kind of hole to do potentially bad things. This type of security exploit is called a Cross Site Scripting (XSS) attack.
为了避免这个问题,你有两个选择:
escape
过滤器来运行每个不受信任的变量(如下文所述),它可以将潜在的有害 HTML 字符转换为无害的字符。在 Django 的前几年,这是默认的解决方案,但问题是它把责任推给了 你,开发者/模板作者,以确保你的一切都被转义。很容易忘记对数据进行转义。在 Django 中,默认情况下,每个模板都会自动转义每个变量标签的输出。具体来说,这五个字符会被转义:
<
被替换为 <
>
被替换为 >
'
(单引号)被替换为 '
"
(双引号)被替换为 "
&
被替换为 &
我们再次强调,这个行为是默认开启的。如果你使用的是 Django 的模板系统,你就会受到保护。
如果你不希望数据被自动转义, 你可以在站点、模板,、变量这三个层面关闭它:
为什么要关闭它?因为有时,模板变量包含的数据是你希望以原始 HTML 的形式呈现的,在这种情况下,你不希望其内容被转义。例如,你可能在数据库中存储了一个 HTML 片段,并希望将其直接嵌入到模板中。或者,你可能会使用 Django 的模板系统来生成非 HTML 的文本——比如说,电子邮件。
要禁用单个变量的自动转义,请使用:tfilter:safe filter:
This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}
Think of safe as shorthand for safe from further escaping or can be
safely interpreted as HTML. In this example, if data
contains '<b>'
,
the output will be:
This will be escaped: <b>
This will not be escaped: <b>
To control auto-escaping for a template, wrap the template (or a particular
section of the template) in the autoescape
tag, like so:
{% autoescape off %}
Hello {{ name }}
{% endautoescape %}
The autoescape
tag takes either on
or off
as its argument. At
times, you might want to force auto-escaping when it would otherwise be
disabled. Here is an example template:
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
标签包含的模板,就像所有的块标签一样。例如:
{% autoescape off %}
<h1>{% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
{% endautoescape %}
{% extends "base.html" %}
{% block title %}This & that{% endblock %}
{% block content %}{{ greeting }}{% endblock %}
Because auto-escaping is turned off in the base template, it will also be
turned off in the child template, resulting in the following rendered
HTML when the greeting
variable contains the string <b>Hello!</b>
:
<h1>This & that</h1>
<b>Hello!</b>
一般来说,模板作者不需要很担心自动转义的问题。Python 方面的开发人员(编写视图和自定义过滤器的人)需要考虑在哪些情况下数据不应该被转义,并适当地标记数据,所以事情在模板中 Just Work。
如果你创建的模板可能会在你不确定是否启用自动转义的情况下使用,那么在任何需要转义的变量中添加一个 escape
过滤器。当自动转义开启时,不会出现 escape
过滤器 双重转义 数据的危险 —— escape
过滤器不会影响自动转义的变量。
As we mentioned earlier, filter arguments can be strings:
{{ data|default:"This is a string literal." }}
所有的字符串文字都是在 没有 任何自动转义的情况下插入到模板中的——就好像它们都通过了 safe
过滤器一样。这背后的原因是,模板作者可以控制字符串文字的内容,因此他们可以确保在编写模板时正确地转义文本。
This means you would write :
{{ data|default:"3 < 2" }}
...rather than:
{{ data|default:"3 < 2" }} {# Bad! Don't do this. #}
这并不影响来自变量本身的数据的处理。如果有必要,变量的内容仍然会被自动转义,因为它们超出了模板作者的控制范围。
Most method calls attached to objects are also available from within templates. This means that templates have access to much more than just class attributes (like field names) and variables passed in from views. For example, the Django ORM provides the "entry_set" syntax for finding a collection of objects related on a foreign key. Therefore, given a model called "comment" with a foreign key relationship to a model called "task" you can loop through all comments attached to a given task like this:
{% for comment in task.comment_set.all %}
{{ comment }}
{% endfor %}
Similarly, QuerySets provide a count()
method
to count the number of objects they contain. Therefore, you can obtain a count
of all comments related to the current task with:
{{ task.comment_set.all.count }}
你也可以访问你在自己的模型上明确定义的方法:
class Task(models.Model):
def foo(self):
return "bar"
{{ task.foo }}
由于 Django 有意限制了模板语言中可用的逻辑处理量,所以不可能将参数传递给从模板内访问的方法调用。数据应该在视图中计算,然后传递给模板显示。
Certain applications provide custom tag and filter libraries. To access them in
a template, ensure the application is in INSTALLED_APPS
(we'd add
'django.contrib.humanize'
for this example), and then use the load
tag in a template:
{% load humanize %}
{{ 45000|intcomma }}
在上面的例子中, load
标签加载了 humanize
标签库,然后使 intcomma
过滤器可以使用。如果你已经启用了 django.contrib.admindocs
,你可以在你的管理中的文档区查找安装中的自定义库列表。
The load
tag can take multiple library names, separated by spaces.
Example:
{% load humanize i18n %}
参见 如何编写自定义的模板标签和过滤器 了解如何编写自己的模板库。
9月 22, 2023