模板语言概述
本文档解释了Django模板系统的语言语法。如果您正在寻找关于它如何工作以及如何扩展它的更多技术观点,请参阅 Django模板语言:适用于Python程序员。
Django的模板语言旨在在功能和轻松之间取得平衡。它旨在让那些习惯使用HTML的人感到舒服。如果您对其他基于文本的模板语言(如Smarty 或Jinja2)有任何接触,那么您应该对Django的模板感到亲切。
信条
如果您有编程背景,或者习惯于将编程代码直接混合到HTML中的语言,那么您需要记住,Django模板系统不仅仅是嵌入到HTML中的Python。这是设计的:模板系统用于表示,而不是程序逻辑。
Django模板系统提供的标签功能类似于一些编程结构 - if标签,for 标签,循环标签等 - 但这些不是简单地作为相应的Python代码执行,模板系统不会执行任意Python表达式。默认情况下,仅支持下面列出的标签,过滤器和语法(尽管您可以根据需要将自己的扩展添加到模板语言中)。↑
1. 模板
模板只是一个文本文件。它可以生成任何基于文本的格式(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很变态!↑
2. 变量
变量看起来像这样:{{ variable }}
。当模板引擎遇到变量时,它会计算该变量并将其替换为结果。变量名由字母,数字字符和下划线(_
)的任意组合组成,但不能以下划线开头。点(.
)也出现在可变部分中,尽管它具有特殊含义,如下所示。重要的是,变量名称中不能包含空格或标点符号。
使用点(.
)访问变量的属性。
在幕后
从技术上讲,当模板系统遇到一个点时,它会按以下顺序尝试以下查找:
- 字典查找
- 属性或方法查找
- 数字索引查找
如果结果值是可调用的,则调用它时不带参数。调用的结果成为模板值。
此查找顺序可能会导致覆盖字典查找的对象出现一些意外行为。例如,请考虑以下代码片段尝试遍历collections.defaultdict
:
{% for k, v in defaultdict.items %}
Do something with k and v here...
{% endfor %}
因为字典查找首先发生,所以该行为启动并提供默认值而不是使用预期的.items()方法。在这种情况下,请考虑先转换为字典。↑
在上面的例子中,{{ section.title }}
将替换为title对象的section属性。
如果使用不存在的变量,模板系统将插入string_if_invalid
选项的值,’'默认情况下设置为(空字符串)。
请注意,模板表达式{{ foo.bar }}
中的“bar” 将被解释为文字字符串,而不使用变量“bar”的值(如果模板上下文中存在)。
可能无法访问以下划线开头的变量属性,因为它们通常被视为私有。
3. 过滤器
您可以使用过滤器修改要显示的变量。
过滤器看起来像这样:{{ 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。
同样,这些仅仅是几个例子;
您还可以创建自己的自定义模板过滤器; 见 自定义模板的标签(tags)和过滤器(filters)。
参见
Django的管理界面包含对给定站点可用的所有模板标签和过滤器的完整参考。请参阅 Django管理文档生成器。↑
4. 标签
标签看起来像这样:{% tag %}
。标签比变量更复杂:有些在输出中创建文本,有些通过执行循环或逻辑来控制流,有些则将外部信息加载到模板中以供后来的变量使用。
某些标签需要开始和结束标签(即{% tag %} ... tag contents ... {% endtag %}
)。
Django附带了大约24个内置模板标签。您可以在内置标签引用中阅读所有相关内容。为了让您了解可用的内容,以下是一些更常用的标记:
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 of the locker room soon!
。如果两个名单都是空的,“” 将显示No athletes.
。
您还可以在if标记中使用过滤器和各种运算符:
{% if athlete_list|length > 1 %}
Team: {% for athlete in athlete_list %} ... {% endfor %}
{% else %}
Athlete: {{ athlete_list.0.name }}
{% endif %}
虽然上面的示例有效,但请注意,大多数模板过滤器都返回字符串,因此使用过滤器的数学比较通常无法按预期工作。length是一个例外。
block 和 extends
设置模板继承(见下文),这是一种在模板中减少“重复代码”的强大方法。
同样,以上只是整个列表的一点; 请参阅完整列表的内置标签。
您还可以创建自己的自定义模板标签; 见 自定义模板的标签(tags)和过滤器(filters)。
参见
Django的管理界面包含对给定站点可用的所有模板标签和过滤器的完整参考。请参阅 Django管理文档生成器。↑
5. 注释
两种注释:单行注释({# ... #}
)和多行注释{% comment %}...{% endcomment %}
。
{# hello 单行注释 #}
{% comment %}
多行注释1
多行注释2
...
{% endcomment %}
6. 模板继承
Django模板引擎中最强大的 - 也是最复杂的 - 是模板继承。模板继承允许您构建一个基础“框架”模板,其中包含站点的所有常见元素,并定义子模板可以覆盖的block。
通过以一个示例开头,更容易理解模板继承:
<!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,因此将使用父模板中的值。 父模板中的{% block %}
标记内的内容始终用作后备。
您可以根据需要使用尽可能多的继承级别。使用继承的一种常见方法是以下三级方法:
- 创建一个
base.html
模板,其中包含您网站的主要外观。 - 为您网站的每个“部分” 创建一个
base_SECTIONNAME.html
模板。例如base_news.html
,base_sports.html
。这些模板都扩展base.html并包含特定于部分的样式/设计。 - 为每种类型的页面创建单独的模板,例如新闻文章或博客条目。这些模板扩展了相应的部分模板。
这种方法可以最大化代码重用,并且可以轻松地将项目添加到共享内容区域,例如部分范围的导航。
以下是使用继承的一些提示:
-
如果在模板中使用
{% extends %}
,则它必须是该模板中的第一个模板标记。否则,模板继承将不起作用。 -
基础模板中最好有很多
{% block %}
标签。请记住,子模板不必定义所有父块,因此您可以在多个块中填写合理的默认值,然后仅定义稍后需要的块。最好有更多的钩子而不是更少的钩子。 -
如果您发现自己在许多模板中复制了内容,则可能意味着您应该将内容移动到父模板中的
{% block %}
内。 -
如果您需要从父模板获取块的内容,该变量
{{ block.super }}
将起作用。如果要添加父块的内容而不是完全覆盖它,这将非常有用。使用的{{ block.super }}
的数据不会自动转义(请参阅下一节),因为必要时,它已在父模板中转义。 -
使用模板标签语法在
{% block %}
之外创建的变量不能在块内使用。例如,此模板不呈现任何内容:
译者注:注意变量是用模板标签的语法创建的,而不是你从视图传递过来的变量哦!
{% trans "Title" as title %}
{% block content %}{{ title }}{% endblock %}
- 对于额外的可读性,您可以给你的
{% endblock %}
选择给一个名字例如:
{% block content %}
...
{% endblock content %}
在较大的模板中,此技术可帮助您查看 正在关闭的{% block %}
标签。
最后,请注意,您无法在同一模板中定义多个具有相同名称的block标签。存在这种限制是因为block标签在“两个”方向上工作。也就是说,block标签不仅提供填充孔 - 它还定义填充父级孔的内容。如果block模板中有两个类似命名的标记,则该模板的父级将不知道要使用哪个块的内容。
7. 自动HTML转义
从模板生成HTML时,变量将始终存在影响生成的HTML的字符的风险。例如,考虑这个模板片段:
Hello, {{ name }}
起初,这似乎是一种显示用户名称的无害方式,但考虑如果用户输入其名称为<script>alert('hello')</script>
,会发生什么?
使用此名称值,模板将呈现为:
Hello, <script>alert('hello')</script>
…这意味着浏览器会弹出一个JavaScript警告框!
同样,如果名称包含’<'符号,如<b>username
这将导致像这样的渲染模板:
Hello, <b>username
反过来,这将导致网页的其余部分被加粗!
显然,用户提交的数据不应盲目信任并直接插入到您的网页中,因为恶意用户可能会利用这种漏洞来做坏事。此类安全漏洞称为 跨站点脚本(XSS)攻击。
要避免此问题,您有两种选择:
- 一,您可以确保通过escape过滤器(下面会讲)运行每个不受信任的变量 ,这会将可能有害的HTML字符转换为无害的HTML字符。这是Django最初几年的默认解决方案,但问题在于它让你(开发人员/模板作者)有责任确保你转义一切。很容易忘记转义数据。
- 二,你可以利用Django的自动HTML转义功能。本节的其余部分将介绍自动转义的工作原理。
默认情况下,Django中的每个模板都会自动转义每个变量标记的输出。具体来说,这五个字符被转义:
<
转换为<
>
转换为>
'
(单引号)转换为'
"
(双引号)转换为"
&
转换为&
同样,我们强调默认情况下此行为已启用。如果您正在使用Django的模板系统,那么您将受到保护。
7.1 如何关闭它
如果您不希望在每个站点,每个模板级别或每个变量级别上自动转义数据,则可以通过多种方式将其关闭。
你为什么要把它关掉?因为有时,模板变量包含您打算作为原始HTML呈现的数据,在这种情况下,您不希望对其内容进行转义。例如,您可以在数据库中存储一团HTML,并希望将其直接嵌入到模板中。或者,您可能正在使用Django的模板系统来生成非 HTML的文本- 例如,电子邮件消息。
7.1.1 对于个别变量
要禁用单个变量的自动转义,请使用safe 过滤器:
例如,在视图中sv='<h1>sv</h1>'
,在模板中使用如下:
{{ sv }}
{{ sv|safe }}
浏览器的效果:
对应的html源文件如下:
<h1>sv</h1>
<h1>sv</h1>
7.1.2 对于模板块
要控制模板的自动转义,请在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标记包含的模板,就像所有块标记一样。例如:
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 %}
由于在基本模板中关闭了自动转义,因此它也将在子模板中关闭,从而在greeting变量包含字符串HTML <b>Hello!</b>
时生成以下呈现的内容:
浏览器:
html中的内容:
<h1>This & that</h1>
<b>Hello!</b>
7.2 注意
通常,模板作者不需要太担心自动转义。Python方面的开发人员(编写视图和自定义过滤器的人)需要考虑不应该转义数据的情况,并适当地标记数据,因此只需在模板中工作即可。
如果您正在创建可能在您不确定是否启用了自动转义的情况下使用的模板,则将escape过滤器添加到需要转义的任何变量中。当启用autospace时,同时使用escape过滤器进行双重转义数据没有危险,因为escape过滤器不会影响autospce的变量。
7.3 字符串文字和自动转义
正如我们前面提到的,过滤器参数可以是字符串:
{{ data|default:"This is a string literal." }}
插入所有字符串文字到模板中时不会做任何自动转义 - 就好像它们都通过safe 过滤器一样。这背后的原因是模板作者控制了字符串文字中的内容,因此他们可以确保在编写模板时正确转义文本。
这意味着你会写
{{ data|default:"3 < 2" }}
…而不是:
{{ data|default:"3 < 2" }} {# Bad! Don't do this. #}
这不会影响来自变量本身的数据会发生什么。如有必要,变量的内容仍会自动转义,因为它们超出了模板作者的控制范围。
8. 访问方法调用
附加到对象的大多数方法调用也可以从模板中获得。这意味着模板可以访问的不仅仅是类属性(如字段名称)和从视图传入的变量。例如,Django ORM提供“entry_set”语法,用于查找与外键相关的对象集合。因此,给定一个名为“comment”的模型,该模型与称为“task”的模型具有外键关系,您可以循环遍历给定任务的所有注释,如下所示:
{% for comment in task.comment_set.all %}
{{ comment }}
{% endfor %}
同样,QuerySets提供了count()一种计算它们包含的对象数量的方法。因此,您可以使用以下方法获取与当前任务相关的所有注释的计数:
{{ task.comment_set.all.count }}
当然,您可以轻松访问您在自己的模型上明确定义的方法:
例子见 访问对象的方法。
因为Django故意限制模板语言中可用的逻辑处理量,所以不可能将参数传递给从模板内访问的方法调用。应在视图中计算数据,然后传递给模板进行显示。
9. 自定义标签和过滤器库
某些应用程序提供自定义标记和过滤器库。要在模板中访问它们,请确保应用程序在INSTALLED_APPS(我们为此示例添加'django.contrib.humanize'
),然后在模板中使用load
标记加载:
{% load humanize %}
{{ 45000|intcomma }}
在上面,load标签加载humanize标签库,然后使intcomma过滤器可供使用。如果已启用django.contrib.admindocs
,则可以查阅管理员中的文档区域以查找安装中的自定义库列表。
load标签可以加载多个库名,用空格隔开。例:
{% load humanize i18n %}
有关编写自己的自定义模板库的信息,请参见自定义模板(的标签(标签)和过滤器(filters)。
9.1 自定义库和模板继承
加载自定义标签或过滤器库时,标签/过滤器仅可用于当前模板 - 而不是模板继承路径中的任何父模板或子模板。
例如,如果模板foo.html
具有{% load humanize %}
,则子模板(例如,具有{% extends "foo.html" %}
的模板)将无法访问humanize模板标签和过滤器。子模板负责自己的{% load humanize %}
。
这是为了可维护性和理智性的特征。
参见
模板参考
涵盖内置标签,内置过滤器,使用替代模板,语言等。