模板

作为Web 框架,Django 需要一种很便利的方法以动态地生成HTML。 最常见的做法是使用模板。 模板包含所需HTML 输出的静态部分,以及一些特殊的语法,描述如何将动态内容插入。 创建HTML 页面模板的一个示例,参见Tutorial 3

Django 项目可以配置一个或多个模板引擎(甚至是零,如果你不需要使用模板)。 Django 的模板系统自带内建的后台 —— 称为Django 模板语言(DTL),以及另外一种流行的Jinja2 其他的模板语言的后端,可查找第三方库。

Django 为加载和渲染模板定义了一套标准的API,与具体的后台无关。 加载包括根据给定的标识找到模板然后预处理,通常会将它编译好放在内存中。 渲染表示使用Context 数据对模板插值并返回生成的字符串。

Django template language 是Django 原生的模板系统。 直到Django 1.8,这是唯一可用的内置选项。 尽管,它闭门造车,并且偏重某些方面,但是它仍然是一个优秀的模版库。 如果没有特别紧急的理由选择另外一种后台,你应该使用DTL,特别是你编写可插拔的应用并打算发布其模板的时候。 Django 中包含模板的标准应用,例如django.contrib.admin,都使用DTL。

又由于历史遗留原因,通用支持的模板引擎和Django实现的模板语言都在django.template 命名空间中。

警告

模板系统对不受信任的模板作者是不安全的。 例如,一个网站不应允许其用户提供自己的模板,因为模板作者可以执行诸如执行XSS攻击和访问可能包含敏感信息的模板变量的属性。

支持模板引擎

配置¶ T0>

模板引擎通过TEMPLATES 设置来配置。 它是一个设置选项列表,与引擎一一对应。 默认的值为空。 startproject 命令生成的settings.py 定义了一些有用的值:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            # ... some options here ...
        },
    },
]

BACKEND 是一个指向实现了Django模板后端API的模板引擎类的带点的Python路径。 内置的后端有 django.template.backends.django.DjangoTemplatesdjango.template.backends.jinja2.Jinja2

由于绝大多数引擎都是从文件加载模板的,所以每种模板引擎都包含两项通用设置:

  • DIRS 定义了一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板源文件。
  • APP_DIRS 告诉模板引擎是否应该进入每个已安装的应用中查找模板。 每种模板引擎后端都定义了一个惯用的名称作为应用内部存放模板的子目录名称。(译者注:例如django为它自己的模板引擎指定的是 ‘templates’ ,为jinja2指定的名字是‘jinja2’)

特别的是,django允许你有多个模板引擎后台实例,且每个实例有不同的配置选项。 在这种情况下你必须为每个配置指定一个唯一的NAME .

OPTIONS 中包含了具体的backend设置

用法¶ T0>

django.template.loader 定义了两个函数以加载模板。

get_templatetemplate_nameusing = None[source]

该函数使用给定的名称加载模板并返回一个Template 对象.

真正的返回值类型取决于那个用来加载模版的后台引擎。 每个后台都有各自的 Template类。

get_template()尝试获取每个模板直到有一个成功满足。 如果模板不能成功找到,将会抛出TemplateDoesNotExist 如果能够找到模板但是包含非法值,将会抛出TemplateSyntaxError

模板的查找和加载机制取决于每种后台引擎和配置

如果你想使用指定的模板引擎进行查找,请将模板引擎的NAME 赋给 get_template的using 参数

select_templatetemplate_name_listusing = None[source]

get_template()select_template()很相似, 只不过它用了一个模板名称的列表作为参数。 按顺序搜索模板名称列表内的模板并返回第一个存在的模板。

如果导入模板失败,django.template中定义的两个异常,有可能会被抛出:

exception TemplateDoesNotExist(msg, tried=None, backend=None, chain=None)[source]

在找不到该模板时,抛出该错误。 它接受以下可选参数,用于在调试页面上填充template postmortem

backend
发生异常的模板后端实例。
tried
找到模板时尝试的源的列表。 这被格式化为包含(origin, 状态)的元组列表,其中originorigin-like对象和status是一个字符串,其原因是没有找到模板。
chain
尝试加载模板时引发的中间TemplateDoesNotExist异常列表。 这被函数使用,例如get_template(),尝试从多个引擎加载给定的模板。
exception TemplateSyntaxError(msg)[source]

当模板内出现错误时,将被抛出。

select_template()get_template() 返回的Template 对象必须要有一个render()方法,协议如下:

Template.render(context=None, request=None)

通过给定的 context 对该模板进行渲染。

如果提供了 context ,那么它必须是一个dict对象. 如果没有提供,引擎将是用空 context 对模板进行渲染。

如果要提供request参数 ,必须使用 HttpRequest对象. 之后模板引擎会使它连同CSRF token一起在模板中可用。 具体如何实现由相应模板后端决定。

关于搜索算法的例子。 该例子下 TEMPLATES 的配置是:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            '/home/html/example.com',
            '/home/html/default',
        ],
    },
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2',
        'DIRS': [
            '/home/html/jinja2',
        ],
    },
]

如果你调用函数get_template('story_detail.html'), Django按以下顺序查找story_detail.html:

  • /home/html/example.com/story_detail.html'django'引擎)
  • /home/html/default/story_detail.html'django'引擎)
  • /home/html/jinja2/story_detail.html'jinja2'引擎)

如果你调用函数 select_template(['story_253_detail.html', 'story_detail.html']), Django按一下顺序查找:

  • /home/html/example.com/story_253_detail.html'django'引擎)
  • /home/html/default/story_253_detail.html'django'引擎)
  • /home/html/jinja2/story_253_detail.html'jinja2'引擎)
  • /home/html/example.com/story_detail.html'django'引擎)
  • /home/html/default/story_detail.html'django'引擎)
  • /home/html/jinja2/story_detail.html'jinja2'引擎)

当Django发现模板存在后便停止搜寻。

提示

你可以通过 select_template() 来实现更为灵活的模板加载。 例如,如果你写了一个新闻故事,想要一些故事来制作自定义模板,可以使用select_template(['story_%s_detail.html' % t2 > story.id, 'story_detail.html']) 这允许你为每个故事使用一个个性化的模板,同时有一个后背模板给没有个性化模板的故事使用。

如果可能(其实这会更好)—将模版文件,放在包含模版的目录的子目录下。 基本思路是,使得每个APP的的模版子目录下都有一个子目录来唯一对应这个APP。

这样做可以增强你的APP可用性。 将所有的模版放在根模版目录下会引发混淆。

要在一个子目录内加载模板,像这样使用斜线就好了:

get_template('news/story_detail.html')

作为上述使用相同的 TEMPLATES 选项,这将会尝试加载下列模板︰

  • /home/html/example.com/news/story_detail.html'django'引擎)
  • /home/html/default/news/story_detail.html'django'引擎)
  • /home/html/jinja2/news/story_detail.html'jinja2'引擎)

另外,为了减少加载模板、渲染模板等重复工作,django提供了处理这些工作的快捷函数。

render_to_string(template_name, context=None, request=None, using=None)[source]

render_to_string() 会像 get_template()一样加载模板并立即调用 render() 方法。 它需要以下参数。

TEMPLATE_NAME
要加载和呈现的模板的名称。 如果是模板名称列表,Django使用select_template()而不是get_template()来查找模板。
context
要用作模板的上下文进行渲染的dict
request
HttpRequest是可选的,并且在整个模版渲染期都是可用的。
using
一个可选的模板引擎NAME 搜索模板将仅限于该引擎。

用法示例:

from django.template.loader import render_to_string
rendered = render_to_string('my_template.html', {'foo': 'bar'})

另请参阅调用render_to_string()render()快捷方式,并将结果馈送到适合从视图返回的HttpResponse

最后,您可以直接使用配置的引擎:

engines

模板引擎可在django.template.engines中使用:

from django.template import engines

django_engine = engines['django']
template = django_engine.from_string("Hello {{ name }}!")

在此示例中,查找键 - 'django'是引擎的NAME

内置后端

class DjangoTemplates[source]

设置BACKEND'django.template.backends.django.DjangoTemplates' 来配置Django模板引擎。

APP_DIRStemplates 时, True 引擎会在已安装应用的 DjangoTemplates 子目录中查找模板文件。 这个通用名称是保持向后兼容的。

DjangoTemplates 引擎 OPTIONS 配置项中接受以下参数:

  • 'autoescape':一个布尔值,用于控制是否启用HTML自动转义。

    它默认为True

    警告

    如果您正在渲染非HTML模板,则只将其设置为False

    Django中的新功能1.10:

    添加了autoescape选项。

  • 'context_processors': 是一个包含以"."为分隔符的python调用路径的列表,在一个template被request渲染时,它可以被调用以产生context的数据。 这些可调用要求一个请求对象作为其参数,并返回要合并到上下文中的项目的dict

    它默认是个空的 list。

    有关详细信息,请参阅RequestContext

  • 'debug':打开/关闭模板调试模式的布尔值。 如果它True,那么奇怪的错误页面将显示模板渲染期间引发的任何异常的详细报告。 此报告包含模板的相关代码段,并突出显示相应的行。

    它默认和setting中的 DEBUG有相同的值。

  • 'loaders':模板加载器类的虚拟Python路径列表。 每个Loader类知道如何从特定源导入模板。 你可以选择使用字符串元组来代替字符串。 元组的第一项是 Loader类名,接下来的项在初始化期间会被传递给Loader

    默认值取决于DIRSAPP_DIRS的值。

    有关详细信息,请参见Loader types

  • 'string_if_invalid':作为字符串的输出,模板系统应该用于无效(例如拼写错误的)变量。

    它默认为空字符串。

    有关详细信息,请参见How invalid variables are handled

  • 'file_charset':用于读取磁盘上的模板文件的字符集。

    它默认为FILE_CHARSET的值。

  • 'libraries':模板标签模块的标签和点缀Python路径的字典,用于注册模板引擎。 这可以用于添加新的库或为现有库添加备用标签。 像这样:

    OPTIONS={
        'libraries': {
            'myapp_tags': 'path.to.myapp.tags',
            'admin.urls': 'django.contrib.admin.templatetags.admin_urls',
        },
    }
    

    可以通过将相应的字典键传递到{% load %}标记来加载库。

  • 'builtins':添加到built-ins的模板标签模块的点缀Python路径列表。 像这样:

    OPTIONS={
        'builtins': ['myapp.builtins'],
    }
    

    标签和内置库的过滤器可以在没有首先调用{% load %}标签的情况下使用。

class Jinja2[source]

需要安装 Jinja2 :

$ pip install Jinja2

设置 BACKEND'django.template.backends.jinja2.Jinja2' 时,则启用 Jinja2 引擎。

APP_DIRSjinja2时, True 引擎则在已安装应用的名为 Jinja2 的子目录中查找模板文件。

最重要的配置项是 OPTIONS 中的 'environment'项。 它应该是一个带点的Python路径,调用它则可返回 Jinja2 的环境变量。 它默认为'jinja2.Environment' Django调用该可调用项并传递其他选项作为关键字参数。 此外,Django添加了与Jinja2不同的默认值,用于几个选项:

  • True'autoescape'
  • 'loader':为DIRSAPP_DIRS
  • 'auto_reload'settings.DEBUG
  • 'undefined'DebugUndefined if settings.DEBUG else 未定义 T7> T2>

Jinja2引擎也接受以下OPTIONS

  • 'context_processors': 是一个包含以"."为分隔符的python调用路径的列表,在一个template被request渲染时,它可以被调用以产生context的数据。 这些可调用要求一个请求对象作为其参数,并返回要合并到上下文中的项目的dict

    它默认是个空的 list。

    不要使用具有Jinja2模板的上下文处理器。

    上下文处理器对于Django模板很有用,因为Django模板不支持使用参数调用函数。 由于Jinja2没有这样的限制,建议您使用jinja2.Environment将您作为上下文处理器使用的功能放在模板可用的全局变量中,如下所述。 然后可以在模板中调用该函数:

    {{ function(request) }}
    

    一些Django模板上下文处理器返回固定值。 对于Jinja2模板,这个间接层不是必需的,因为您可以直接在jinja2.Environment中添加常量。

    为Jinja2添加上下文处理器的原始用例涉及:

    • 进行昂贵的计算,这取决于请求。
    • 需要在每个模板的结果。
    • 在每个模板中多次使用结果。

    除了满足所有这些条件之外,将功能传递给模板更简单,更符合Jinja2的设计。

Django中的新功能1.11:

添加了'context_processors'选项。

有意将默认配置保持为最小。 如果使用请求呈现模板(例如,当使用render())时,Jinja2后端会添加全局变量requestcsrf_inputcsrf_token到上下文。 除此之外,这个后端不会创建一个Django风格的环境。 它不知道Django过滤器和标签。 为了使用Django特定的API,您必须将它们配置到环境中。

例如,你可以创建一个包含以下内容的myproject/jinja2.py

from __future__ import absolute_import  # Python 2 only

from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse

from jinja2 import Environment


def environment(**options):
    env = Environment(**options)
    env.globals.update({
        'static': staticfiles_storage.url,
        'url': reverse,
    })
    return env

并将'myproject.jinja2.environment'选项设置为'environment'

之后你可以用下面的内容构建Jinja2模板:

<img src="{{ static('path/to/company-logo.png') }}" alt="Company Logo">

<a href="{{ url('admin:index') }}">Administration</a>

标签和过滤器的概念尽管在Django模板语言和Jinja2中都存在,但使用起来却不相同。 因为Jinja2支持传递参数给模版中的可调用对象,在Django中许多需要模版标记和过滤的特性,在Jinja2可以通过简单的调用函数来得到,比如上面所显示的例子。 Jinja2的全局命名空间移除了模版上下文处理器的需求。 Django模版语言没有等同于Jinja2 tests的东西。

自定义后端

为了使用其他的模板系统,这里将介绍如何实现一个自定义模板后台。 模板后台都是继承自django.template.backends.base.BaseEngine类的。 它必须实现 from_string()方法,也可实现可选函数get_template() 下面是一个虚构的foobar模板库的示例:

from django.template import TemplateDoesNotExist, TemplateSyntaxError
from django.template.backends.base import BaseEngine
from django.template.backends.utils import csrf_input_lazy, csrf_token_lazy

import foobar


class FooBar(BaseEngine):

    # Name of the subdirectory containing the templates for this engine
    # inside an installed application.
    app_dirname = 'foobar'

    def __init__(self, params):
        params = params.copy()
        options = params.pop('OPTIONS').copy()
        super(FooBar, self).__init__(params)

        self.engine = foobar.Engine(**options)

    def from_string(self, template_code):
        try:
          return Template(self.engine.from_string(template_code))
        except foobar.TemplateCompilationFailed as exc:
            raise TemplateSyntaxError(exc.args)

    def get_template(self, template_name):
        try:
            return Template(self.engine.get_template(template_name))
        except foobar.TemplateNotFound as exc:
            raise TemplateDoesNotExist(exc.args, backend=self)
        except foobar.TemplateCompilationFailed as exc:
            raise TemplateSyntaxError(exc.args)


class Template(object):

    def __init__(self, template):
        self.template = template

    def render(self, context=None, request=None):
        if context is None:
            context = {}
        if request is not None:
            context['request'] = request
            context['csrf_input'] = csrf_input_lazy(request)
            context['csrf_token'] = csrf_token_lazy(request)
        return self.template.render(context)

有关详细信息,请参阅DEP 182

自定义模板的调试集成

当Django调试页面出现模板错误时,有钩子提供详细的信息。 自定义模板引擎可以使用这些钩子来增强用户显示的追溯信息。 以下钩子可用:

模板postmortem

TemplateDoesNotExist被提出时,会显示该事件。 它列出了在尝试查找给定模板时使用的模板引擎和加载程序。 例如,如果配置了两个Django引擎,则该事件将会显示为:

../_images/postmortem.png

定制引擎可以通过在提升TemplateDoesNotExist时传递backendtried参数来填充后验。 使用postmortem should specify an origin

上下文行信息

如果在模板解析或渲染过程中发生错误,Django可以显示错误发生的行。 像这样:

../_images/template-lines.png

自定义引擎可以通过在解析和呈现期间引发的异常设置template_debug属性来填充此信息。 该属性是dict,具有以下值:

  • 'name':发生异常的模板的名称。
  • 'message':异常消息。
  • 'source_lines':之前,之后和包括发生异常的行。 这是为了上下文,所以它不应该包含20行以上。
  • 'line':发生异常的行号。
  • 之前的'before'
  • 期间的'during'
  • 之后的'after'
  • 'total'source_lines中的行数。
  • 'top'source_lines开始的行号。
  • 'bottom'source_lines结束的行号。

鉴于上述模板错误,template_debug将如下所示:

{
    'name': '/path/to/template.html',
    'message': "Invalid block tag: 'syntax'",
    'source_lines': [
        (1, 'some\n'),
        (2, 'lines\n'),
        (3, 'before\n'),
        (4, 'Hello {% syntax error %} {{ world }}\n'),
        (5, 'some\n'),
        (6, 'lines\n'),
        (7, 'after\n'),
        (8, ''),
    ],
    'line': 4,
    'before': 'Hello ',
    'during': '{% syntax error %}',
    'after': ' {{ world }}\n',
    'total': 9,
    'bottom': 9,
    'top': 1,
}

Origin API和第三方集成

Django模板具有通过template.origin属性可用的Origin对象。 这将使调试信息显示在template postmortem以及第三方库中,如Django Debug Toolbar

自定义引擎可以通过创建一个指定以下属性的对象来提供自己的template.origin信息:

  • 'name':模板的完整路径。
  • 'template_name':传递给模板加载方法的模板的相对路径。
  • 'loader_name':标识用于加载模板的函数或类的可选字符串,例如。 django.template.loaders.filesystem.Loader

Django模板语言

语法¶ T0>

关于本节

以下是关于Django模板语法的概览。 更多细节请见 language syntax reference.

Django模板是一个简单的文本文档,或用Django模板语言标记的一个Python字符串。 某些结构是被模板引擎解释和识别的。 主要的有变量和标签。

模板是由context来进行渲染的。 渲染的过程是用在context中找到的值来替换模板中相应的变量,并执行相关tags。 其他的一切都原样输出。

Django模板语言的语法包括四个结构。

变量¶ T0>

变量的值是来自context中的输出, 这类似于字典对象的keys到values的映射关系。

变量是被 {{}}括起来的部分,例如:

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.

字典查询,属性查询和列表索引查找都是通过一个点符号来实现:

{{ my_dict.key }}
{{ my_object.attribute }}
{{ my_list.0 }}

如果一个变量被解析为一个可调用的,模板系统会调用它不带任何参数,并使用调用它的结果来代替这个可调用对象本身。

标签¶ T0>

标签在渲染的过程中提供任意的逻辑。

这个定义是刻意模糊的。 例如,一个标签可以输出内容,作为控制结构,例如“if”语句或“for”循环从数据库中提取内容,甚至可以访问其他的模板标签。

Tags是由{%%} 来定义的,例如:

{% csrf_token %}

大部分标签都接受参数

{% cycle 'odd' 'even' %}

部分标签要求使用起始和闭合标签:

{% if user.is_authenticated %}Hello, {{ user.username }}.{% endif %}

reference of built-in tagsinstructions for writing custom tags都可以参阅。

过滤器¶ T0>

过滤器会更改变量或标签参数的值。

看上去像这样:

{{ django|title }}

有一个上下文 { 'django的': “在 卷筒纸 骨架 对于 完美主义者 最后期限},此模板呈现:

The Web Framework For Perfectionists With Deadlines

有些过滤器看起来更像参数:

{{ my_date|date:"Y-m-d" }}

具体可以查看 reference of built-in filtersinstructions for writing custom filters这两篇文档.

注释¶ T0>

注释看起来像这样:

{# this won't be rendered #}

A {% comment %} 标签提供多行注释。

组成部分

关于本节

本文只是对Django模板语言API作一个简单概述. 具体请参阅 API reference.

引擎

django.template.Engine 封装Django模板系统的一个实例。 直接实例化一个 Engine 的主要原因是为了在Django项目之外使用Django模板语言。

django.template.backends.django.DjangoTemplatesdjango.template.Engine的简化版,他们都是指向Django模板后端的API。

模板

django.template.Template 表示一个已编译的模板。 模板可以通过 Engine.get_template()Engine.from_string()来获取。

同样地, django.template.backends.django.Templatedjango.template.Template的一个简化版,他们指向共同的模板API.

上下文

django.template.Context 除了持有context数据以外,还持有一些元数据. 它被传递到Template.render()用于渲染一个模板.

django.template.RequestContextContext的一个子类,它存储当前的HttpRequest并且运行了模板的context处理器.

一般的API不具备这样的概念。 大多数情况下,context数据是在一个普通的dict里传递,而当前的HttpRequest 如果有需要的话是另外传递的。

加载器

模板加载器负责定位模板,加载它们,并返回Template对象.

Django提供几个built-in template loaders并且支持custom template loaders.

上下文处理器

Context处理器是这样的函数:接收当前的 HttpRequest 作为参数,并返回一个 dict,该字典中包含了将要添加到渲染的context中的数据。

它们的主要用途是添加所有的模板context共享的公共数据,而不需要在每个视图中重复代码。

Django提供了很多 built-in context processors. 实现自定义context处理器很简单,只要定义一个函数。