Django模板语言

Django模板语言

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

变量

变量看起来是这样的:{{ variable }}。当模板引擎遇到变量时,它会将变量用里面的值展开来代替。变量名由任意字母和下划线组成,但是不能以下划线开头。绝对不能在变量名中出现空格或其它符号。

如:
My first name is {{ first_name }}. My last name is {{ last_name }}
上面的内容使用{'first_name':'John, 'last_name':'Doe'}这个字典来渲染成:
My first name is John. My last name is Doe.

使用点(.)符号来进行字典查找、访问变量属性和列表索引查找。

后台操作
技术上讲,当模板系统遇到点号,它按顺序尝试下面的查找:

  • 字典查找(点后的内容会被当作字典类型的KEY使用)
  • 属性或方法查找
  • 数字索引查找

如果结果值是可调用的,它被以无参数的形式调用。调用结果变成模板值。

这个查找顺序能导至一些未知行为(超出对象的字典查找范围)。考虑下面的代码段,它试图超出collections.defaultdict的查找范围:

    {% for k, v in defaultdict.items %}
              do something with k and v here...
    {% endfor %}

因为先从字典查找开始,字典查找强行开始并提供了一个默认值代替使用.items()方法。在本例中先考虑转换成一个字典。

如果使用了一个不存在的变量,模板系统将会使用string_if_invalid选项的值代替,在默认情况下它的值是”(空字符串)。

Note that “bar” in a template expression like {{ foo.bar }} will be interpreted as a literal string and not using the value of the variable “bar”, if one exists in the template context.

变量的属性以下划线开头,可能不能被访问,因为它可能被认为是私有属性。

过滤器

可以通过“过滤器”修改变量。

过滤器看起来是这个样子的:{{ name | lower }}。在过滤器lower修改后,显示{{ name }}变量的值。该过滤器将文本转换成小写。通过管道操作符(|)应用过滤器。

过滤器可以使用多个。一个过滤器的输出做为下一个过滤器的输入。{{ text | escape | linebreaks }}这是将文本中的特殊符号转换成HTML接收的文本形式,然后交换行转换成<p>标记。

有些过滤器是有参数的。过滤器参数看起来是这样的:{{ bio | truncatewords:30 }}。这会只显示bio变量中的前30个字符。

过滤器中包含的空格必须被双引号包含起来;例如,用逗号和空格链接列表{{ list | join:”,  ” }}

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.7MB。

还有这只是少数几个例子, built-in filter reference 有完整列表。

也可以创建自己的模板过滤器; Custom template tags and filters.

标签

标签看起来是这样的:{% tag %}。标签比变量更复杂:有些创建文本输出,有些控制程序逻辑,有些加载额外信息到模板。

有些标签需要开始和结束标签:{% tag %} tag contents {% endtag %}。

Django 有二十几个内建模板标签。可以在built-in tag reference看到所有。下面展示一些常用的标签:

for
循环。例如显示athlete_list的元素:

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

if, elif 和 else
展开变量,如果变量的值是“真”,那么显示块中的内容:

{%  if  athlete_list %}

    Number of athletes: {{ athlete_list|length}}

{% elif athlete_in_locker_room_list %}

    Athletes sould be out of the locker room soon!

{% else %}

    No athletes.

{% endif %}

在上面的例子中,如果athlete_list不是空, {{ athlete_list|length }}就会显示它的值。否则,如果athlete_in_locker_room_list 不是空,那么会显示“Athletes sould 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是个例外。

blockextends
设置模板继承,可以在模板中设置一个“基类”模板,是大幅缩减重复工作的工具。

模板继承

在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="contendt">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

这个模板我们称之为“base.html”,定义了一个简单的HTML“骨架”文档,它可以用于一个简单的两列页面。它的子模板的工作是填充空白的block块内容。

在本例中,block标记定义了三个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 %}标记里的内容一直是用于备用。

你可以使用任意多层的模板继承,只要有需要。使用继承的常用方法由下面三级组成:

  • 创建一个base.html模板,它保存了站点主要的构架。
  • 创建一个base_SECTIONNAME.html模板,用于站点的每一章节(“section”)。例如,base_news.html,base_sports.html。这些模板所有都是base.html的扩展,并包含的不同章节的风格设计。
  • 为每种页面创建独立的模板,如:新闻或是blog。这些模板都是base_SECTIONNAME.html的适当扩展。

这种方法可以最大程度的重用代码,并且很容易添加项目到共享内容区域,如章节导航。

这里还有一些使用继承的技巧:

  • 如果在模板中使用了{% extends %},它必须先在被扩展的模板中使用模板标记(block)。
  • 在父模板中有越多的{% block %}越好。记信,子模板不用填充所有的在父模板中定义的块。
  • 如果你在模板中发现了重复内容,这可能意味着你应该移动重复的内容到父级模板。
  • 如果你需要从父模板获得block内容,那么{{ block.super }}变量可以做到。如果你想向父级block添加内容,而不是完全覆盖它,这非常有用。使用{{ block.super }}插入的数据不会被自动展开,因为它已经是展开形式。
  • 使用模板标记as语法在{% block %}外部建立的变量,不能在block内使用。例如:
    {% trans "Title" as title %}
    {% block content %}{{ title }}{% endblock %}

    这个模板不会产生任何预期效果
  • 额外的易读性,你可以选择性的给于{% endblock %}标记一个名字:
    {% block content %}
    ...
    {% endblock content %}

    在一个大型的模板中,这个技术可以帮你清晰的看到哪个{% block %}标记已经结束。

最后,注意你不能在同一个模板中定义多个同名block。

注释

注释标记{# #}。两个井号之前可以放任何字符,不过只能单行。多行要用多行注释标签。

{% comment %}…{% endcomment %}

HTML自动转义

当从模板产生HTML时,变量包含的字符会影响HTML的结果,这一直是个威胁。例如,考虑这个模板框架:

Hello, {{ name }}

首先,对于显示用户名来说这看起来并不是危险的操作,但是考虑一下如果用户输入以下内容为他们的名字会发生什么?:

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

使用这个做为名字值时,模板会变成:

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

这意味着浏览器会弹出一个JavaScript警告窗口!
类似的,如果名字中包含了‘<’号,会怎么样?如:

<b>username

页面会保留粗体字。

用户提交的数据不能盲目的被信任并直接插入到你的页面上,因为一个恶意用户可能使用这类漏洞做一些潜在坏事。这类安全问题被叫做:Cross Site Scripting(XSS)攻击。

避免这类问题你有两个选择:

  • 一,通过escape过滤器运行这些不被信任的变量,它可以转换潜在HTML威胁字符到无威胁状态。前些年在DJANGO中这是默认的解决方法,但是这把责任转给了开发者,开发者很容易忘记转换数据。
  • 二,使用Django的自动HTML转义(escaping)特性。本节剩下部分描述了如何使用自动展开。

Django默认情况下,每一个模板自动的转义每一个变量标签。特下面五种:

  • <转换成&lt;
  • >转换成&gt;
  • (单引号)转换成&#39;
  • (双引号)转换成&quot;
  • &转换成&amp;

再次声明,转义在Django的模板中是默认开启的。

如何关闭它

如果你不希望数据被自动转义,可以在站点、模板或变量级别上关闭它。

为什么要关闭它呢?因为有时模板变量包含的数据你希望就是以源生的HTML发布出来,在这时你不希望对其进行转义。例如你可以存储HTML的粗体在你的数据库中并希望它直接嵌入到你的模板中。

独立变量

关闭自动转义使用safe过滤器:

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

safe做为safe from further escaping or can be safely interpreted as HTML的速记形式。在本例中如果data包含了'<b>’,那么输出将会是直接显示出‘<b>’这三个字符:

This will be escaped: <b>
下面使用了safe过滤器,输出的字体会是粗体(下面的<b>只是为了表达粗体,并不是显示的内容):      
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 %}

自动转义标签可以传递它的效果到子模板中。

注意

一般来说,模板的作者不需要过分担心自动转义的问题。PYTHON端的开发者才需要思考哪些数据不需要被转义并适当的标记。

如果你建立了一个模板,而且不能确定它使用的场景是否开启了自动转义,那么添加escape过滤器到任何需要转义的变量上。当自动转义是开启的,不用担心escape过滤器会双重转义变量。

字符串和自动转义

上面说过,过滤器参数可以是字符串:

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

所有的字符串不会带有任何自动转义被插入到模板中,就像使用了safe过滤器一样,如:

{{ data | default :3 &lt; 2 }}

页面会显示3<2。&不会被转义成“&amp;”

访问方法调用

多数方法是依托于对象的,在模板内部可用。这意味着模板可以访问超过在视图中传递的类属性和变量的更多内容(如:字段名)。例如,Django ORM 提供了‘entry_set’语法,用于查找在外键上相关对象集合。现在有一个名为“comment”的模型,它有一个外键关联到一个名为“task”的模型,可以通过循环,使所有的“comment”关联到的“task”都被遍历一次:

{% for comment in task.comment_set.all %}
    {{ comment }}
{% endfor %}

类似的,QuerySets提供了count()方法,用于统计它们包含的对象数量。因此你可以获得所有comment关联task模型的数量:

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

See Custom template tags and filters for information on writing your own custom template libraries.

自定义库和模板继承

当加载了一个自定义标签或过滤器库后,标签、过滤器只在当前的模板可用——不会属于任何父或子模板的继承路径。

例如,如果模板foo.html包含{% load humanize %},它的子模板将不能访问humanize模板的标记和过滤器。子模板只能使用它自己的{% load humanize %}

发表回复