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

过滤器可以“链式的”。一个过滤器的输出被应用到下一个过滤器。{{ 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 有二十多个内置的模板标签。你可以在 内置标签参考 中阅读所有关于它们的信息。为了让你了解这些标签,这里有一些比较常用的标签。

for

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

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

评估一个变量,如果该变量为 "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...”的消息。如果两个列表都是空的,将显示“No athletes.”。

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

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

虽然上面的例子是可行的,但要注意大多数模板过滤器都会返回字符串,所以使用过滤器进行数学比较一般不会像你预期的那样。

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 的扩展,并包括特定部分的样式/设计。
  • 为每种类型的页面创建单独的模板,如新闻文章或博客条目。这些模板扩展了相应的部分模板。

这种方法可以最大限度地重用代码,并有助于将项目添加到共享内容区域,如全部分导航。

下面是一些关于继承工作的技巧:

  • 如果你在模板中使用 {% extends %},它必须是该模板中的第一个模板标签。否则,模板继承将无法使用。

  • 基础模板中的 {% block %} 标签越多越好。记住,子模板不需要定义所有的父块,所以你可以在一些块中填入合理的默认值,然后只定义以后需要的块。钩子多比钩子少好。

  • 如果你发现自己的内容在多个模板中重复,可能意味着你应该把这些内容移到父模板中的 {% block %}

  • 如果你需要从父模板中获取块的内容,{{ block.super }} 变量就可以做到这一点。如果你想添加到父模板的内容中,而不是完全覆盖它,这很有用。使用 {{ block.super }} 插入的数据不会被自动转义(参见 下一节 ),因为如果需要的话,它已经在父模板中被转义了。

  • 通过使用与继承模板相同的模板名称,{% extends %} 可以在覆盖模板的同时继承它。结合 {{ block.super }},这可以成为一种强大的小规模定制方式。完整的例子请参见 扩展复写模板 中的 Overriding templates How-to。

  • {% block %} 外部使用模板标签 as 语法创建的变量无法在块内使用。例如,这个模板不会渲染任何内容:

    {% translate "Title" as title %}
    {% block content %}{{ title }}{% endblock %}
    
  • 为了更好的可读性,你可以选择为你的 {% endblock %} 标签指定一个 name。例如:

    {% block content %}
    ...
    {% endblock content %}
    

    在较大的模板中,这种技术可以帮助你看到哪些 {% block %} 标签正在被关闭。

  • {% block %} 标签首先被评估。这就是为什么无论周围标签的真实性如何,块的内容始终会被覆盖的原因。例如,这个模板将 始终 覆盖 title 块的内容:

    {% if change_title %}
        {% block title %}Hello!{% endblock title %}
    {% endif %}
    

最后,请注意,你不能在同一个模板中定义多个同名的 block 标签。之所以有这种限制,是因为块标签可以“双向”工作。也就是说,一个块标签不只是提供一个洞来填补——它还定义了填补 模板中洞的内容。如果一个模板中有两个名称相似的 block 标签,那么该模板的父标签就不知道该使用哪一个块的内容。

自动 HTML 转义

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

Hello, {{ name }}

乍一看,这似乎是一种无害的显示用户姓名的方式,但考虑一下如果用户输入他们的名字为以下内容会发生什么:

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

使用这个名字值,模板将被呈现为:

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

...这意味着浏览器会弹出一个 JavaScript 提示框!

同样,如果名称中包含一个 '<' 符号,像这样呢?

<b>username

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

Hello, <b>username

...这将导致网页的其余部分都是粗体!这是一个典型的 HTML 注入攻击的例子,因此在渲染用户提供的数据时,需要格外小心以确保安全性。

显然,不应该盲目信任用户提交的数据并将其直接插入到您的网页中,因为恶意用户可能会利用这种漏洞进行潜在的不良行为。这种类型的安全漏洞称为 跨站脚本攻击 (XSS) 攻击。

为了避免这个问题,你有两个选择:

  • 一,你可以确保通过 escape 过滤器来运行每个不受信任的变量(如下文所述),它可以将潜在的有害 HTML 字符转换为无害的字符。在 Django 的前几年,这是默认的解决方案,但问题是它把责任推给了 ,开发者/模板作者,以确保你的一切都被转义。很容易忘记对数据进行转义。
  • 二,你可以利用 Django 的自动 HTML 转义功能。本节剩余部分将介绍自动转义的工作原理。

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

  • < 被替换为 &lt;
  • > 被替换为 &gt;
  • ' (单引号)被替换为 &#x27;
  • " (双引号)被替换为 &quot;
  • & 被替换为 &amp;

我们再次强调,这个行为是默认开启的。如果你使用的是 Django 的模板系统,你就会受到保护。

如何关闭它

如果你不希望数据被自动转义, 你可以在站点、模板,、变量这三个层面关闭它:

为什么要关闭它?因为有时,模板变量包含的数据是你希望以原始 HTML 的形式呈现的,在这种情况下,你不希望其内容被转义。例如,你可能在数据库中存储了一个 HTML 片段,并希望将其直接嵌入到模板中。或者,你可能会使用 Django 的模板系统来生成非 HTML 的文本——比如说,电子邮件。

对于单个变量

要禁用单个变量的自动转义,请使用:tfilter:safe filter:

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

自动转义标签将其效果传递给扩展当前模板的模板以及通过 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 方面的开发人员(编写视图和自定义过滤器的人)需要考虑在哪些情况下数据不应该被转义,并适当地标记数据,所以事情在模板中 Just Work。

如果你创建的模板可能会在你不确定是否启用自动转义的情况下使用,那么在任何需要转义的变量中添加一个 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 %}

类似地,查询集 提供了一个 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" %} 的子模板)将 不能 访问人性化模板标签和过滤器。子模板负责自己的 {% load humanize %}

这是因为这能使模板更健全且更好维护。

参见

模板参考
包括内置标签、内置过滤器、使用替代模板语言等。
Back to Top