本文将介绍Django模版系统的语法。如果您需要更多该系统如何工作的技术细节,以及希望扩展它,请浏览 The Django template language: for Python programmers.
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模版。在线上世界,我们在email、Javascript和CSV中使用它。你可以在任何基于文本的格式中使用这个模版语言。
还有,让人类编辑XML简直是施虐狂的做法!
变量看起来就像是这样: {{ variable }}。当模版引擎遇到一个变量,它将计算这个变量,然后用结果替换掉它本身。变量的命名包括任何字母数字以及下划线 ("_")的组合。点(".") 也在会变量部分中出现,不过它有特殊的含义,我们将在后面说明。重要的是, 你不能在变量名称中使用空格和标点符号。
点号(.)用来访问变量的属性。
幕后
从技术上来说,当模版系统遇到点("."),它将以这样的顺序查询:
如果计算结果的值是可调用的,它将被无参数的调用。调用的结果将成为模版的值。
这个查询顺序,会在优先于字典查询的对象上造成意想不到的行为。例如,思考下面的代码片段,它尝试循环 collections.defaultdict:
{% for k, v in defaultdict.iteritems %}
Do something with k and v here...
{% endfor %}
因为字典查询首先发生,行为奏效了并且提供了一个默认值,而不是使用我们期望的 .iteritems() 方法。在这种情况下,考虑首先转换成字典。
在前文的例子中, {{ section.title }}将被替换为 section 对象的 title 属性。
如果你使用的变量不存在, 模版系统将插入 string_if_invalid 选项的值, 它被默认设置为'' (空字符串) 。
注意,像{{ foo.bar }} 这种模版表达式中的“bar”, 如果在模版上下文中存在,将解释为一个字面意义的字符串而不是使用变量“bar”的值 。
您可以通过使用 过滤器来改变变量的显示。
过滤器看起来是这样的:{{ name|lower }}。这将在变量 {{ name }} 被过滤器 lower 过滤后再显示它的值,该过滤器将文本转换成小写。使用管道符号 (|)来应用过滤器。
过滤器能够被“串联”。一个过滤器的输出将被应用到下一个。{{ text|escape|linebreaks }} 就是一个常用的过滤器链,它编码文本内容,然后把行打破转成<p> 标签。
一些过滤器带有参数。过滤器的参数看起来像是这样: {{ bio|truncatewords:30 }}。这将显示 bio 变量的前30个词。
过滤器参数包含空格的话,必须被引号包起来;例如,使用逗号和空格去连接一个列表中的元素,你需要使用 {{ list|join:", " }}。
Django提供了大约六十个内置的模版过滤器。你可以在 内置过滤器参考手册中阅读全部关于它们的信息。为了体验一下它们的作用,这里有一些常用的模版过滤器:
如果一个变量是false或者为空,使用给定的默认值。否则,使用变量的值。例如:
{{ value|default:"nothing" }}
如果 value没有被提供,或者为空, 上面的例子将显示“nothing”。
返回值的长度。它对字符串和列表都起作用。例如:
{{ value|length }}
如果 value 是 ['a', 'b', 'c', 'd'],那么输出是 4。
将该数值格式化为一个 “人类可读的” 文件容量大小 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。例如:
{{ value|filesizeformat }}
如果 value 是 123456789,输出将会是 117.7 MB。
再说一下,这仅仅是一些例子;查看 内置过滤器参考手册 来获取完整的列表。
您也可以创建自己的自定义模版过滤器;参考 自定义模版标签和过滤器。
更多
Django的admin接口提供了一个模板标记和可用的过滤器的完整参考。请参见The Django admin documentation generator。
标签看起来像是这样的: {% tag %}。标签比变量复杂得多:有些用于在输出中创建文本,有些用于控制循环或逻辑,有些用于加载外部信息到模板中供以后的变量使用。
有些标签需要开始标签和结束标签(例如{% tag %} ... tag contents ... {% endtag %})。
Django自带了大约24个内置的模版标签。你可以在 内置标签参考手册中阅读全部关于它们的内容。为了体验一下它们的作用,这里有一些常用的标签:
循环数组中的每个元素。例如,显示 athlete_list中提供的运动员列表:
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
计算一个变量,并且当变量是“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 %}
当上面的例子工作时,需要注意,大多数模版过滤器返回字符串,所以使用过滤器做数学的比较通常都不会像您期望的那样工作。length 是一个例外。
再说一下,上面的仅仅是整个列表的一部分;查看 内置标签参考手册 来获取完整的列表。
您也可以创建您自己的自定义模版标签;参考 自定义模版标签和过滤器。
更多
Django的管理界面可以包含对给定网站可用的所有模板标记和过滤器的完整引用。请参见The Django admin documentation generator。
要注释模版中一行的部分内容,使用注释语法 {# #}.
例如,这个模版将被渲染为 'hello':
{# greeting #}hello
注释可以包含任何模版代码,有效的或者无效的都可以。例如:
{# {% if foo %}bar{% else %} #}
这个语法只能被用于单行注释 (在 {# 和 #} 分隔符中,不允许有新行)。如果你需要注释掉模版中的多行内容,请查看 comment 标签。
Django模版引擎中最强大也是最复杂的部分就是模版继承了。模版继承可以让您创建一个基本的“骨架”模版,它包含您站点中的全部元素,并且可以定义能够被子模版覆盖的 blocks 。
通过从下面这个例子开始,可以容易的理解模版继承:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css" />
<title>{% block title %}My amazing site{%/span> 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骨架。“子模版”的工作是用它们的内容填充空的blocks。
在这个例子中, block 标签定义了三个可以被子模版内容填充的block。 block 告诉模版引擎: 子模版可能会覆盖掉模版中的这些位置。
子模版可能看起来是这样的:
{% extends "base.html" %}/span>
{% 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 标签,并用子模版中的内容来替换这些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,所以系统使用了父模版中的值。父模版的 {% block %} 标签中的内容总是被用作备选内容(fallback)。
您可以根据需要使用多级继承。使用继承的一个常用方式是类似下面的三级结构:
这种方式使代码得到最大程度的复用,并且使得添加内容到共享的内容区域更加简单,例如分支范围内的导航。
这里是使用继承的一些提示:
如果你在模版中使用 {% extends %} 标签,它必须是模版中的第一个标签。其他的任何情况下,模版继承都将无法工作。
在base模版中设置越多的 {% block %} 标签越好。请记住,子模版不必定义全部父模版中的blocks,所以,你可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。
如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个 {% block %} 中。
如果需要获取父模板中的block 的内容,可以使用{{ block.super }} 变量。如果你想要在父block 中新增内容而不是完全覆盖它,它将非常有用。使用{{ block.super }} 插入的数据不会被自动转义(参见下一节),因为父模板中的内容已经被转义。
为了更好的可读性,你也可以给你的 {% endblock %} 标签一个 名字 。例如:
{% block content %}
...
{% endblock content %}
在大型模版中,这个方法帮你清楚的看到哪一个 {% block %} 标签被关闭了。
最后,请注意不能在一个模版中定义多个相同名字的block 标签。这个限制的存在是因为block标签的作用是“双向”的。这个意思是,block 标签不仅提供了一个坑去填,它定义向父模版的坑中所填的内容。如果在一个模版中有两个名字一样的 block 标签,模版的父模版将不知道使用哪个block的内容。
当从模版中生成HTML时,总会有这样一个风险:值可能会包含影响HTML最终呈现的字符。例如,思考这个模版片段:
Hello, {{ name }}
首先,它看起来像是一个无害的方式来显示用户的名字,但是设想一下,如果用户像下面这样输入他的名字,会发生什么:
<script>alert('hello')</script>
使用这个名字值,模版将会被渲染成这样:
Hello, <script>alert('hello')</script>
...这意味着,浏览器将会弹出一个Javascript警示框!
类似的,如果名字包含一个 '<' 符号(比如下面这样),会发生什么呢?
<b>username
这将会导致模版被渲染成这样:
Hello, <b>username
...进而这将导致网页的剩余部分都被加粗!
显然,用户提交的数据都被不应该被盲目的信任,并且被直接插入到你的网页中,因为一个怀有恶意的用户可能会使用这样的漏洞来做一些可能的坏事。这种类型的安全问题被叫做 跨站脚本(Cross Site Scripting) (XSS) 攻击。
为避免这个问题,你有两个选择:
默认情况下,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>
要控制模板上的自动转义,将模板(或者模板中的特定区域)包裹在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 标签包含的模板,就像所有block标签那样。例如:
{% autoescape off %}
<h1>{% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
{% endautoescape %}
{% extends "base.html" %}
{% block title %}This & that{% endblock %}
{% block content %}{{ greeting }}{% endblock %}
由于自动转义标签在base模板中关闭,它也会在child模板中关闭,导致当 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. #}
这不会影响来源于变量自身的数据。变量的内容在必要时仍然会自动转义,因为它们不受模板作者的控制。
大多数对象上的方法调用同样可用于模板中。这意味着模板能够访问到的不仅仅是类属性(比如字段名称)和视图中传入的变量。例如,Django ORM提供了“entry_set” 语法用于查找关联到外键的对象集合。所以,如果模型“comment” 有一个外键关联到模型“task” ,你可以根据task 遍历其所有的comments,像这样:
{% 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,你可以查询admin站点中的文档部分,来寻找你的安装中的自定义库列表。
load标签可以接受多个库名称,由空格分隔。例如:
{% load humanize i18n %}
关于编写你自己的自定义模板库,详见自定义模板标签和过滤器。
2015年5月13日