Django 模板语言 — Django 文档
Django 模板语言
本文档解释了 Django 模板系统的语言语法。 如果您正在寻找关于它如何工作以及如何扩展它的更多技术观点,请参阅 Django 模板语言:Python 程序员 。
Django 的模板语言旨在在强大和易用之间取得平衡。 它旨在让那些习惯于使用 HTML 的人感到舒适。 如果您接触过其他基于文本的模板语言,例如 Smarty 或 Jinja2,您应该对 Django 的模板感到宾至如归。
哲学
如果您有编程背景,或者如果您习惯于将编程代码直接混合到 HTML 中的语言,您需要牢记 Django 模板系统不仅仅是嵌入到 HTML 中的 Python。 这是设计使然:模板系统旨在表达表示,而不是程序逻辑。
Django 模板系统提供了与一些编程结构类似的标签——一个 :ttag:`if` 用于布尔测试的标签,一个 :ttag:`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 模板。 在 World Online,我们将其用于电子邮件、JavaScript 和 CSV。 您可以将模板语言用于任何基于文本的格式。
哦,还有一件事:让人类编辑 XML 是虐待狂!
变量
变量如下所示:模板:Variable
。 当模板引擎遇到一个变量时,它会评估该变量并将其替换为结果。 变量名称由字母数字字符和下划线 ("_"
) 的任意组合组成,但不能以下划线开头。 点 ("."
) 也出现在可变部分中,尽管它具有特殊含义,如下所示。 重要的是,变量名中不能有空格或标点符号。
使用点 (.
) 访问变量的属性。
幕后花絮
从技术上讲,当模板系统遇到一个点时,它会按以下顺序尝试以下查找:
- 字典查找
- 属性或方法查找
- 数字索引查找
如果结果值是可调用的,则不带参数调用它。 调用的结果成为模板值。
对于覆盖字典查找的对象,此查找顺序可能会导致一些意外行为。 例如,考虑以下尝试循环 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
。 这将显示通过 :tfilter:`lower` 过滤器过滤后的 模板:Name
变量的值,该过滤器将文本转换为小写。 使用管道 (|
) 应用过滤器。
过滤器可以“链接”。 一个过滤器的输出应用于下一个。 模板:Text
是用于转义文本内容,然后将换行符转换为 <p>
标签的常用习惯用法。
一些过滤器接受参数。 过滤器参数如下所示:模板:Bio
。 这将显示 bio
变量的前 30 个字。
必须引用包含空格的过滤器参数; 例如,要加入带有逗号和空格的列表,您可以使用 模板:List
。
Django 提供了大约 60 个内置模板过滤器。 您可以在 内置过滤器参考 中阅读有关它们的所有信息。 为了让您了解可用的内容,以下是一些更常用的模板过滤器:
- :tfilter:`默认`
如果变量为 false 或为空,则使用给定的默认值。 否则,使用变量的值。 例如:
{{ value|default:"nothing" }}
如果
value
未提供或为空,则上面将显示“nothing
”。- :tfilter:`长度`
返回值的长度。 这适用于字符串和列表。 例如:
{{ value|length }}
如果
value
为['a', 'b', 'c', 'd']
,则输出将为4
。- :tfilter:`文件大小格式`
将值格式化为“人类可读”的文件大小(即
'13 KB'
、'4.1 MB'
、'102 bytes'
等)。 例如:{{ value|filesizeformat }}
如果
value
为 123456789,则输出将为117.7 MB
。
同样,这些只是几个例子; 有关完整列表,请参阅 内置过滤器参考 。
您还可以创建自己的自定义模板过滤器; 请参阅 自定义模板标签和过滤器 。
模板继承
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 框架文档,您可以将它用于一个两栏页面。 “子”模板的工作是用内容填充空块。
在这个例子中,:ttag:`block` 标签定义了三个子模板可以填充的块。 :ttag:`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 %}
:ttag:`extends` 标签是这里的关键。 它告诉模板引擎这个模板“扩展”了另一个模板。 当模板系统评估这个模板时,它首先找到父模板——在这种情况下,“base.html”。
此时,模板引擎会注意到 base.html
中的三个 :ttag:`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
并包含特定于部分的样式/设计。 - 为每种类型的页面创建单独的模板,例如新闻文章或博客条目。 这些模板扩展了适当的部分模板。
这种方法可以最大限度地重用代码,并有助于将项目添加到共享内容区域,例如部分范围的导航。
以下是使用继承的一些技巧:
如果你使用 :ttag:`{% 扩展 %} ` 在模板中,它必须是该模板中的第一个模板标签。 模板继承将不起作用,否则。
更多的 :ttag:`{% 块 %} ` 基础模板中的标签更好。 请记住,子模板不必定义所有父块,因此您可以在多个块中填写合理的默认值,然后仅定义您以后需要的块。 钩子多总比钩子少好。
如果您发现自己在多个模板中复制了内容,则可能意味着您应该将该内容移动到父模板中的
{% block %}
。如果您需要从父模板获取块的内容,
模板:Block.super
变量可以解决问题。 如果您想添加到父块的内容而不是完全覆盖它,这很有用。 使用模板:Block.super
插入的数据不会自动转义(请参阅 下一节 ),因为如果需要,它已经在父模板中进行了转义。在外部创建的变量 :ttag:`{% 块 %} ` 使用模板标签
as
语法不能在块内使用。 例如,此模板不呈现任何内容:{% trans "Title" as title %} {% block content %}{{ title }}{% endblock %}
为了提高可读性,您可以选择为
{% endblock %}
标签指定 名称 。 例如:{% block content %} ... {% endblock content %}
在较大的模板中,此技术可帮助您查看正在关闭的
{% block %}
标签。
最后,注意不能在同一个模板中定义多个同名的 :ttag:`block` 标签。 存在此限制是因为块标记在“双向”方向上都起作用。 也就是说,块标签不仅提供了一个洞来填充——它还定义了填充 父 中的洞的内容。 如果模板中有两个名称相似的 :ttag:`block` 标签,则该模板的父级将不知道要使用哪个块的内容。
自动 HTML 转义
从模板生成 HTML 时,始终存在变量将包含影响生成的 HTML 的字符的风险。 例如,考虑这个模板片段:
Hello, {{ name }}
起初,这似乎是一种无害的显示用户名的方式,但请考虑如果用户输入他们的姓名会发生什么:
<script>alert('hello')</script>
使用此名称值,模板将呈现为:
Hello, <script>alert('hello')</script>
...这意味着浏览器会弹出一个 JavaScript 警告框!
同样,如果名称包含一个 '<'
符号怎么办,像这样?
<b>username
这将导致渲染模板如下:
Hello, <b>username
……反过来,这会导致网页的其余部分被加粗!
显然,不应盲目信任用户提交的数据并将其直接插入到您的网页中,因为恶意用户可能会利用这种漏洞来做潜在的坏事。 这种类型的安全漏洞称为 跨站点脚本 (XSS) 攻击。
为避免此问题,您有两种选择:
- 一,您可以确保通过 :tfilter:`escape` 过滤器(记录如下)运行每个不受信任的变量,它将潜在有害的 HTML 字符转换为无害的字符。 这是 Django 最初几年的默认解决方案,但问题是它让开发人员/模板作者 you 有责任确保您逃避一切。 很容易忘记转义数据。
- 二、你可以利用 Django 的自动 HTML 转义。 本节的其余部分描述了自动转义的工作原理。
默认情况下,在 Django 中,每个模板都会自动转义每个变量标签的输出。 具体来说,这五个字符被转义:
<
转换为<
>
转换为>
'
(单引号)转换为'
"
(双引号)转换为"
&
转换为&
我们再次强调,默认情况下此行为处于启用状态。 如果您使用的是 Django 的模板系统,那么您将受到保护。
如何关闭它
如果您不希望在每个站点、每个模板级别或每个变量级别自动转义数据,您可以通过多种方式将其关闭。
你为什么要关掉它? 因为有时,模板变量包含您 打算 呈现为原始 HTML 的数据,在这种情况下,您不希望它们的内容被转义。 例如,您可能在数据库中存储了一段 HTML,并希望将其直接嵌入到您的模板中。 或者,您可能正在使用 Django 的模板系统来生成 而非 HTML 的文本——例如电子邮件消息。
对于单个变量
要禁用单个变量的自动转义,请使用 :tfilter:`safe` 过滤器:
This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}
将 safe 视为 safe from进一步转义的简写 或 可以安全地解释为 HTML。 在这个例子中,如果 data
包含 '<b>'
,输出将是:
This will be escaped: <b>
This will not be escaped: <b>
对于模板块
要控制模板的自动转义,请将模板(或模板的特定部分)包装在 :ttag:`autoescape` 标签中,如下所示:
{% autoescape off %}
Hello {{ name }}
{% endautoescape %}
:ttag:`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 %}
自动转义标签将其效果传递到扩展当前模板的模板以及通过 :ttag:`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 端的开发人员(编写视图和自定义过滤器的人)需要考虑数据不应该被转义的情况,并适当地标记数据,以便在模板中工作。
如果您正在创建一个可能在不确定是否启用自动转义的情况下使用的模板,则将 :tfilter:`escape` 过滤器添加到任何需要转义的变量。 当自动转义打开时,:tfilter:`escape` 过滤器没有危险 双重转义 数据 – :tfilter:`escape` 过滤器不影响自动转义的变量。
字符串文字和自动转义
正如我们之前提到的,过滤器参数可以是字符串:
{{ data|default:"This is a string literal." }}
所有字符串文字都被插入 而没有 任何自动转义到模板中——它们就像它们都通过了 :tfilter:`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 有意限制了模板语言中可用的逻辑处理量,所以不可能将参数传递给从模板内访问的方法调用。 数据应在视图中计算,然后传递到模板进行显示。
自定义标签和过滤器库
某些应用程序提供自定义标记和过滤器库。 要在模板中访问它们,请确保应用程序位于 :setting:`INSTALLED_APPS`(我们将在本示例中添加 'django.contrib.humanize'
),然后使用 :ttag:模板中的`load` 标签:
{% load humanize %}
{{ 45000|intcomma }}
在上面,:ttag:`load` 标签加载了 humanize
标签库,然后使 intcomma
过滤器可用。 如果您已启用 django.contrib.admindocs,您可以查阅管理员中的文档区域以查找安装中的自定义库列表。
:ttag:`load` 标签可以使用多个库名,用空格分隔。 例子:
{% load humanize i18n %}
有关编写自己的自定义模板库的信息,请参阅 自定义模板标签和过滤器 。
自定义库和模板继承
当您加载自定义标签或过滤器库时,标签/过滤器仅可用于当前模板——而不是模板继承路径上的任何父模板或子模板。
例如,如果模板 foo.html
具有 {% load humanize %}
,则子模板(例如,具有 {% extends "foo.html" %}
的模板)将 not 有权访问人性化模板标签和过滤器。 子模板负责自己的{% load humanize %}
。
这是一个为了可维护性和健全性的特性。
评论
要注释掉模板中一行的一部分,请使用注释语法:
{# #}
。例如,此模板将呈现为
'hello'
:注释可以包含任何模板代码,无论是否无效。 例如:
此语法只能用于单行注释(在
{#
和#}
分隔符之间不允许换行)。 如果您需要注释掉模板的多行部分,请参阅 :ttag:`comment` 标签。