============================ Django模板语言 ============================ 本文介绍Django模板系统的语法. 如果你想从技术角度了解它的工作原理以及如何扩展它, 请参见 :doc:`/ref/templates/api`. Django的模板语言旨在在功能和易用性之间取得平衡. 它的设计目的是让那些习惯于使用HTML的人能够自如应对. 如果您接触过其他基于文本的模板语言, 例如 Smarty_ 或 Jinja2_, 那么您将对Django的模板语言感到一见如故. .. admonition:: Philosophy 如果您有编程方面的背景知识, 或者您习惯于将程序代码直接混入HTML中的语言, 那么您需要记住, Django模板系统不仅仅是嵌入到HTML中的Python. 它是通过设计实现的: 模板系统旨在表示表现形式, 而不是程序逻辑. Django模板系统提供了类似于编程结构的标签 -- 用于布尔判断的 :ttag:`if` 标签, 用于循环的 :ttag:`for` 标签等. -- 这些标签并不是简单地作为相应的Python代码执行的, 模板系统也不会执行任何Python表达式. 默认情况下, 仅支持下面列出的标签, 过滤器和语法(尽管您可以根据需要向模板系统中添加 :doc:`自定义扩展 `). .. _`The Django template language: For Python programmers`: ../templates_python/ .. _Smarty: http://www.smarty.net/ .. _Jinja2: http://jinja.pocoo.org/ 模板 ========= .. highlightlang:: html+django 模板是一个文本文件. 它可以生成任何基于文本的格式(HTML, XML, CSV 等). 一个模板包含使用时会被值替换的 **变量** 和控制模版逻辑的 **标签**. 下面是一个基础的模板, 展示了一些基础的内容. 在后面的文档中会解释其中每个元素. .. code-block:: html+django {% extends "base_generic.html" %} {% block title %}{{ section.title }}{% endblock %} {% block content %}

{{ section.title }}

{% for story in story_list %}

{{ story.headline|upper }}

{{ story.tease|truncatewords:"100" }}

{% endfor %} {% endblock %} .. admonition:: Philosophy 为什么使用基于文本的模板而不是基于XML的模板(比如Zope的TAL)? 我们希望Django的模板语言可以用在更多的地方, 而不仅仅是XML/HTML模版. 你可以将模板语言用于任何基于文本的格式中, 如电子邮件,JavaScript和CSV. 还有, 让人去编辑XML简直是一种虐待! .. _template-variables: 变量 ========= 变量看起来就像这样: ``{{ variable }}``. 当模板引擎遇到一个变量时, 它会计算这个变量并用结果替换它. 变量名可以由任意字母数字字符和下划线(``"_"``)组成. 点(``"."``)也会在变量部分出现, 它具有特殊含义, 我们将在后面说明. 重要的是, **变量名中不能有空格或标点符号**. 使用点号 (``.``)来访问变量的属性. .. admonition:: Behind the scenes 从技术上讲, 当模板系统遇到一个点时, 它会按照以下顺序进行查找: * 字典查找 (Dictionary lookup) * 属性或方法查找 (Attribute or method lookup) * 数字索引查找 (Numeric index lookup) 如果结果值是可调用的, 则会无参调用. 调用的结果值成为模板值. 这个查询顺序, 会对优先于字典查找的对象上造成意想不到的行为. 例如, 思考下面的代码片段, 它原意是想遍历 ``collections.defaultdict``:: {% for k, v in defaultdict.iteritems %} Do something with k and v here... {% endfor %} 因为字典查找是先执行的, 这时返回了一个默认值, 而不是使用预期的 ``.iteritems()`` 方法. 在这种情况下, 可以考虑先转换为字典. 在上面例子中, ``{{ section.title }}`` 将被 ``section`` 对象的 ``title`` 属性替代. 如果你使用一个不存在的变量, 模板系统会插入 ``string_if_invalid`` 选项的值, 它被默认设置为 ``''`` (空字符串). 请注意, 像 ``{{ foo.bar }}`` 这样的模板表达式中的"bar", 如果在模板上下文中存在的话, 将被解释为一个字面意义字符串, 而不是使用变量"bar"的值. 过滤器 ======= **过滤器** 用来修改展示的变量. 过滤器看起来像这样: ``{{ name|lower }}``. 这将显示变量 ``{{ name }}`` 被过滤器 :tfilter:`lower` 修改后的值, 它将文本转换成小写形式. 使用管道符 (``|``) 来应用过滤器. 过滤器可以 "链式." 一个过滤器的输出将应用到下一个. ``{{ text|escape|linebreaks }}`` 就是一个常用的过滤器链, 它用于转义文本内容, 然后将换行符转换为 ``

`` 标签. 过滤器可以接收参数. 例如: ``{{ bio|truncatewords:30 }}``. 它显示变量 ``bio`` 的前30个字符. 包含空格的过滤器参数必须加引号; 例如, 连接一个包含逗号和空格的列表: ``{{ list|join:", " }}``. Django提供了大约60个内置的模板过滤器. 你可以在 :ref:`内置过滤器参考 ` 中查看. 为了让你了解模板过滤器, 这里列出了一些比较常用的模板过滤器: :tfilter:`default` 如果变量为false或者空, 则使用给定的默认值. 否则, 使用变量的值. 例如:: {{ value|default:"nothing" }} 如果 ``value`` 不存在或者为空, 那么将显示"``nothing``". :tfilter:`length` 返回值的长度. 这对字符串和列表都有效. 例如:: {{ value|length }} 如果 ``value`` 为 ``['a', 'b', 'c', 'd']`` 时, 则将显示 ``4``. :tfilter:`filesizeformat` 转换为"易读"的文件大小 (例如. ``'13 KB'``, ``'4.1 MB'``, ``'102 bytes'``, 等.). 例如:: {{ value|filesizeformat }} 如果 ``value`` 为 123456789, 则将显示为 ``117.7 MB``. 这些只是几个例子; 查看 :ref:`内置过滤器参考 ` 了解所有的内置过滤器. 你也可以自定义过滤器; 详见 :doc:`/howto/custom-template-tags`. .. seealso:: Django的admin接口提供了一个模板标签和可用的过滤器的完整参考. 详见 :doc:`/ref/contrib/admin/admindocs`. 标签 ==== 标签看起来像这样: ``{% tag %}``. 标签比变量更复杂: 有些用于在输出中创建文本, 有些用于控制循环或逻辑, 有些用于加载外部信息到模板中供以后的变量使用. 有些标签要求要有开始和结束标签 (例如. ``{% tag %} ... 标签正文 ... {% endtag %}``). Django附带了大约24个内置模板标签, 你可以在 :ref:`内置标签参考 ` 中了解他们. 为了让你了解这些标签, 下面是一些常用的标签: :ttag:`for` 循环遍历数组中的每个元素. 例如, 显示 ``athlete_list`` 的athletes列表::

:ttag:`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的数量. 否则, 如果 ``athlete_in_locker_room_list`` 不为空, 将会显示 "Athletes should be out...". 如果两个列表都为空, 将会显示"No athletes.". :ttag:`if` 标签中也可以使用过滤器或其他操作符:: {% if athlete_list|length > 1 %} Team: {% for athlete in athlete_list %} ... {% endfor %} {% else %} Athlete: {{ athlete_list.0.name }} {% endif %} 虽然上面的例子是可行的, 需要注意, 大多数模版过滤器返回字符串, 所以使用过滤器做数学的比较通常都不会像您期望的那样工作. :tfilter:`length` 是个例外. :ttag:`block` 和 :ttag:`extends` 参见 `模板继承`_ (见下文), 一种减少模板中重复代码的有效方法. 这些只是几个例子; 查看 :ref:`内置标签参考 ` 了解所有的内置标签. 你也可以自定义过滤标签; 详见 :doc:`/howto/custom-template-tags`. .. seealso:: Django的admin接口提供了一个模板标签和可用的过滤器的完整参考. 详见 :doc:`/ref/contrib/admin/admindocs`. .. _模板继承: 注释 ======== 要注释模板中的内容, 请使用注释语法: ``{# #}``. 例如, 下面内容会显示为 ``'hello'``:: {# greeting #}hello 注释可以包含任何有效或无效的代码. 例如:: {# {% if foo %}bar{% else %} #} 这种语法只能用于单行注释 (在 ``{#`` 和 ``#}`` 中不允许有换行). 如果你需要注释掉模版中的多行内容, 请参见 :ttag:`comment` 标签. .. _template-inheritance: 模板继承 ==================== Django的模板引擎中 -- 最强大 -- 也是最复杂的部分是模板继承. 模板继承允许你创建一个基础的“骨架”模板, 它包含了网页的所有常用元素, 并定义子模板可以覆盖的 **blocks**. 通过下面这个例子, 可以很容易的理解模版继承:: {% block title %}My amazing site{% endblock %}
{% block content %}{% endblock %}
我们将上面模板称为 ``base.html``, 它定义了一个基础的HTML骨架模板, 用来制作一个简单的两栏式页面. "子" 模板的任务是用内容填充空块. 在上面例子中, 使用 :ttag:`block` 标签定义三个可以被子模板填充的块. :ttag:`block` 的作用就是告诉模版引擎: 子模版可能会覆盖掉模版中的这些位置. 子模板可能是这样的:: {% extends "base.html" %} {% block title %}My amazing blog{% endblock %} {% block content %} {% for entry in blog_entries %}

{{ entry.title }}

{{ entry.body }}

{% endfor %} {% endblock %} :ttag:`extends` 标签是其中的关键. 它告诉模版引擎, 这个模版"继承"了另一个模版. 当模版引擎处理这个模版时, 首先它将定位父模版 -- 在此例中, 就是"base.html". 此时, 模板引擎将注意到 ``base.html`` 中的三个 :ttag:`block` 标签, 并用子模板的内容替换这些块. 依据 ``blog_entries`` 的值可能显示如下:: My amazing blog

Entry one

This is my first entry.

Entry two

This is my second entry.

注意, 由于子模板中没有定义 ``sidebar`` 块, 所以直接使用父模板的值. 父模板 ``{% block %}`` 标签中的内容总是作为备选. 你可以根据需要使用任意层次的继承. 一种常见的使用继承的方式是下面的三层继承结构: * 创建一个 ``base.html`` 模板来控制网站的主要外观及风格. * 为站点的每个"部分"创建一个 ``base_SECTIONNAME.html`` 模板. 比如, ``base_news.html``, ``base_sports.html``. 这些模板都继承于 ``base.html``, 用来控制这些特定部分的样式和内容. * 为每一种页面类型创建独立的模版, 例如新闻内容或者博客文章. 这些模版继承于对应部分的模版. 这种方式使代码得到最大程度的复用, 并且使得添加内容到共享的内容区域更加简单, 例如部分的导航. 下面是一些使用继承技巧: * 使用模板继承时, :ttag:`{% extends %}` 必须是模板文件中的第一个标签, 否则模板继承不会生效. * 在基础模板中使用的 :ttag:`{% block %}` 标签越多越好. 记住, 在子模板中不必实现父模板中所有的blocks, 所以你可以在某些的blocks中填入合理的默认值, 然后之后只需要定义需要的block, 钩子多总比钩子少好. * 如果你发现自己的内容在多个模板中重复, 那么可能你需要考虑将这些内容移到父模板的 ``{% block %}`` 中. * 如果你需要获取父模板block中的内容, 可以使用 ``{{ block.super }}`` 变量. 如果你想在父模板中添加的内容而不是覆盖它, 这很有用. 使用 ``{{ block.super }}`` 插入的数据不会被自动转义(参见 `下一节`_), 因为如果需要的话, 它已经在父模板中被转义了. * 为增加可读性, 你可以给 ``{% endblock %}`` 标签起一个 *名字*. 例如:: {% block content %} ... {% endblock content %} 在大型模版中, 这个方法可以帮你清楚的看到哪一个 ``{% block %}`` 标签被关闭了. 最后, 请注意不要在同一模板中定义多个具有相同名称标签. 此限制的存在是因为 :ttag:`block` 标签是"双向"工作的. 这个意思是, :ttag:`block` 标签不仅是提供了一个填充的位置, 它定义了向父模版的位置中所填的内容. 如果在一个模版中有两个名字一样的 :ttag:`block` 标签, 模版的父模版将不知道使用哪个block的内容. .. _下一节: #automatic-html-escaping .. _automatic-html-escaping: 自动转义HTML ======================= 从模板生成HTML总是存在这样一个风险: 即变量值可能会包含影响HTML最终呈现的字符. 例如, 考虑这个模板片段:: Hello, {{ name }} 乍一看, 这似乎是一种无伤大雅的显示用户姓名的方式, 但考虑一下, 如果用户将自己的姓名设置为:: 使用这个值, 该模板会被渲染为:: Hello, ...这意味着浏览器会弹出一个JavaScript警报框! 同样, 如果名称中包含一个 ``'<'`` 符号,像这样呢? .. code-block:: html username 这样一来, 呈现出来的模板就会是这样的:: Hello, username ...这又会导致网页的其余部分被加粗! 显然, 不应该盲目信任用户提交的数据, 并且直接插入到你的网页中, 因为恶意用户可能会利用这种漏洞做潜在的坏事. 这种类型的安全漏洞被称为 `跨站点脚本`_ (XSS) 攻击. 为避免这个问题, 你有两个选择: * 第一, 对每个不被信任的变量使用 :tfilter:`escape` 过滤器(见下文), 它可以将潜在的有害HTML字符转换为无害的字符. 在Django的开始几年里这是默认的解决方案, 但是这样就把责任给了 *你*, 也就是开发者/模板作者, 需要你来确保都被转义了, 但是很容易忘记对数据进行转义. * 第二, 你可以利用Django的HTML自动转义功能. 本节剩余部分将介绍自动转义的工作原理. 默认情况下, 在Django中模板都会为每个变量输出自动转义. 明确地说, 这五个字符会被转义: * ``<`` 会被转成 ``<`` * ``>`` 会被转成 ``>`` * ``'`` (单引号) 会被转成 ``'`` * ``"`` (双引号) 会被转成 ``"`` * ``&`` 会被转成 ``&`` 再次强调这个行为是默认启用的. 如果你使用Django的模板系统, 你就会受到此保护. .. _跨站点脚本: https://en.wikipedia.org/wiki/Cross-site_scripting 如何关闭它 ------------------ 如果你不希望数据自动转义, 无论是在站点, 模板还是变量级别, 可以使用下面几种方法来关闭它. 为什么想要关闭它呢? 因为有时, 模板变量中包含一些希望以原始HTML形式呈现的数据, 并不想转义这些内容. 例如, 在数据库中储存一些HTML代码, 并希望将其直接嵌入到模板中. 或者, 使用Django的模板系统来生成非HTML的文本 -- 比如邮件内容. 对于单个变量 ~~~~~~~~~~~~~~~~~~~~~~~~ 关闭单个变量的自动转义可以使用 :tfilter:`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`` 带有 ``''``, 输出将是:: This will be escaped: <b> This will not be escaped: 对于模板块 ~~~~~~~~~~~~~~~~~~~ 要控制模板的自动转义, 可以将模板(或模板的某一部分)放在 :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` 标签包含的模板, 就像所有block标签那样. 例如: .. snippet:: :filename: base.html {% autoescape off %}

{% block title %}{% endblock %}

{% block content %} {% endblock %} {% endautoescape %} .. snippet:: :filename: child.html {% extends "base.html" %} {% block title %}This & that{% endblock %} {% block content %}{{ greeting }}{% endblock %} 由于在基础模板中关闭了自动转义, 所以在子模板中也会关闭, 当 ``greeting`` 变量带有 ``Hello!`` 字符时, 会呈现以下的HTML渲染结果::

This & that

Hello! 注意 ----- 一般来说, 模板作者不需要太担心自动转义的问题. Python端的开发人员(编写视图和自定义过滤器的人)会考虑在哪些情况下数据不应该被转义, 并进行适当地标记数据让其在模板中正常工作. 如果你在不确定是否启用自动转义的情况下创建的模板, 那么可以在所有需要转义的变量中添加一个 :tfilter:`escape` 过滤器. 在自动转义开启时, 也不会有 :tfilter:`escape` 过滤器 *双重转义* 数据的问题 -- :tfilter:`escape` 过滤器不会影响已自动转义的变量. .. _string-literals-and-automatic-escaping: 字符串和自动转义 -------------------------------------- 正如前面提到的, 过滤器的参数可以是字符串:: {{ data|default:"This is a string literal." }} 所有的字符串文本都是在 **没有** 自动转义的情况下插入到模板中的 -- 它们的行为类似于通过 :tfilter:`safe` 过滤器传入一样. 这背后的原因是, 模板作者可以控制字符串文本的内容, 因此他们可以确保在编写模板时正确地转义文本. 这意味着你应当这么写:: {{ data|default:"3 < 2" }} ...而不是:: {{ data|default:"3 < 2" }} {# Bad! Don't do this. #} 这并不影响来源于变量自身的数据. 变量的内容在必要时仍然会自动转义, 因为它们不受模板作者的控制. .. _template-accessing-methods: 方法调用 ====================== 对象的大多数方法同样可以在模板中调用. 这意味着模板能够访问到的不仅仅是类属性(比如字段名称)和视图中传入的变量. 例如, Django ORM提供了 :ref:`"entry_set"` 语法用于查找关联到外键的对象集合. 因此, 如果模型"comment"有一个外键关联到模型"task", 你可以通过 "task" 遍历其所有的comments, 像这样:: {% for comment in task.comment_set.all %} {{ comment }} {% endfor %} 同样, :doc:`QuerySets` 提供了一个 ``count()`` 方法来计算它们所包含的对象数量. 因此, 可以通过以下方法获得与当前task相关的所有comment的数量:: {{ task.comment_set.all.count }} 你也可以访问在模型上定义的方法: .. snippet:: :filename: models.py class Task(models.Model): def foo(self): return "bar" .. snippet:: :filename: template.html {{ task.foo }} 由于Django有意限制了模板语言中的逻辑处理, 不能够在模板中传递参数来调用方法. 所以数据应该在视图中处理然后传递给模板展示. .. _loading-custom-template-libraries: 自定义标签和自定义库 =============================== 某些应用提供了自定义标签和过滤器库. 如果要在模板中访问它们, 请确保应用在 :setting:`INSTALLED_APPS` 中(本例中我们会添加 'django.contrib.humanize'), 然后在模板中使用 :ttag:`load` 标签:: {% load humanize %} {{ 45000|intcomma }} 在上面例子中, :ttag:`load` 标签加载了 ``humanize`` 标签库, 然后使 ``intcomma`` 过滤器可以使. 如果你启用了 :mod:`django.contrib.admindocs`, 你可以在admin站点的文档区查找安装的自定义库列表. :ttag:`load` 标签可以接收多个库名, 使用空格分开. 例如:: {% load humanize i18n %} 如何自定义模板库请参见 :doc:`/howto/custom-template-tags`. 自定义库和模板继承 ----------------------------------------- 当你加载自定义标签或过滤器库时, 标签/过滤器仅对当前模板可用, 而不是沿模板继承路径的所有父模板或子模板都可用. 例如, 假设模板 ``foo.html`` 带有 ``{% load humanize %}``, 子模版(例如, 带有 ``{% extends "foo.html" %}`` 的模板)中 *不能* 访问humanize模板标签和过滤器. 子模版需要自己添加 ``{% load humanize %}``. 这是因为这能使模板更健全且更好维护. .. seealso:: :doc:`模板参考 ` 涵盖内置标签, 内置过滤器, 使用替代模板, 语言等.