Django 模板语言:对于 Python 开发者

这篇文档从技术角度解释了 Django 模板系统——它是如何工作的以及如何扩展它。如果你想找语言语法的参考,请看 Django 模板语言

它的前提是对模板、上下文、变量、标签和渲染的理解。如果你不熟悉这些概念,可以从 Django 模板语言介绍 开始。

概况

在 Python 中使用模板系统是一个三步走的过程:

  1. 你设置了一个 Engine
  2. 你把模板代码编译成一个 Template
  3. 你用一个 Context 来渲染模板。

Django 项目一般依靠 高级、后端不可知的 API 来完成每一个步骤,而不是模板系统的低级 API。

  1. 对于 TEMPLATES 设置中的每一个 DjangoTemplates 后端,Django 都会实例化一个 EngineDjangoTemplates 封装 Engine 并将其适配到通用的模板后端 API 中。
  2. django.template.loader 模块提供了 get_template() 等函数来加载模板。它们返回一个 django.template.backends.django.Template,它封装了实际的 django.template
  3. 上一步得到的 Template 有一个 render() 方法,它将一个上下文和可能的请求汇集到一个 Context 中,并委托底层的 Template 进行渲染。

设置引擎

如果你使用的是 DjangoTemplates 后端,这可能不是你要找的文档。下面描述的 Engine 类的实例可以通过该后端的 engine 属性来访问,下面提到的任何属性默认值都会被 DjangoTemplates 传递的内容所覆盖。

class Engine(dirs=None, app_dirs=False, context_processors=None, debug=False, loaders=None, string_if_invalid='', file_charset='utf-8', libraries=None, builtins=None, autoescape=True)

当实例化一个 Engine 时,所有的参数都必须作为关键字参数传递。

  • dirs 是引擎应该寻找模板源文件的目录列表。它用于配置 filesystem.Loader

    默认为空列表。

  • app_dirs 只影响 loaders 的默认值。见下文。

    默认为 False

  • autoescape 控制是否启用 HTML 自动转码。

    默认为 True

    警告

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

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

    默认为空列表。

    查看 RequestContext 获取更多信息。

  • debug 是一个开启/关闭模板调试模式的布尔值。如果它为 True,模板引擎将存储额外的调试信息,这些信息可用于显示模板渲染过程中出现的任何异常的详细报告。

    默认为 False

  • loaders 是一个模板加载器类的列表,以字符串形式指定。每个 Loader 类都知道如何从特定来源导入模板。可以选择使用元组来代替字符串。元组中的第一项应该是 Loader 类名,随后的项目在初始化时传递给 Loader

    它默认为包含以下内容的列表:

    • 'django.template.loaders.filesystem.Loader'
    • 'django.template.loaders.app_directories.Loader' 如果且仅当 app_dirsTrue 时。

    These loaders are then wrapped in django.template.loaders.cached.Loader.

    Changed in Django 4.1:

    In older versions, the cached template loader was only enabled by default when DEBUG was False.

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

  • string_if_invalid 是模板系统对无效变量(如拼写错误)应使用的字符串输出。

    默认为空字符串。

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

  • file_charset 是用来读取磁盘上模板文件的字符集。

    默认为 'utf-8'

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

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

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

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

    Engine(
        builtins=["myapp.builtins"],
    )
    

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

static Engine.get_default()

从第一个配置的 DjangoTemplates 引擎中返回底层 Engine。如果没有配置引擎,则引发 ImproperlyConfigured

这是为保存依赖于全局可用、隐式配置引擎的 API 所必需的。任何其他用途都是不鼓励的。

Engine.from_string(template_code)

编译给定的模板代码并返回一个 Template 对象。

Engine.get_template(template_name)

加载给定名称的模板,编译后返回一个 Template 对象。

Engine.select_template(template_name_list)

就像 get_template() 一样,只不过它接收一个名称列表,并返回找到的第一个模板。

加载模板

推荐的创建 Template 的方法是调用 Engine 的工厂方法: get_template()select_template()from_string()

在 Django 项目中,如果 TEMPLATES 设置定义了一个 DjangoTemplates 引擎,那么可以直接实例化一个 Template。如果定义了多个 DjangoTemplates 引擎,则使用第一个引擎。

class Template

这个类位于 django.template.Template。构造函数需要一个参数——原始模板代码:

from django.template import Template

template = Template("My name is {{ my_name }}.")

幕后

系统只在创建 Template 对象时解析一次原始模板代码。从那时起,为了提高性能,它将以树结构的形式存储在内部。

即使是解析本身也是相当快的。大部分的解析工作都是通过调用一个简短的正则表达式来完成的。

渲染上下文

一旦你有一个编译过的 Template 对象,你就可以用它来渲染一个上下文。你可以重复使用同一个模板,在不同的上下文中多次渲染它。

class Context(dict_=None)

django.template.Context 的构造函数需要一个可选的参数——一个将变量名映射到变量值的字典。

详情请看下面的 使用 Context 对象

Template.render(context)

Call the Template object's render() method with a Context to "fill" the template:

>>> from django.template import Context, Template
>>> template = Template("My name is {{ my_name }}.")

>>> context = Context({"my_name": "Adrian"})
>>> template.render(context)
"My name is Adrian."

>>> context = Context({"my_name": "Dolores"})
>>> template.render(context)
"My name is Dolores."

变量和查找

变量名称必须由任何字母(A-Z)、任何数字(0-9)、下划线(但不得以下划线开头)或点组成。

点在模板渲染中具有特殊的意义。变量名中的点表示 查找。具体来说,当模板系统遇到变量名中的点时,它将按照以下顺序尝试进行查找:

  • 词典查找。例如:foo["bar"]
  • 属性查找。例如:foo.bar
  • 列表索引查找。例如:foo[bar]

请注意,像 {{ foo.bar }} 这样的模板表达式中的“bar”将被解释为一个字面字符串,而不是使用变量“bar”的值,如果模板上下文中存在的话。

The template system uses the first lookup type that works. It's short-circuit logic. Here are a few examples:

>>> from django.template import Context, Template
>>> t = Template("My name is {{ person.first_name }}.")
>>> d = {"person": {"first_name": "Joe", "last_name": "Johnson"}}
>>> t.render(Context(d))
"My name is Joe."

>>> class PersonClass:
...     pass
...
>>> p = PersonClass()
>>> p.first_name = "Ron"
>>> p.last_name = "Nasty"
>>> t.render(Context({"person": p}))
"My name is Ron."

>>> t = Template("The first stooge in the list is {{ stooges.0 }}.")
>>> c = Context({"stooges": ["Larry", "Curly", "Moe"]})
>>> t.render(c)
"The first stooge in the list is Larry."

If any part of the variable is callable, the template system will try calling it. Example:

>>> class PersonClass2:
...     def name(self):
...         return "Samantha"
...
>>> t = Template("My name is {{ person.name }}.")
>>> t.render(Context({"person": PersonClass2}))
"My name is Samantha."

可调用对象的变量比只需要直接查找的变量要复杂一些。下面是一些需要注意的事项:

  • If the variable raises an exception when called, the exception will be propagated, unless the exception has an attribute silent_variable_failure whose value is True. If the exception does have a silent_variable_failure attribute whose value is True, the variable will render as the value of the engine's string_if_invalid configuration option (an empty string, by default). Example:

    >>> t = Template("My name is {{ person.first_name }}.")
    >>> class PersonClass3:
    ...     def first_name(self):
    ...         raise AssertionError("foo")
    ...
    >>> p = PersonClass3()
    >>> t.render(Context({"person": p}))
    Traceback (most recent call last):
    ...
    AssertionError: foo
    
    >>> class SilentAssertionError(Exception):
    ...     silent_variable_failure = True
    ...
    >>> class PersonClass4:
    ...     def first_name(self):
    ...         raise SilentAssertionError
    ...
    >>> p = PersonClass4()
    >>> t.render(Context({"person": p}))
    "My name is ."
    

    请注意 django.core.exceptions.ObjectDoesNotExist 是所有 Django 数据库 API DoesNotExist 异常的基类,有 silent_variable_failure = True。所以如果你使用 Django 模板与 Django 模型对象,任何 DoesNotExist 异常都会静默失败。

  • 一个变量只有在没有所需参数的情况下才可以被调用,否则,系统将返回引擎的 string_if_invalid 选项的值。否则,系统将返回引擎的 string_if_invalid 选项的值。

  • 在调用一些变量的时候可能会有副作用,如果让模板系统访问这些变量,要么是傻瓜,要么是安全漏洞。

    A good example is the delete() method on each Django model object. The template system shouldn't be allowed to do something like this:

    I will now delete this valuable data. {{ data.delete }}
    

    为了防止这种情况发生,在可调用的变量上设置一个 alters_data 属性。如果设置了 alters_data=True,模板系统将不会调用变量,而是无条件地用 string_if_invalid 替换变量。 动态生成的 delete()save() 方法会自动获取 alters_data=True。例如:

    def sensitive_function(self):
        self.database_record.delete()
    
    
    sensitive_function.alters_data = True
    
  • 偶尔你可能会因为其他原因想关闭这个功能,并告诉模板系统无论如何都不调用一个变量。 要做到这一点,请在可调用变量上设置一个 do_not_call_in_templates 属性,其值为 True。 这样,模板系统就会把你的变量当作不可调用的变量(例如,允许你访问可调用变量的属性)。

如何处理无效变量

一般来说,如果一个变量不存在,模板系统会插入引擎的 string_if_invalid 配置选项的值,默认设置为 '' (空字符串)。

只有当 string_if_invalid 被设置为 '' (空字符串)时,才会对无效变量应用过滤器。如果 string_if_invalid 被设置为任何其他值,变量过滤器将被忽略。

对于 ifforregroup 模板标签,这种行为略有不同。如果向这些模板标签之一提供了一个无效的变量,该变量将被解释为 None。过滤器总是应用于这些模板标签中的无效变量。

如果 string_if_invalid 包含 '%s',格式标记将被替换为无效变量的名称。

仅供调试使用!

虽然 string_if_invalid 是一个有用的调试工具,但把它作为“开发默认值”是一个坏主意。

很多模板,包括一些 Django 的模板,在遇到不存在的变量时,都会依靠模板系统的静默。如果你给 '' string_if_invalid 以外的值,你会在这些模板和网站上遇到渲染问题。

一般来说,string_if_invalid 只有在调试某个特定的模板问题时才应该启用,调试完成后再清除。

内置变量

每个上下文都包含 TrueFalseNone。正如你所期望的那样,这些变量解析为相应的 Python 对象。

字符串的限制

Django 的模板语言没有办法转义用于自己语法的字符。例如,如果你需要输出像 {%%} 这样的字符序列,就需要使用 templatetag 标签。

A similar issue exists if you want to include these sequences in template filter or tag arguments. For example, when parsing a block tag, Django's template parser looks for the first occurrence of %} after a {%. This prevents the use of "%}" as a string literal. For example, a TemplateSyntaxError will be raised for the following expressions:

{% include "template.html" tvar="Some string literal with %} in it." %}

{% with tvar="Some string literal with %} in it." %}{% endwith %}

The same issue can be triggered by using a reserved sequence in filter arguments:

{{ some.variable|default:"}}" }}

如果你需要使用这些序列的字符串,请将它们存储在模板变量中,或者使用自定义模板标签或过滤器来解决这个限制。

使用 Context 对象

Most of the time, you'll instantiate Context objects by passing in a fully-populated dictionary to Context(). But you can add and delete items from a Context object once it's been instantiated, too, using standard dictionary syntax:

>>> from django.template import Context
>>> c = Context({"foo": "bar"})
>>> c["foo"]
'bar'
>>> del c["foo"]
>>> c["foo"]
Traceback (most recent call last):
...
KeyError: 'foo'
>>> c["newvariable"] = "hello"
>>> c["newvariable"]
'hello'
Context.get(key, otherwise=None)

如果 key 在上下文中,返回 key 的值,否则返回 otherwise

Context.setdefault(key, default=None)

如果 key 在上下文中,则返回其值。否则用 default 值插入 key 并返回 default

Context.pop()
Context.push()
exception ContextPopException

A Context object is a stack. That is, you can push() and pop() it. If you pop() too much, it'll raise django.template.ContextPopException:

>>> c = Context()
>>> c["foo"] = "first level"
>>> c.push()
{}
>>> c["foo"] = "second level"
>>> c["foo"]
'second level'
>>> c.pop()
{'foo': 'second level'}
>>> c["foo"]
'first level'
>>> c["foo"] = "overwritten"
>>> c["foo"]
'overwritten'
>>> c.pop()
Traceback (most recent call last):
...
ContextPopException

你也可以使用 push() 作为上下文管理器,以确保匹配的 pop() 被调用。

>>> c = Context()
>>> c['foo'] = 'first level'
>>> with c.push():
...     c['foo'] = 'second level'
...     c['foo']
'second level'
>>> c['foo']
'first level'

传递给 push() 的所有参数都将传递给 dict 构造函数,用于建立新的上下文层次。

>>> c = Context()
>>> c['foo'] = 'first level'
>>> with c.push(foo='second level'):
...     c['foo']
'second level'
>>> c['foo']
'first level'
Context.update(other_dict)

除了 push()pop() 之外,Context 对象还定义了一个 update() 方法。它的工作原理与 push() 类似,但它接受一个字典作为参数,并将该字典推到栈上,而不是空的。

>>> c = Context()
>>> c['foo'] = 'first level'
>>> c.update({'foo': 'updated'})
{'foo': 'updated'}
>>> c['foo']
'updated'
>>> c.pop()
{'foo': 'updated'}
>>> c['foo']
'first level'

push() 一样,你可以使用 update() 作为上下文管理器,以确保调用匹配的 pop()

>>> c = Context()
>>> c['foo'] = 'first level'
>>> with c.update({'foo': 'second level'}):
...     c['foo']
'second level'
>>> c['foo']
'first level'

一些自定义模板标签 中,使用 Context 作为栈是很方便的。

Context.flatten()

使用 flatten() 方法,你可以得到整个 Context 堆栈作为一个字典,包括内置的变量。

>>> c = Context()
>>> c['foo'] = 'first level'
>>> c.update({'bar': 'second level'})
{'bar': 'second level'}
>>> c.flatten()
{'True': True, 'None': None, 'foo': 'first level', 'False': False, 'bar': 'second level'}

内部还使用 flatten() 方法使 Context 对象具有可比性。

>>> c1 = Context()
>>> c1['foo'] = 'first level'
>>> c1['bar'] = 'second level'
>>> c2 = Context()
>>> c2.update({'bar': 'second level', 'foo': 'first level'})
{'foo': 'first level', 'bar': 'second level'}
>>> c1 == c2
True

flatten() 的结果在单元测试中可以用来比较 Contextdict

class ContextTest(unittest.TestCase):
    def test_against_dictionary(self):
        c1 = Context()
        c1["update"] = "value"
        self.assertEqual(
            c1.flatten(),
            {
                "True": True,
                "None": None,
                "False": False,
                "update": "value",
            },
        )

使用 RequestContext

class RequestContext(request, dict_=None, processors=None)

Django 有一个特殊的 Context 类,django.template.RequestContext,它的作用与普通的 django.template.Context 略有不同。第一个不同是它以一个 HttpRequest 作为它的第一个参数。例如:

c = RequestContext(
    request,
    {
        "foo": "bar",
    },
)

第二个区别是,它根据引擎的 context_processors 配置选项,自动给上下文填充一些变量。

context_processors 选项是一个可调用的列表——称为 上下文处理器——它将一个请求对象作为参数,并返回一个要合并到上下文中的项目字典。在默认生成的配置文件中,默认模板引擎包含以下上下文处理器:

[
    "django.template.context_processors.debug",
    "django.template.context_processors.request",
    "django.contrib.auth.context_processors.auth",
    "django.contrib.messages.context_processors.messages",
]

除此以外,RequestContext 总是启用 'django.template.context_processors.csrf'。 这是管理和其他 contrib 应用所需要的安全相关的上下文处理器,为了防止意外的错误配置,特意将其硬编码进去,不能在 context_processors 选项中关闭。

每个处理器都是按顺序应用的。这意味着,如果一个处理器向上下文添加了一个变量,而第二个处理器添加了一个同名的变量,第二个处理器将覆盖第一个处理器。下面解释默认的处理器。

当应用上下文处理器时

上下文处理器是应用在上下文数据之上的。这意味着上下文处理器可能会覆盖你提供给 ContextRequestContext 的变量,所以要注意避免变量名与上下文处理器提供的变量名重叠。

如果你想让上下文数据优先于上下文处理器,请使用以下模式:

from django.template import RequestContext

request_context = RequestContext(request)
request_context.push({"my_name": "Adrian"})

Django 这样做是为了让上下文数据覆盖 API 中的上下文处理器,如 render()TemplateResponse

此外,你还可以使用可选的第三个位置参数 processors,给 RequestContext 一个额外的处理器列表。在这个例子中, RequestContext 实例得到一个 ip_address 变量:

from django.http import HttpResponse
from django.template import RequestContext, Template


def ip_address_processor(request):
    return {"ip_address": request.META["REMOTE_ADDR"]}


def client_ip_view(request):
    template = Template("{{ title }}: {{ ip_address }}")
    context = RequestContext(
        request,
        {
            "title": "Your IP Address",
        },
        [ip_address_processor],
    )
    return HttpResponse(template.render(context))

内置模板上下文处理器

下面是每个内置处理器的作用:

django.contrib.auth.context_processors.auth

auth(request)

如果启用了这个处理器,每一个 RequestContext 都会包含这些变量:

  • user —— 代表当前登录用户的 auth.User 实例(如果客户端没有登录,则为 AnonymousUser 实例)。
  • perms —— django.contrib.uth.context_processors.PermWrapper 的实例,表示当前登录用户拥有的权限。

django.template.context_processors.debug

debug(request)

如果启用了这个处理器,每一个 RequestContext 都会包含这两个变量——但前提是你的 DEBUG 设置为 True,并且请求的 IP 地址(request.META['REMOTE_ADDR'])在 INTERNAL_IPS 配置中:

  • debug —— True。你可以在模板中使用它来测试你是否处于 DEBUG 模式。
  • sql_queries —— {'sql': ..., 'time': ...} 字典的列表,表示在请求过程中迄今为止发生的每一个 SQL 查询,以及花费的时间。这个列表是按照数据库别名,然后按照查询的顺序排列的。它是在访问时惰性生成的。

django.template.context_processors.i18n

i18n(request)

如果启用了这个处理器,每一个 RequestContext 都会包含这些变量:

  • LANGUAGES —— LANGUAGES 配置值。
  • LANGUAGE_BIDI —— True 如果当前语言是从右到左的语言,如希伯来语、阿拉伯语。False 如果是从左到右的语言,如英语、法语、德语。
  • LANGUAGE_CODE —— request.LANGUAGE_CODE,如果存在的话。否则,使用 LANGUAGE_CODE 配置的值。

请参阅 i18n 模板标签,了解产生相同值的模板标签。

django.template.context_processors.media

如果启用了这个处理器,每一个 RequestContext 都会包含一个变量 MEDIA_URL,提供 MEDIA_URL 配置的值。

django.template.context_processors.static

static(request)

如果启用了这个处理器,每个 RequestContext 都会包含一个变量 STATIC_URL,提供 STATIC_URL 配置的值。

django.template.context_processors.csrf

该处理器添加了 csrf_token 模板标签所需的令牌,以防止 跨站点伪造请求

django.template.context_processors.request

如果启用了这个处理器,每个 RequestContext 都会包含一个变量 request,就是当前的 HttpRequest

django.template.context_processors.tz

tz(request)

如果启用了这个处理器,每个 RequestContext 将包含一个变量 TIME_ZONE,提供当前活动时区的名称。

django.contrib.messages.context_processors.messages

如果启用了这个处理器,每一个 RequestContext 都会包含这两个变量:

  • messages —— 通过 消息框架 设置的信息列表(字符串)。
  • DEFAULT_MESSAGE_LEVELS —— 信息级别名称与 其数值 的映射。

编写你自己的上下文处理器

A context processor has a simple interface: It's a Python function that takes one argument, an HttpRequest object, and returns a dictionary that gets added to the template context.

For example, to add the DEFAULT_FROM_EMAIL setting to every context:

from django.conf import settings


def from_email(request):
    return {
        "DEFAULT_FROM_EMAIL": settings.DEFAULT_FROM_EMAIL,
    }

自定义上下文处理器可以存在于你的代码库中的任何地方。Django 只关心你的自定义上下文处理器是否被你的 TEMPLATES 配置中的 'context_processors' 选项所指向,如果你直接使用的话,则是 Enginecontext_processors 参数。

加载模板

一般来说,你会把模板存储在文件系统的文件中,而不是自己使用低级的 template API。将模板保存在指定的 template 目录 中。

Django 会在很多地方搜索模板目录,这取决于你的模板加载设置(见下面的“加载器类型”),但最基本的指定模板目录的方法是使用 DIRS 选项。

DIRS 选项

通过使用配置文件中的 TEMPLATES 配置中的 DIRS 选项来告诉 Django 你的模板目录是什么,或者使用 Engine 中的 dirs 参数。这应该被设置为一个字符串列表,其中包含你的模板目录的完整路径:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [
            "/home/html/templates/lawrence.com",
            "/home/html/templates/default",
        ],
    },
]

Your templates can go anywhere you want, as long as the directories and templates are readable by the web server. They can have any extension you want, such as .html or .txt, or they can have no extension at all.

请注意,这些路径应该使用 Unix 风格的斜线,即使在 Windows 上也是如此。

加载器类型

默认情况下,Django 使用的是基于文件系统的模板加载器,但 Django 自带了一些其他的模板加载器,它们知道如何从其他来源加载模板。

其他一些加载器默认是禁用的,但是你可以通过在 TEMPLATES 配置中为你的 DjangoTemplates 后端添加一个 'loaders' 选项来激活它们,或者向 Engine 传递一个 loaders 参数。loaders 应该是一个字符串或元组的列表,每个元组代表一个模板加载器类。下面是 Django 自带的模板加载器。

django.template.loaders.filesystem.Loader

class filesystem.Loader

根据 DIRS 从文件系统加载模板。

这个加载器默认是启用的。然而,它不会找到任何模板,直到你将 DIRS 设置为非空列表:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
    }
]

你也可以覆盖 'DIRS',为特定的文件系统加载器指定特定的目录:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "OPTIONS": {
            "loaders": [
                (
                    "django.template.loaders.filesystem.Loader",
                    [BASE_DIR / "templates"],
                ),
            ],
        },
    }
]

django.template.loaders.app_directories.Loader

class app_directories.Loader

从文件系统中加载 Django 应用的模板。对于 INSTALLED_APPS 中的每个应用,加载器会寻找一个 templates 子目录。如果该目录存在,Django 就会在其中寻找模板。

这意味着你可以将模板与你的各个应用一起存储。这也有助于分发带有默认模板的 Django 应用。

例如,对于这个配置:

INSTALLED_APPS = ["myproject.polls", "myproject.music"]

...然后 get_template('foo.html') 将在这些目录中按这个顺序查找 foo.html

  • /path/to/myproject/polls/templates/
  • /path/to/myproject/music/templates/

...会用它最先找到的那个:

INSTALLED_APPS 的顺序很重要!例如,如果你想自定义 Django 管理,你可能会选择覆盖标准 django.contrib.adminadmin/base_site.html 模板,,用你自己 myproject.polls 中的 admin/base_site.html。然后你必须确保你的 myproject.pollsINSTALLED_APPS 中在 django.contrib.admin 之前,否则 django.contrib.admin 的会先被加载,你的会被忽略。

请注意,加载器在第一次运行时进行了优化:它缓存了一个列表,显示哪些 INSTALLED_APPS 包有一个 templates 子目录。

你可以通过设置 APP_DIRSTrue 来启用该加载器:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "APP_DIRS": True,
    }
]

django.template.loaders.cached.Loader

class cached.Loader

While the Django template system is quite fast, if it needs to read and compile your templates every time they're rendered, the overhead from that can add up.

你可以用其他加载器的列表来配置缓存的模板加载器,它应该对这些加载器进行封装。当第一次遇到未知模板时,封装的加载器被用来定位它们。然后,缓存加载器将编译后的 Template 存储在内存中。缓存的 Template 实例会被返回,供后续加载同一模板的请求使用。

This loader is automatically enabled if OPTIONS['loaders'] isn't specified.

You can manually specify template caching with some custom template loaders using settings like this:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "OPTIONS": {
            "loaders": [
                (
                    "django.template.loaders.cached.Loader",
                    [
                        "django.template.loaders.filesystem.Loader",
                        "django.template.loaders.app_directories.Loader",
                        "path.to.custom.Loader",
                    ],
                ),
            ],
        },
    }
]

备注

所有内置的 Django 模板标签都可以安全地使用缓存加载器,但如果你使用的是来自第三方包的自定义模板标签,或者你自己编写的模板标签,你应该确保每个标签的“节点”实现是线程安全的。更多信息,请参见 模板标签线程安全注意事项

Changed in Django 4.1:

The cached template loader was enabled whenever OPTIONS['loaders'] is not specified. Previously it was only enabled when DEBUG was False.

django.template.loaders.locmem.Loader

class locmem.Loader

从 Python 字典中加载模板。这对测试很有用。

该加载器将模板字典作为其第一个参数:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "OPTIONS": {
            "loaders": [
                (
                    "django.template.loaders.locmem.Loader",
                    {
                        "index.html": "content here",
                    },
                ),
            ],
        },
    }
]

该加载器默认为禁用。

Django 根据 'loaders' 选项的顺序使用模板加载器。它使用每个加载器,直到一个加载器找到匹配的模板。

自定义加载器

可以使用自定义模板加载器从其他来源加载模板。自定义 Loader 类应该继承 django.template.loaders.base.Loader 并定义 get_contents()get_template_sources() 方法。

加载器方法

class Loader

从给定的源,如文件系统或数据库中加载模板。

get_template_sources(template_name)

一个接受 template_name 的方法,并为每个可能的来源产生 Origin 实例。

例如,文件系统加载器可以接收 'index.html' 作为 template_name 参数。 这个方法将产生 index.html 的完整路径的起源,因为它出现在加载器查看的每个模板目录中。

该方法不需要验证模板是否存在于给定的路径中,但它应该确保路径是有效的。例如,文件系统加载器会确保路径位于一个有效的模板目录下。

get_contents(origin)

返回给定 Origin 实例的模板的内容。

这是一个文件系统加载器从文件系统读取内容的地方,或者一个数据库加载器从数据库读取内容的地方。如果一个匹配的模板不存在,这应该会引发一个 TemplateDoesNotExist 错误。

get_template(template_name, skip=None)

通过循环浏览 get_template_sources() 和调用 get_contents() 的结果,为给定的 template_name 返回一个 Template 对象。这将返回第一个匹配的模板。如果没有找到模板,则会引发 TemplateDoesNotExist

可选的 skip 参数是扩展模板时要忽略的起源列表。这允许模板扩展同名的其他模板。它也用于避免递归错误。

一般来说,定义 get_template_sources()get_contents() 为自定义模板加载器就可以了。get_template() 通常不需要重写。

构建你自己的

例如,请阅读 Django 内置加载器的源代码

模板起源

模板有一个 origin,包含的属性取决于它们的来源。

class Origin(name, template_name=None, loader=None)
name

模板加载器返回的模板路径。对于从文件系统读取的加载器,这是模板的完整路径。

如果模板是直接实例化的,而不是通过模板加载器,这是一个字符串值 <unknown_source>

template_name

传入模板加载器的模板的相对路径。

如果模板是直接实例化的,而不是通过模板加载器,这就是 None

loader

构建这个 Origin 的模板加载器实例。

如果模板是直接实例化的,而不是通过模板加载器,这就是 None

django.template.loaders.cached.Loader 要求所有封装的加载器都要设置这个属性,通常是通过实例化 loader=selfOrigin