本文档解释了Django模板系统的语言语法。如果您想从技术角度了解它的工作原理和扩展方法,请参见 Django模板语言:针对Python程序员 .
Django的模板语言旨在在功能和易用性之间取得平衡。它的设计让那些习惯于使用HTML的人感到舒适。如果您接触过其他基于文本的模板语言,例如 Smarty 或 Jinja2, 你应该对Django的模板感到宾至如归。
原理
如果您有编程背景,或者您习惯了将编程代码直接混合到HTML中的语言,那么您需要记住,django模板系统不仅仅是嵌入到HTML中的python。这是通过设计实现的:模板系统是用来表达表示,而不是程序逻辑。
django模板系统提供了与某些编程结构类似的标记——一个 if
布尔测试的标记,a for
循环标记等——但这些标记并不是简单地作为相应的python代码执行的,模板系统不会执行任意的python表达式。默认情况下只支持下面列出的标记、筛选器和语法(尽管您可以添加 your own extensions 根据需要使用模板语言)。
模板是一个文本文件。它可以生成任何基于文本的格式(HTML、XML、CSV等)。
模板包含 变量 ,在评估模板时将其替换为值,并且 tags 控制模板的逻辑。
下面是一个演示一些基础知识的最小模板。本文稍后将解释每个元素。
{% 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。
变量如下所示: {{ variable }}
。当模板引擎遇到变量时,它会计算该变量并将其替换为结果。变量名由字母数字字符和下划线的任意组合组成 ("_"
),但不能以下划线开头,也不能是数字。圆点 ("."
)也出现在可变部分中,尽管这有特殊的含义,如下所示。重要的是 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 }}}}
将替换为 title
的属性 section
对象。
如果使用不存在的变量,模板系统将插入 string_if_invalid
选项,设置为 ''
(空字符串)默认情况下。
注意模板表达式中的“bar”类似于 {{{{ foo.bar }}}}
如果模板上下文中存在变量“bar”,则将被解释为文本字符串,而不使用变量“bar”的值。
不能访问以下划线开头的变量属性,因为它们通常被认为是私有的。
您可以使用 过滤器 .
筛选器如下: {{{{ name|lower }}}}
. 这将显示 {{{{ name }}}}
通过筛选后的变量 lower
过滤器,将文本转换为小写。使用管道 (|
)应用过滤器。
过滤器可以“链接”。一个过滤器的输出应用于下一个。 {{{{ text|escape|linebreaks }}}}
是一个常见的习惯用法,用于转义文本内容,然后将换行符转换为 <p>
标签。
有些筛选器接受参数。筛选器参数如下所示: {{{{ bio|truncatewords:30 }}}}
. 这将显示 bio
必须引用包含空格的筛选器参数;例如,要用逗号和空格联接列表,必须使用 {{{{ list|join:", " }}}}
.
Django提供了大约60个内置模板过滤器。你可以在 built-in filter reference . 为了让您了解可用的内容,以下是一些更常用的模板过滤器:
default
如果变量为FALSE或空,则使用给定的默认值。否则,使用变量的值。例如:
{{ value|default:"nothing" }}
如果 value
未提供或为空,上面将显示“``无'”。
length
返回值的长度。这对字符串和列表都有效。例如:
{{ value|length }}
如果 value
是 ['a', 'b', 'c', 'd']
,输出为 4
.
filesizeformat
将该值格式化为“人类可读”的文件大小(即 '13 KB'
, '4.1 MB'
, '102 bytes'
等)。例如:
{{ value|filesizeformat }}
如果 value
是123456789,输出为 117.7 MB
.
同样,这些只是几个例子;请参见 built-in filter reference 完整的列表。
您还可以创建自己的自定义模板过滤器;请参见 如何创建自定义模板标记和过滤器 .
参见
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”。
此时,模板引擎将注意到这三个 block
中的标签 base.html
并用子模板的内容替换这些块。取决于 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
块,使用父模板中的值。A中的内容 {{% block %}}
父模板中的标记始终用作回退。
您可以根据需要使用任意级别的继承。使用继承的一种常见方法是以下三级方法:
创建一个 base.html
保存网站主要外观和感觉的模板。
创建一个 base_SECTIONNAME.html
网站每个“部分”的模板。例如, base_news.html
, base_sports.html
. 这些模板都扩展 base.html
并包括特定于部分的样式/设计。
为每种类型的页面创建单独的模板,例如新闻文章或博客条目。这些模板扩展了相应的节模板。
这种方法最大限度地实现了代码重用,并有助于将项添加到共享内容区域,如节范围的导航。
以下是处理继承的一些技巧:
如果你使用 {{% extends %}}
在模板中,它必须是该模板中的第一个模板标记。否则模板继承将不起作用。
更多 {{% block %}}
基本模板中的标记更好。记住,子模板不必定义所有父块,因此您可以在多个块中填充合理的默认值,然后只定义稍后需要的块。钩子多,钩子少。
如果您发现自己在许多模板中复制内容,这可能意味着您应该将该内容移动到 {{% block %}}
在父模板中。
如果需要从父模板获取块的内容,则 {{{{ block.super }}}}
变量将起作用。如果要添加到父块的内容中而不是完全重写父块,则此选项非常有用。数据插入使用 {{{{ block.super }}}}
不会自动转义(请参见 next section ,因为它已经在父模板中进行了转义(如果需要)。
通过使用与继承的模板名称相同的模板名, {{% extends %}}
可用于在重写模板的同时继承模板。与 {{{{ block.super }}}}
,这可能是进行小型定制的强大方法。看到了吗 扩展重写的模板 在 覆盖模板 一个完整的例子。
在外部创建的变量 {% block %}
使用模板标记 as
不能在块内使用语法。例如,此模板不呈现任何内容:
{% translate "Title" as title %}
{% block content %}{{ title }}{% endblock %}
为了获得更好的可读性,您可以选择给 name 致您的 {% endblock %}
标签。例如:
{% block content %}
...
{% endblock content %}
在较大的模板中,此技术帮助您了解 {{% block %}}
正在关闭标签。
{% block %}
首先对标记进行评估。这就是块的内容总是被覆盖的原因,而不管周围标记的真实性。例如,此模板将 always 重写 title
数据块:
{% if change_title %}
{% block title %}Hello!{% endblock title %}
{% endif %}
最后,请注意,不能定义多个 block
在同一模板中使用相同名称的标记。这种限制的存在是因为块标记在“两个”方向上都有效。也就是说,块标记不仅提供要填充的孔,还定义了填充 起源 . 如果有两个相似的名字 block
标记在模板中,该模板的父级将不知道要使用哪个块的内容。
从模板生成HTML时,始终存在变量包含影响生成的HTML的字符的风险。例如,考虑以下模板片段:
Hello, {{ name }}
乍一看,这似乎是一种无害的显示用户名的方式,但考虑一下如果用户按如下方式输入他们的姓名会发生什么:
<script>alert('hello')</script>
使用此Name值,模板将呈现为:
Hello, <script>alert('hello')</script>
…这意味着浏览器将弹出一个javascript警告框!
类似地,如果名称包含 '<'
符号,像这样?
<b>username
这将产生如下所示的渲染模板:
Hello, <b>username
...这又会导致网页的其余部分以粗体显示!
显然,不应该盲目信任用户提交的数据并将其直接插入到您的网页中,因为恶意用户可能会利用这种漏洞来做潜在的坏事。这种类型的安全漏洞被称为 Cross Site Scripting (Xss)攻击。
要避免此问题,您有两个选项:
第一,您可以确保通过 escape
过滤器(文档如下),它将可能有害的HTML字符转换为不安全的字符。这是 Django 最初几年的默认解决方案,但问题是它将责任推到了 you 开发人员/模板作者,以确保您正在转义所有内容。很容易忘记转义数据。
第二,您可以利用Django的自动HTML转义。本节的其余部分介绍自动转义的工作原理。
在Django中,默认情况下,每个模板都自动转义每个变量标记的输出。具体来说,这五个字符是转义的:
<
is converted to <
>
is converted to >
'
(single quote) is converted to '
"
(double quote) is converted to "
&
is converted to &
我们再次强调这个行为是默认的。如果你使用的是Django的模板系统,你会受到保护。
如果不希望在每个站点、每个模板级别或每个变量级别自动转义数据,可以通过多种方式将其关闭。
你为什么要关掉它?因为有时模板变量包含 打算 将呈现为原始HTML,在这种情况下,您不希望对其内容进行转义。例如,您可以在数据库中存储一个HTML块,并希望将其直接嵌入到模板中。或者,您可能正在使用Django的模板系统生成文本,即 not 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
标签,就像所有的块标签一样。例如:
{% 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>
:
<h1>This & that</h1>
<b>Hello!</b>
一般来说,模板作者不需要非常担心自动转义。在Python方面的开发人员(编写视图和自定义过滤器的人员)需要考虑数据不应该被转义的情况,并适当地标记数据,所以这些东西只在模板中工作。
如果创建的模板可能在不确定是否启用自动转义的情况下使用,请添加 escape
过滤到任何需要转义的变量。当自动逃逸打开时,没有危险 escape
滤波器 double-escaping 数据—— 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 %}
见 如何创建自定义模板标记和过滤器 有关编写自己的自定义模板库的信息。
加载自定义标记或筛选器库时,标记/筛选器仅对当前模板可用,而不是模板继承路径上的任何父模板或子模板。
例如,如果模板 foo.html
有 {{% load humanize %}}
,子模板(例如, {{% extends "foo.html" %}}
意志 not 可以访问Humanize模板标记和过滤器。子模板自己负责 {{% load humanize %}}
.
这是一个为了可维护性和健全性的特性。
参见
包括内置标记、内置过滤器、使用替代模板语言等。
12月 18, 2023