Django模板语言
关于本文档
本文档介绍了Django模板系统语言的语法. 如果你希望从一个更技术的角度来看它是如何工作的和如何扩展 它,请参阅 The Django template language: For Python programmers .
Django模板语言设计的初衷是为了获得效率和易用性之间的平衡. HTML使用者会感觉到他设计的很舒服.如果你有使用其他基于文本的模板语言如 Smarty 或 CheetahTemplate 的使用经验,你会感觉到使用Django模板语言和那些东西没什么两样.
哲理
如果你有编程背景,或者你使用过PHP等嵌入HTML的程序语言,你要牢记, Django的模板系统是不是简单的把Python嵌入到HTML中. 它的设计是:模板系统旨在展示内容,而不是程序逻辑.
Django的模板系统提供的标签,其功能类似于一些编程结构 – if 标签用于布尔值测试, for 标签用来做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中. 您可以在任何文本格式中使用它.
哦,还有一件事: 让人写XML, 那就是虐待!
变量
变量看起来是这样的: {{ 变量 }}. 当模板系统遇到一个变量, 他直接被替换为他的值. 变量名由字母数字和下划线 ("_")组成. 点字符 (".") 也可以出现在变量名中,但他有特殊的含义.比如下面的. 更重要的是, 变量名中不能有空格或其他的标点符号.
使用点 (.) 来访问变量的属性.
幕后
从技术上讲,当模板系统遇到一个点,它会尝试下面的动作,顺序如下:
- 字典查询
- 属性查询
- 方法调用
- 列表索引查找
在上面的例子中, {{ section.title }} 将被替换为这个对象的title属性值.
如果你使用一个不存在的变量,模板系统会替换它为settings中的 TEMPLATE_STRING_IF_INVALID 值,在默认情况下他被指定为 '' (空字符串).
过滤器(Filters)
通过使用 过滤器 ,您可以修改变量的显示.
过滤器用起来像这样: {{ name|lower }} . 通过过滤器 lower 变量 {{ name }} 变为了小写字符. 通过管道符 (|) 间隔变量和过滤器来使用过滤器.
过滤器可以是 “链式的”. 一个过滤器的输出作为下一个过滤器的输入. {{ text|escape|linebreaks }} 是一种常用的转换方式, 在这之后换行符被替换为了 <p> 标签.
有些过滤器是带有参数的. 一个带参数的过滤器看起来像这样: {{ bio|truncatewords:30 }}. 他会显示 bio 变量的前30个字符.
如果过滤器的参数包含空格, 那么参数必须被引号包起来; 比如, 用逗号和空格来间隔列表内的元素, 你可以像这样用{{ list|join:", " }}.
Django提供了约30个内置的过滤器. 有关这些过滤器你可以在 内置过滤器参考 中找到更详尽的描述. 这儿有一些过滤器的例子,可以让你尝到一些过滤器的甜头:
- default
-
如果一个变量是假的或空的,使用默认值. 否则,使用变量的值
例如:
{{ value|default:"nothing" }}
如果 value 没有提供或者为空,那么他将显示为 “nothing” 。
- length
-
返回的值的长度。可以被用于字符串或者列表;
for example:
{{ value|length }}
如果 value 为 ['a', 'b', 'c', 'd'], 那么他将被显示为 4.
- striptags
-
去掉所有 [X]HTML 标签. 比如:
{{ value|striptags }}
如果 value 为 "<b>Joel</b> <button>is</button> a <span>slug</span>", 那么输出就会是 "Joel is a slug".
这些只是几个例子,请参阅 内置过滤器参考 的完整列表。
您还可以创建自己的自定义模板过滤器,请参见 Custom template tags and filters.
查看更多
Django的管理界面可以在一个给定的站点中包含所有的模板标签和过滤器的完整参考。 请参阅 The Django admin documentation generator.
注释(Comments)
要注释掉行的一部分,在模板中使用注释语法: {# #}.
例如,该模板将呈现为 '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{% 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 标签定义了三个可以被子模板填充的块. 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 模板. 这些模板中包含特定的设计/风格.
- 为每一种类型的页面创建一个模板, 比如 news article 或 blog 内容. 这些模板扩展上一级模板的相应分类.
这种方法最大限度地重用了代码并能很容易将元素添加到公用的区域, 比如分类导航.
这里有一些关于模板继承的提示
-
如果你在模板中使用 {% extends %}, 他必须位于模板的最开始. 如果在其他的部分声明, 则不生效.
-
在你的基础模板中使用更多的 {% block %} 是一个好的方法. 请记住, 子模板不需要定义所有父模板中的块, 所以你可以在若干的块中填充默认值, 然后定义之后需要的块. 有更多的可用块总是更好的.
-
如果你发现自己在许多模板中有重复内容的了, 这可能意味着你要移动这些内容到父模板的 {% block %} 中.
-
如果你需要得到父模板块中的内容, 可以用 {{ block.super }} 变量. 这在你想使用父模板块中的内容又想在其中追加内容时是非常有用的. 使用 {{ block.super }} 插入的数据不会被自动转义 (参见 下一节), 因为它已经被转义了. 如果需要转义, 可以在父模板中转义.
-
更好的可读性, 你可以给 {% endblock %} 标签定义一个 名字. 比如:
{% block content %} ... {% endblock content %}
在一个较长的模板中, 这个方法可以让你知道是哪一个 {% block %} 标签定义结束了.
最后请注意, 你不能在同一模板中定义多个具有相同名称的块标签. 存在这个限制是因为 block 标签是”双向”定义的. 也就是说, 它不仅指定了子模板要填充哪个”洞”, 也说明了子模板要引用那些父模板的内容. 所以在有多个同名 block 标签时, 父模板就不知道到底要引用哪个块的内容了.
自动HTML转义
从模板生成HTML时, 总是有一种风险, 即一个变量将会影响生成的HTML字符. 例如, 考虑这个模板片段:
Hello, {{ name }}.
用这种方法显示用户的姓名似乎没什么问题, 但考虑一下, 如果用户输入下面的文字作为他的名字, 会发生什么:
<script>alert('hello')</script>
当名字为这个值, 该模板将呈现为:
Hello, <script>alert('hello')</script>
...这意味着浏览器将弹出一个JavaScript警告框!
同样的, 如果名字中包含一个 '<' 符号, 像这样:
.. code-block:: html
<b>username
这将导致呈现出这样的模板:
Hello, <b>username
...这意味着在此之后的文字将呈现为粗体!
显然, 用户提交的数据是不可靠的且不应被直接插入到您的网页, 因为恶意用户可以使用这种潜在的漏洞做不好的事情. 这种类型的安全漏洞被称为 Cross Site Scripting (跨站脚本) (XSS) 攻击.
为了避免这个问题, 你有两种选择:
- 一, 确保让不受信任的变量经过了 escape 过滤器 (下文介绍), 将危险的HTML字符替换为无害的HTML转义字符. 这在Django最初时是默认的方案, 它使得 你, 开发者, 有责任确保在必要时转义了变量. 但是人们很容易忽略它.
- 二, 你可以用Django的自动HTML转义. 本节的其余部分介绍了自动转义如何工作.
在Django中, 默认每个模板会自动转义输出的每一个变量标签. 具体来说, 这五个字符会被转义:
- < 被替换为 <
- > 被替换为 >
- ' (单引号) 被替换为 '
- " (双引号) 被替换为 "
- & 被替换为 &
再次强调, 这种行为是默认的. 如果你使用的是Django的模板系统, 你是受保护的.
如何关闭它
如果你不希望数据被自动转义, 你可以在站点/模板/变量这三个层面关闭它.
为什么你会想要把它关掉? 因为有时候, 你希望把模板变量中包含的数据作为原始的HTML渲染, 在这种情况下, 你不希望自己的内容被转义. 例如, 你可能在你的数据库的BLOB字段存储HTML, 并希望直接嵌入到您的模板. 或者你使用Django的模板系统生成不是HTML的文本 – 比如电子邮件正文.
对于单个变量
要为一个单独的变量禁用自动转义, 使用 safe 过滤器:
这个会被自动转义: {{ data }}
这个不会被自动转义: {{ data|safe }}
safe 可以被认为是 safe from further escaping 或 can be safely interpreted as HTML 的简写. 在这个例子中, 如果 data 是 '<b>', 那么输出将会是:
这个会被自动转义: <b>
这个不会被自动转义: <b>
对于模板文本块
要在模板中控制自动转义, 可以在整个模板 (或者模板的特定区域) 使用 autoescape 标签, 如:
{% autoescape off %}
Hello {{ name }}
{% endautoescape %}
autoescape 标签接受 on 或 off 作为参数. 有时, 你希望自动转义在某个区域被禁用. 如这个模板的例子:
自动转义是默认被开启的. 你好 {{ name }}
{% autoescape off %}
这个不会被自动转义: {{ data }}.
这个也不会: {{ other_data }}
{% autoescape on %}
自动转义被开启: {{ 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 %}
由于自动转义在base模板中被关闭了, 所以在子模板中他也是被关闭了的, 导致了在呈现HTML时, greeting 变量被输出为 <b>Hello!</b>:
<h1>This & that</h1>
<b>Hello!</b>
注意
一般情况下, 模板的开发者并不需要对自动转义太过关注. 这是编写Python的开发者 (写视图和写自定义模板标签的人) 需要考虑的事. 所以, 你只需要做和模板有关的活.
如果你不确定模板在何时会进行自动转义(或不进行), 那么就向所有需要转义的变量添加 escape 过滤器. 当自动转义被打开, 这么做不会有任何问题, escape 过滤器不会再次转义 – escape 过滤器不会影响自动转义的变量.
字符串和自动转义
正如我们前面提到的, 过滤器参数可以是字符串:
{{ data|default:"This is a string literal." }}
这些作为过滤器参数的字符串 都不经过 自动转义, 他们是被认为经过了 safe 过滤器的. 这么做的原因是, 模板的开发者是在控制字符的输出, 所以他们能确保正确的使用转义后的HTML字面值.
这意味着你应当这么写
{{ data|default:"3 < 2" }}
...而不是这样
{{ data|default:"3 < 2" }} <-- Bad! Don't do this.
这不会影响来自变量本身的数据. 如果必要, 变量本身仍会被自动转义, 因为他们无法控制的模板作者.
访问方法调用
大多附加到对象上的方法都可以在模板中被调用. 这意味着模板可以从视图中传递过来变量中获得对象属性之外的值. 例如, Django ORM 提供了 “entry_set” 语法 寻找一个外键所关联的对象的集合. 因此, 架设一个叫 “comment” 的模型有外键指向了模型 “task”, 你可以通过给定一个实际的 task , 像这样循环输出它所有的 comment:
{% for comment in task.comment_set.all %}
{{ comment }}
{% endfor %}
同样, QuerySets 提供了一个 count() 方法来统计它所包含的对象数目. 因此, 您可以得到当前 task 相关的所有 comment 的数目:
{{ task.comment_set.all.count }}
当然, 你可以很容易地访问你自己的模型中明确地定义的方法:
# In model
class Task(models.Model):
def foo(self):
return "bar"
# In template
{{ task.foo }}
由于Django有意限制了模板中语言逻辑的处理, 所以不能在模板内调用方法时传递参数. 数据应当在视图中计算, 然后再传递给模板.
自定义标签和过滤器库
某些应用提供了自定义的标签和过滤器库. 要在模板中访问这些, 应使用 load 标签:
{% load comments %}
{% comment_form for blogs.entries entry.id with is_public yes %}
在上文中, load 标签加载了 comments 标签库, 然后 comment_form 标签就能用了. 你可以在管理员文档中找到用户自定义库的安装列表.
load 标签可以根据名字加载多个标签库, 名字间使用空格分隔. 例如:
{% load comments i18n %}
请参考 Custom template tags and filters 以获得更多的有关自定义标签的内容.
自定义库和模板继承
当你加载一个自定义标签和过滤器库, 这些被加载的标签/过滤器只能在当前模板中使用 – 其他与此模板有继承关系的(父/子)模板均不能使用这些标签/过滤器.
例如, 如果一个模板 foo.html 使用了 {% load comments %}, 一个子模板 (就是使用了 {% extends "foo.html" %}) 是不能访问 comments 库中的标签和过滤器的. 子模板需要具有自己的 {% load comments %}.
这是因为这能使模板更健全且更好维护.