本文档解释了 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.
使用点号(.
)来访问一个变量的属性。
幕后
从技术上讲,当模板系统遇到一个点时,它会按照以下顺序尝试进行查找:
如果产生的值是可调用对象,则调用时不含参数。调用的结果成为模板值。
这种查找顺序可能会对覆盖字典查找的对象造成一些意外的行为。例如,考虑下面的代码片段,它试图在 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
如果一个变量为 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 管理文档生成器。
标签是这样的: {% tag %}
。标签比变量更复杂。有的在输出中创建文本,有的通过执行循环或逻辑来控制流程,有的将外部信息加载到模板中,供以后的变量使用。
有些标签要求有开始和结束标签(即 {% tag %} ... tag contents ... {% endtag %}
)。
Django 有二十多个内置的模板标签。你可以在 内置标签参考 中阅读所有关于它们的信息。为了让你了解这些标签,这里有一些比较常用的标签。
for
循环浏览数组中的每个项目。 例如,要显示 athlete_list
中提供的运动员名单:
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
if
、elif
和 else
判断一个变量的布尔值,如果该是“true”,则显示块的内容:
{% 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.”。
你也可以在 if
标签中使用过滤器和各种操作符:
{% if athlete_list|length > 1 %}
Team: {% for athlete in athlete_list %} ... {% endfor %}
{% else %}
Athlete: {{ athlete_list.0.name }}
{% endif %}
虽然上面的例子是可行的,但要注意大多数模板过滤器都会返回字符串,所以使用过滤器进行数学比较一般不会像你预期的那样。
block
和 extends
同样,以上只是整个列表的一部分,完整的列表请参见 内置标签参考。
你也可以创建你自己的模板标签;参见 自定义模板标签和过滤器。
参见
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”。
这时,模板引擎会注意到 base.html
中的三个 block
标签,然后用子模板的内容替换这些块。根据 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 }}
,这可以成为一种强大的小规模定制方式。完整的例子请参见 扩展复写模板 中的 Overriding templates How-to。
在 {% block %}
之外使用模板标签 as
语法创建的变量不能在块中使用。例如,这个模板不会呈现任何东西:
{% translate "Title" as title %}
{% block content %}{{ title }}{% endblock %}
为了增加可读性,你可以给你的 {% endblock %}
标签 起一个 名字。例如:
{% block content %}
...
{% endblock content %}
在较大的模板中,这种技术可以帮助你看到哪些 {% block %}
标签正在被关闭。
最后,请注意,你不能在同一个模板中定义多个同名的 block
标签。之所以有这种限制,是因为块标签可以“双向”工作。也就是说,一个块标签不只是提供一个洞来填补——它还定义了填补 父 模板中洞的内容。如果一个模板中有两个名称相似的 block
标签,那么该模板的父标签就不知道该使用哪一个块的内容。
当从模板中生成 HTML 时,总是存在一个风险,即变量会包含影响生成的 HTML 的字符。例如,考虑这个模板片段:
Hello, {{ name }}
乍一看,这似乎是一种无伤大雅的显示用户姓名的方式,但考虑一下,如果用户将自己的姓名输入为:
<script>alert('hello')</script>
有了这个名称值,模板将呈现为:
Hello, <script>alert('hello')</script>
...这意味着浏览器会弹出一个 JavaScript 提示框!
同样,如果名称中包含一个 '<'
符号,像这样呢?
<b>username
这样一来,呈现出来的模板就会是这样的:
Hello, <b>username
...这又会导致网页的其余部分被加粗!
显然,不应该盲目相信用户提交的数据,直接插入到你的网页中,因为恶意用户可能会利用这种漏洞做潜在的坏事。这种类型的安全漏洞被称为 跨站点脚本 (XSS)攻击。
为了避免这个问题,你有两个选择:
escape
过滤器来运行每个不受信任的变量(如下文所述),它可以将潜在的有害 HTML 字符转换为无害的字符。在 Django 的前几年,这是默认的解决方案,但问题是它把责任推给了 你,开发者/模板作者,以确保你的一切都被转义。很容易忘记对数据进行转义。在 Django 中,默认情况下,每个模板都会自动转义每个变量标签的输出。具体来说,这五个字符会被转义:
<
被替换为 <
>
被替换为 >
'
(单引号)被替换为 '
"
(双引号)被替换为 "
&
被替换为 &
我们再次强调,这个行为是默认开启的。如果你使用的是 Django 的模板系统,你就会受到保护。
如果你不希望数据被自动转义, 你可以在站点、模板,、变量这三个层面关闭它:
为什么要关闭它?因为有时,模板变量包含的数据是你希望以原始 HTML 的形式呈现的,在这种情况下,你不希望其内容被转义。例如,你可能在数据库中存储了一个 HTML 片段,并希望将其直接嵌入到模板中。或者,你可能会使用 Django 的模板系统来生成非 HTML 的文本——比如说,电子邮件。
要禁用单个变量的自动转义,请使用 safe
过滤器:
This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}
把 safe 看作是 safe from further escaping 或 can be safely interpreted as HTML 的简写。在这个例子中,如果 data
包含 '<b>'
,输出将是:
This will be escaped: <b>
This will not be escaped: <b>
要控制模板的自动转义,可以用 autescape
标签来包装模板(或模板的某一特定部分),就像这样:
{% 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
标签包含的模板,就像所有的块标签一样。例如:
{% autoescape off %}
<h1>{% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
{% endautoescape %}
{% 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 方面的开发人员(编写视图和自定义过滤器的人)需要考虑在哪些情况下数据不应该被转义,并适当地标记数据,所以事情在模板中 Just Work。
如果你创建的模板可能会在你不确定是否启用自动转义的情况下使用,那么在任何需要转义的变量中添加一个 escape
过滤器。当自动转义开启时,不会出现 escape
过滤器 双重转义 数据的危险 —— escape
过滤器不会影响自动转义的变量。
正如我们前面提到的,过滤器的参数可以是字符串:
{{ data|default:"This is a string literal." }}
所有的字符串文字都是在 没有 任何自动转义的情况下插入到模板中的——就好像它们都通过了 safe
过滤器一样。这背后的原因是,模板作者可以控制字符串文字的内容,因此他们可以确保在编写模板时正确地转义文本。
这意味着你应当这么写:
{{ data|default:"3 < 2" }}
...而不是:
{{ data|default:"3 < 2" }} {# Bad! Don't do this. #}
这并不影响来自变量本身的数据的处理。如果有必要,变量的内容仍然会被自动转义,因为它们超出了模板作者的控制范围。
大多数附加到对象上的方法调用也可以从模板中获得。这意味着模板可以访问的东西远不止是类属性(如字段名)和从视图中传递过来的变量。例如,Django ORM 提供了 "entry_set" 语法,用于查找一个外键相关的对象集合。因此,给定一个名为“comment”的模型与一个名为“task”的模型的外键关系,你可以像这样循环浏览所有附加在给定任务上的评论:
{% for comment in task.comment_set.all %}
{{ comment }}
{% endfor %}
同样,QuerySets 提供了一个 count()
方法来计算它们所包含的对象数量。因此,你可以通过以下方法获得与当前任务相关的所有注释的数量:
{{ task.comment_set.all.count }}
你也可以访问你在自己的模型上明确定义的方法:
class Task(models.Model):
def foo(self):
return "bar"
{{ task.foo }}
由于 Django 有意限制了模板语言中可用的逻辑处理量,所以不可能将参数传递给从模板内访问的方法调用。数据应该在视图中计算,然后传递给模板显示。
某些应用程序提供了自定义标签和过滤器库。要在模板中访问它们,请确保应用程序在 INSTALLED_APPS
中(本例中我们会添加 'django.contrib.humanize'
),然后在模板中使用 load
标签:
{% load humanize %}
{{ 45000|intcomma }}
在上面的例子中, load
标签加载了 humanize
标签库,然后使 intcomma
过滤器可以使用。如果你已经启用了 django.contrib.admindocs
,你可以在你的管理中的文档区查找安装中的自定义库列表。
load
标签可以使用多个库名,用空格分隔。例如:
{% load humanize i18n %}
参见 自定义模板标签和过滤器 了解如何编写自己的模板库。
12月 07, 2021