Django 模板语言

本文档解释了 Django 模板系统的语言语法。如果您想从更技术的角度了解其工作原理以及如何扩展它,请参阅 Django 模板语言:面向 Python 程序员

Django 的模板语言旨在平衡功能和易用性。它旨在让习惯使用 HTML 的人感到舒适。如果您接触过其他基于文本的模板语言,例如 SmartyJinja2,那么您应该很快就能适应 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 模板。您可以将模板语言用于任何基于文本的格式,例如电子邮件、JavaScript 和 CSV。

变量

变量如下所示:{{ 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|lower }}。这将显示 {{ name }} 变量的值,该值已通过 lower 过滤器过滤,该过滤器将文本转换为小写。使用管道 (|) 应用过滤器。

过滤器可以“链接”。一个过滤器的输出应用于下一个过滤器。{{ text|escape|linebreaks }} 是转义文本内容,然后将换行符转换为 <p> 标签的常用习惯用法。

某些过滤器需要参数。过滤器参数如下所示:{{ bio|truncatewords:30 }}。这将显示 bio 变量的前 30 个单词。

包含空格的过滤器参数必须用引号括起来;例如,要使用逗号和空格连接列表,可以使用 {{ list|join:", " }}

Django 提供了大约 60 个内置模板过滤器。您可以在 内置过滤器参考 中详细了解所有这些过滤器。为了让您了解可用的内容,以下是一些常用的模板过滤器

default

如果变量为假或为空,则使用给定的默认值。否则,使用变量的值。例如

{{ 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

同样,这些只是一些示例;有关完整列表,请参阅 内置过滤器参考

您还可以创建自己的自定义模板过滤器;请参阅 如何创建自定义模板标签和过滤器

另请参阅

Django 的管理界面可以包含给定站点上所有可用模板标签和过滤器的完整参考。请参阅 Django 管理员文档生成器

标签

标签如下所示:{% tag %}。标签比变量更复杂:一些标签在输出中创建文本,一些标签通过执行循环或逻辑来控制流程,还有一些标签将外部信息加载到模板中,以便后续变量使用。

某些标签需要开始和结束标签(即 {% tag %} ... tag contents ... {% endtag %})。

Django 附带了大约 24 个内置模板标签。您可以在 内置标签参考 中详细了解所有这些标签。为了让您了解可用的内容,以下是一些常用的标签

for

循环遍历数组中的每个项目。例如,要显示 athlete_list 中提供的运动员列表

<ul>
{% for athlete in athlete_list %}
    <li>{{ athlete.name }}</li>
{% endfor %}
</ul>
ifelifelse

计算变量的值,如果该变量为“真”,则显示块的内容

{% 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不为空,则将显示消息“运动员应该出去……”。如果两个列表都为空,则将显示“没有运动员”。

您还可以在if标签中使用过滤器和各种运算符。

{% if athlete_list|length > 1 %}
   Team: {% for athlete in athlete_list %} ... {% endfor %}
{% else %}
   Athlete: {{ athlete_list.0.name }}
{% endif %}

虽然上面的示例有效,但请注意,大多数模板过滤器返回字符串,因此使用过滤器的数学比较通常不会按预期工作。length是一个例外。

blockextends

设置模板继承(参见下文),这是一种强大的方法,可以减少模板中的“样板代码”。

同样,以上内容仅是整个列表的一部分;有关完整列表,请参阅内置标签参考

您还可以创建自己的自定义模板标签;请参阅如何创建自定义模板标签和过滤器

另请参阅

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”。

此时,模板引擎会注意到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.htmlbase_sports.html。这些模板都扩展了base.html,并包含特定于部分的样式/设计。

  • 为每种类型的页面创建单独的模板,例如新闻文章或博客条目。这些模板扩展了相应的章节模板。

这种方法最大限度地提高了代码重用率,并有助于将项目添加到共享内容区域,例如部分范围内的导航。

以下是一些使用继承的技巧

最后,请注意,您不能在同一模板中定义多个具有相同名称的block标签。此限制存在是因为块标签在“两个”方向上都起作用。也就是说,块标签不仅提供了一个要填充的孔——它还定义了填充模板中孔的内容。如果模板中存在两个名称相同的block标签,则该模板的父模板将不知道要使用哪个块的内容。

自动 HTML 转义

当从模板生成 HTML 时,变量始终存在包含影响结果 HTML 的字符的风险。例如,考虑以下模板片段

Hello, {{ name }}

起初,这似乎是显示用户姓名的一种无害方法,但请考虑如果用户输入的姓名如下会发生什么

<script>alert('hello')</script>

使用此名称值,模板将呈现为

Hello, <script>alert('hello')</script>

…这意味着浏览器将弹出 JavaScript 警报框!

同样,如果名称包含'<'符号,如下所示呢?

<b>username

这将导致呈现的模板如下所示

Hello, <b>username

…这反过来又会导致网页的其余部分以粗体显示!

显然,不应盲目信任用户提交的数据并将其直接插入到您的网页中,因为恶意用户可以使用这种漏洞来做一些潜在的坏事。这种类型的安全漏洞称为跨站点脚本(XSS)攻击。

为了避免此问题,您有两个选择

  • 第一,您可以确保通过escape过滤器(如下所述)运行每个不受信任的变量,该过滤器将潜在的有害 HTML 字符转换为无害的字符。这是 Django 在最初几年中的默认解决方案,但问题在于它将责任推给了您,即开发人员/模板作者,以确保您转义所有内容。很容易忘记转义数据。

  • 第二,您可以利用 Django 的自动 HTML 转义。本节的其余部分介绍了自动转义的工作原理。

在 Django 中,默认情况下,每个模板都会自动转义每个变量标签的输出。具体来说,以下五个字符被转义

  • <转换为&lt;

  • >转换为&gt;

  • 单引号 ' 将被转换为 &#x27;

  • 双引号 " 将被转换为 &quot;

  • & 将被转换为 &amp;

再次强调,此行为默认开启。如果您使用 Django 的模板系统,则您受到保护。

如何关闭

如果您不希望数据自动转义,可以在站点级别、模板级别或变量级别关闭它,可以通过多种方式实现。

为什么要关闭它?因为有时,模板变量包含您**希望**以原始 HTML 渲染的数据,在这种情况下,您不希望其内容被转义。例如,您可能将 HTML 代码块存储在数据库中,并希望将其直接嵌入到模板中。或者,您可能正在使用 Django 的模板系统生成**不是** HTML 的文本——例如电子邮件消息。

针对单个变量

要禁用单个变量的自动转义,请使用 safe 过滤器

This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}

safe视为免于进一步转义可以安全地解释为 HTML的简写。在此示例中,如果data包含'<b>',则输出将为

This will be escaped: &lt;b&gt;
This will not be escaped: <b>

针对模板块

要控制模板的自动转义,请使用 autoescape 标签包装模板(或模板的特定部分),如下所示

{% autoescape off %}
    Hello {{ name }}
{% endautoescape %}

autoescape 标签接受onoff作为其参数。有时,您可能希望在自动转义被禁用时强制执行自动转义。以下是一个示例模板

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 %}

auto-escaping 标签将其效果传递给扩展当前模板的模板以及通过 include 标签包含的模板,就像所有块标签一样。例如

base.html
{% autoescape off %}
<h1>{% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
{% endautoescape %}
child.html
{% extends "base.html" %}
{% block title %}This &amp; that{% endblock %}
{% block content %}{{ greeting }}{% endblock %}

由于在基本模板中关闭了自动转义,因此它也将关闭子模板中的自动转义,当 greeting 变量包含字符串 <b>Hello!</b> 时,将生成以下渲染的 HTML

<h1>This &amp; that</h1>
<b>Hello!</b>

备注

通常,模板作者不需要过多地担心自动转义。Python 端的开发人员(编写视图和自定义过滤器的开发人员)需要考虑不应该转义数据的情况,并适当地标记数据,以便模板能够正常工作。

如果您正在创建可能在不确定是否启用了自动转义的情况下使用的模板,则为需要转义的任何变量添加 escape 过滤器。当自动转义开启时,escape 过滤器不会出现**重复转义**数据的危险——escape 过滤器不影响自动转义的变量。

字符串字面量和自动转义

如前所述,过滤器参数可以是字符串

{{ data|default:"This is a string literal." }}

所有字符串字面量都**无需**任何自动转义即可插入模板——它们的行为就像都通过 safe 过滤器传递一样。这样做的原因是模板作者可以控制字符串字面量中的内容,因此他们可以在编写模板时确保文本正确转义。

这意味着您将编写

{{ data|default:"3 &lt; 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 }}

您还可以访问在您自己的模型上明确定义的方法

models.py
class Task(models.Model):
    def foo(self):
        return "bar"
template.html
{{ 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" %} 的模板)将**无法**访问 humanize 模板标签和过滤器。子模板负责自己的 {% load humanize %}

这是一项为了可维护性和健全性而设计的特性。

另请参阅

模板参考

涵盖内置标签、内置过滤器、使用替代模板语言等。

返回顶部