模板

作为一个网络框架,Django 需要一种方便的方式来动态生成 HTML。最常见的方法是依靠模板。一个模板包含了所需 HTML 输出的静态部分,以及一些特殊的语法,描述了如何插入动态内容。关于使用模板创建 HTML 页面的实战例子,请看 教程 3

一个 Django 项目可以配置一个或多个模板引擎(如果你不使用模板,甚至可以不配置模板)。Django 内置了自己的模板系统后端,创造性地称为 Django 模板语言(DTL),以及流行的替代版本 Jinja2。其他模板语言的后端可以从第三方获得。你也可以编写自己的自定义后端,参见:自定义模板后端

Django 定义了一个标准的API,用于加载和渲染模板,而不考虑后端。加载包括为给定的标识符找到模板并对其进行预处理,通常是将其编译成内存中的表示形式。渲染是指将上下文数据插入模板,并返回结果字符串。

Django 模板语言 是 Django 自己的模板系统。在 Django 1.8 之前,它是唯一的内置选项。它是一个很好的模板库,尽管它是相当有主见的,并且有一些特殊的地方。如果你没有迫切的理由选择另一个后端,你应该使用 DTL,特别是当你正在编写一个可插拔的应用程序,并且你打算发布模板时。Django 的 contrib 应用如果包含模板,比如 django.contrib.admin,就使用 DTL。

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

警告

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

Django 模板语言

语法

关于本节

这是对 Django 模板语言语法的概述。详细信息请参见 语言语法参考

Django 模板是使用 Django 模板语言标记的一个文本文档或Python字符串。模板引擎可以识别和解释一些构造。主要是变量和标签。

模板是通过上下文来渲染的。渲染用变量的值替换变量,变量的值在上下文中查找,并执行标签。其他的一切都按原样输出。

Django 模板语言的语法涉及四个构造。

变量

变量从上下文中输出一个值,上下文是一个类似于字典的对象,将键映射到值。

Variables are surrounded by {{ and }} like this:

My first name is {{ first_name }}. My last name is {{ last_name }}.

With a context of {'first_name': 'John', 'last_name': 'Doe'}, this template renders to:

My first name is John. My last name is Doe.

Dictionary lookup, attribute lookup and list-index lookups are implemented with a dot notation:

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

如果变量解析为可调用对象,则模板系统将不带任何参数的情况下调用它,并使用其结果代替可调用对象。

标签

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

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

Tags are surrounded by {% and %} like this:

{% csrf_token %}

Most tags accept arguments:

{% cycle 'odd' 'even' %}

Some tags require beginning and ending tags:

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

提供 内置标签参考 以及 自定义标签编写指南

过滤器

过滤器转换变量和标签参数的值。

They look like this:

{{ django|title }}

With a context of {'django': 'the web framework for perfectionists with deadlines'}, this template renders to:

The Web Framework For Perfectionists With Deadlines

Some filters take an argument:

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

提供 内建过滤器参考 以及 自定义过滤器编写指南.

Comments

Comments look like this:

{# this won't be rendered #}

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

组件

关于本节

这是对 Django 模板语言 API 的概述。详细信息请参见 API 参考

引擎

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

django.template.backends.django.DjangoTemplates 是一个简单封装,使 django.template.Engine 适应 Django 的模板后端API。

模板

django.template.Template 代表已编译的模板。模板可以通过 Engine.get_template()Engine.from_string() 获得。

同样 django.template.backends.django.Template 是一个简单封装,使 django.template.Template 适应通用模板 API。

上下文

django.template.Context 除了上下文数据外,还保存了一些元数据。它被传递给 Template.render() 来渲染模板。

django.template.RequestContextContext 的子类,它储存当前的 HttpRequest 并运行模板上下文处理器。

通用 API 没有对应的概念。上下文数据以普通的 dict 传递,而当前的 HttpRequest 则根据需要单独传递。

加载器

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

Django 提供了几个 内建模板加载器 并且支持 自定义模板加载器

上下文处理器

上下文处理器是接收当前的 HttpRequest 作为参数,并返回一个 dict 的数据添加到渲染上下文的函数。

它们的主要用途是将所有模板共享的通用数据添加到上下文中,而无需在每个视图中重复代码。

Django 提供了许多 内置上下文处理器,你也可以实现自己的其他上下文处理器。

模板引擎的支持

配置

模板引擎是通过 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 告诉引擎是否应该在已安装的应用程序中寻找模板。每个后端都为应用程序中存储模板的子目录定义了一个惯用名称。

虽然不常见,但可以使用不同的选项配置同一后端的多个实例。 在这种情况下,你应该为每个引擎定义一个唯一的 NAME

OPTIONS 包含特定于后端的配置。

用法

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

get_template(template_name, using=None)

此函数使用给定名称加载模板并返回 Template 对象。

返回值的确切类型取决于加载模板的后端。 每个后端都有自己的 Template 类。

get_template() 依次尝试每个模板引擎,直到成功为止。如果找不到模板,则会引发 TemplateDoesNotExist 错误。如果找到模板但包含无效语法,则会引发 TemplateSyntaxError 错误。

搜索和加载模板的方式取决于每个引擎的后端和配置。

如果你想把搜索限制在一个特定的模板引擎上,在 using 参数中传递该引擎的 NAME

select_template(template_name_list, using=None)

select_template() 就像 get_template(),不同的是,它接受一个模板名称的列表。它按顺序尝试每个名字,并返回第一个存在的模板。

如果加载模板失败,则可能会引发在 django.template 中定义的以下两个异常:

exception TemplateDoesNotExist(msg, tried=None, backend=None, chain=None)

当找不到模板时引发此异常。 它接受以下可选参数在调试页面上填充 模板事后检验

backend
产生异常的模板后端实例。
tried
查找模板时尝试过的来源列表。它的格式为包含 (origin, status) 的元组列表,其中 origin 是一个 类 origin 对象而 status 是一个说明找不到模板原因的字符串。
chain
尝试加载模板时引发的一系列中间 TemplateDoesNotExist 异常列表。这由函数使用,例如:get_template(),这些函数尝试从多个引擎加载给定的模板。
exception TemplateSyntaxError(msg)

当找到模板但包含错误时,将引发此异常。

get_template()select_template() 返回的 Template 对象必须提供具有以下签名的 render()` 方法:

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

使用给定的上下文渲染此模板。

如果提供了 context ,则必须是 dict。如果未提供,则引擎将使用空上下文渲染模板。

如果提供了 request,它必须是 HttpRequest。同时引擎必须使它和 CSRF 令牌在模板中可用。如何实现这一点由每个后端决定。

下面是一个搜索算法的例子。在这个例子中 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 将按顺序查找的文件:

  • /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 发现一个模板存在时,它就会停止寻找。

Use django.template.loader.select_template() for more flexibility

你可以使用 select_template() 灵活的加载模板。例如,如果你写了一个新闻故事,并希望一些故事有自定义模板,使用类似 select_template(['story_%s_detail.html' % story.id, 'story_detail.html']) 。这将允许你为单个故事使用自定义模板,为没有自定义模板的故事使用备用模板。

可以——而且最好是——在每个包含模板的目录内的子目录中组织模板。惯例是为每个 Django 应用程序创建一个子目录,根据需要在这些子目录中包含子目录。

这样做是为了你自己的理智。将所有模板存储在一个目录的根级别会很麻烦。

要加载子目录中的模板,请使用斜杠,如下所示:

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)

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() 快捷函数,它调用 render_to_string() ,并将结果提供给 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

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

APP_DIRSTrue 时,DjangoTemplates 引擎会在已安装应用程序的 templates 子目录中寻找模板。这个通用名称是为了向后兼容而保留的。

DjangoTemplates 引擎接受以下 OPTIONS:

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

    默认为 True

    警告

    只有当你渲染非 HTML 模板时,才将其设置为 False

  • 'context_processors':当模板被请求渲染时,用于填充上下文的可调用项的点分隔 Python 路径列表。这些可调用的对象以一个请求对象作为参数,并返回一个 dict 的项目,这些项目将被合并到上下文中。

    默认为空列表。

    查看 RequestContext 获取更多信息。

  • 'debug':开启/关闭模板调试模式的布尔值。如果是 True,错误页面将显示模板渲染过程中出现的任何异常的详细报告。该报告包含模板的相关片段,并突出显示相应的行。

    默认为 DEBUG 配置的值。

  • 'loaders':模板加载器类的点分隔 Python 路径列表。每个 Loader 类都知道如何从特定源导入模板。可以选择使用元组来代替字符串。元组中的第一项应该是 Loader 类名,随后的项在初始化期间传递给 Loader

    默认值取决于 DIRSAPP_DIRS 的值。

    查看 加载器类型 获取详细信息。

  • 'string_if_invalid':模板系统对无效变量(如拼写错误)应将此字符串输出。

    默认为空字符串。

    查看 如何处理无效变量 获取更多信息。

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

    默认为 'utf-8'

  • 'libraries':模板标签模块的标签字典和点分隔 Python 路径,用于向模板引擎注册。 这可用于添加新库或为现有库提供替代标签。例如:

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

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

  • 'builtins':要添加的 内置模板标签和过滤器 的点分隔 Python 路径列表。例如:

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

    可以使用内置库中的标签和过滤器,而不需要先调用 {% load %} 标签。

class Jinja2

需要安装 Jinja2

$ python -m pip install Jinja2
...\> py -m pip install Jinja2

设置 BACKEND'django.template.backends.jinja2.Jinja2' 以配置一个 Jinja2 引擎。

APP_DIRSTrue 时,Jinja2 引擎会在安装的应用程序的 jinja2 子目录中查找模板。

OPTIONS 中最重要的条目是 'environment'。它是一个点分隔 Python 路径,指向一个返回 Jinja2 环境的可调用对象。默认为 'jinja2.Environment'。Django 调用该可调用对象并传递其他选项作为关键字参数。此外,Django 为一些选项添加了不同于 Jinja2 的默认值。

  • 'autoescape'True
  • 'loader':为 DIRSAPP_DIRS 配置的加载器
  • 'auto_reload'settings.DEBUG
  • 'undefined'DebugUndefined if settings.DEBUG else Undefined

Jinja2 引擎也接受以下 OPTIONS

  • 'context_processors':当模板被请求渲染时,用于填充上下文的可调用项的点分隔 Python 路径列表。这些可调用的对象以一个请求对象作为参数,并返回一个 dict 的项目,这些项目将被合并到上下文中。

    默认为空列表。

    不建议将上下文处理器与 Jinja2 模板一起使用。

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

    {{ function(request) }}
    

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

    最初为 Jinja2 增加上下文处理器的用例涉及:

    • 根据请求进行昂贵的计算。
    • 在每个模板中都需要结果。
    • 在每个模板中多次使用结果。

    除非满足所有这些条件,否则将函数传递给模板更符合 Jinja2 的设计。

默认配置被有意地保持为最低。如果一个模板被请求渲染(例如,当使用 render()),Jinja2 后端会在上下文中添加 requestcsrf_inputcsrf_token。除此之外,此后端不会创建 Django 风格的环境。 它不知道 Django 过滤器和标签。 为了使用 Django 特有的 API,你必须将它们配置到环境中。

例如,您可以使用以下内容创建 myproject/jinja2.py

from django.templatetags.static import static
from django.urls import reverse

from jinja2 import Environment


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

并将 'environment' 选项设置为 'myproject.jinja2.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 测试相对应的功能。