Django 模板语言:对于 Python 开发者
Django 模板语言:对于 Python 开发者
这篇文档从技术角度解释了 Django 模板系统——它是如何工作的以及如何扩展它。如果你想找语言语法的参考,请看 Django 模板语言。
它的前提是对模板、上下文、变量、标签和渲染的理解。如果你不熟悉这些概念,可以从 Django 模板语言介绍 开始。
概况
在 Python 中使用模板系统是一个三步走的过程:
- 你设置了一个 Engine。
- 你把模板代码编译成一个 Template。
- 你用一个 Context 来渲染模板。
Django 项目一般依靠 高级、后端不可知的 API 来完成每一个步骤,而不是模板系统的低级 API。
- 对于 TEMPLATES 设置中的每一个 DjangoTemplates 后端,Django 都会实例化一个 Engine。 DjangoTemplates 封装 Engine 并将其适配到通用的模板后端 API 中。
- django.template.loader 模块提供了 get_template() 等函数来加载模板。它们返回一个
django.template.backends.django.Template,它封装了实际的 django.template。 - 上一步得到的
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_dirs` 为 `True` 时。
These loaders are then wrapped in django.template.loaders.cached.Loader.
查看 加载器类型 获取详细信息。
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_failurewhose value isTrue. If the exception does have asilent_variable_failureattribute whose value isTrue, the variable will render as the value of the engine’sstring_if_invalidconfiguration 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` 被设置为任何其他值,变量过滤器将被忽略。
对于 `if`、`for` 和 `regroup` 模板标签,这种行为略有不同。如果向这些模板标签之一提供了一个无效的变量,该变量将被解释为 `None`。过滤器总是应用于这些模板标签中的无效变量。
如果 `string_if_invalid` 包含 `'%s'`,格式标记将被替换为无效变量的名称。
仅供调试使用!
虽然 `string_if_invalid` 是一个有用的调试工具,但把它作为“开发默认值”是一个坏主意。
很多模板,包括一些 Django 的模板,在遇到不存在的变量时,都会依靠模板系统的静默。如果你给 `''` `string_if_invalid` 以外的值,你会在这些模板和网站上遇到渲染问题。
一般来说,`string_if_invalid` 只有在调试某个特定的模板问题时才应该启用,调试完成后再清除。
### 内置变量
每个上下文都包含 `True`、`False` 和 `None`。正如你所期望的那样,这些变量解析为相应的 Python 对象。
### 字符串的限制
Django 的模板语言没有办法转义用于自己语法的字符。例如,如果你需要输出像 `{%` 和 `%}` 这样的字符序列,就需要使用 [templatetag](/read/django-5.0-zh/d223.md#std-templatetag-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'
在 [一些自定义模板标签](/read/django-5.0-zh/d082.md#howto-writing-custom-template-tags) 中,使用 `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()` 的结果在单元测试中可以用来比较 `Context` 和 `dict`:
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](</read/django-5.0-zh/894f850ad4eb1a49.md#django.http.HttpRequest> "django.http.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` 选项中关闭。
每个处理器都是按顺序应用的。这意味着,如果一个处理器向上下文添加了一个变量,而第二个处理器添加了一个同名的变量,第二个处理器将覆盖第一个处理器。下面解释默认的处理器。
当应用上下文处理器时
上下文处理器是应用在上下文数据之上的。这意味着上下文处理器可能会覆盖你提供给 Context 或 RequestContext 的变量,所以要注意避免变量名与上下文处理器提供的变量名重叠。
如果你想让上下文数据优先于上下文处理器,请使用以下模式:
from django.template import RequestContext
request_context = RequestContext(request) request_context.push({"my_name": "Adrian"})
Django 这样做是为了让上下文数据覆盖 API 中的上下文处理器,如 [render()](</read/django-5.0-zh/04b8c26c6288f26f.md#django.shortcuts.render> "django.shortcuts.render") 和 [TemplateResponse](</read/django-5.0-zh/c4216c911f61e822.md#django.template.response.TemplateResponse> "django.template.response.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](/read/django-5.0-zh/d219.md#std-setting-DEBUG) 设置为 `True`,并且请求的 IP 地址(`request.META['REMOTE_ADDR']`)在 [INTERNAL_IPS](/read/django-5.0-zh/d219.md#std-setting-INTERNAL_IPS) 配置中:
* `debug` —— `True`。你可以在模板中使用它来测试你是否处于 [DEBUG](/read/django-5.0-zh/d219.md#std-setting-DEBUG) 模式。
* `sql_queries` —— `{'sql': ..., 'time': ...}` 字典的列表,表示在请求过程中迄今为止发生的每一个 SQL 查询,以及花费的时间。这个列表是按照数据库别名,然后按照查询的顺序排列的。它是在访问时惰性生成的。
#### `django.template.context_processors.i18n`
`i18n`(_request_)
如果启用了这个处理器,每一个 `RequestContext` 都会包含这些变量:
* `LANGUAGES` —— [LANGUAGES](/read/django-5.0-zh/d219.md#std-setting-LANGUAGES) 配置值。
* `LANGUAGE_BIDI` —— `True` 如果当前语言是从右到左的语言,如希伯来语、阿拉伯语。`False` 如果是从左到右的语言,如英语、法语、德语。
* `LANGUAGE_CODE` —— `request.LANGUAGE_CODE`,如果存在的话。否则,使用 [LANGUAGE_CODE](/read/django-5.0-zh/d219.md#std-setting-LANGUAGE_CODE) 配置的值。
请参阅 [i18n 模板标签](<https://docs.djangoproject.com/zh-hans/5.0/topics/i18n/translation/#i18n-template-tags>),了解产生相同值的模板标签。
#### `django.template.context_processors.media`
如果启用了这个处理器,每一个 `RequestContext` 都会包含一个变量 `MEDIA_URL`,提供 [MEDIA_URL](/read/django-5.0-zh/d219.md#std-setting-MEDIA_URL) 配置的值。
#### `django.template.context_processors.static`
`static`(_request_)
如果启用了这个处理器,每个 `RequestContext` 都会包含一个变量 `STATIC_URL`,提供 [STATIC_URL](/read/django-5.0-zh/d219.md#std-setting-STATIC_URL) 配置的值。
#### `django.template.context_processors.csrf`
该处理器添加了 [csrf_token](/read/django-5.0-zh/d223.md#std-templatetag-csrf_token) 模板标签所需的令牌,以防止 [跨站点伪造请求](/read/django-5.0-zh/d183.md)。
#### `django.template.context_processors.request`
如果启用了这个处理器,每个 `RequestContext` 都会包含一个变量 `request`,就是当前的 [HttpRequest](</read/django-5.0-zh/894f850ad4eb1a49.md#django.http.HttpRequest> "django.http.HttpRequest")。
#### `django.template.context_processors.tz`
`tz`(_request_)
如果启用了这个处理器,每个 `RequestContext` 将包含一个变量 `TIME_ZONE`,提供当前活动时区的名称。
#### `django.contrib.messages.context_processors.messages`
如果启用了这个处理器,每一个 `RequestContext` 都会包含这两个变量:
* `messages` —— 通过 [消息框架](/read/django-5.0-zh/d165.md) 设置的信息列表(字符串)。
* `DEFAULT_MESSAGE_LEVELS` —— 信息级别名称与 [其数值](/read/django-5.0-zh/d165.md#message-level-constants) 的映射。
### 编写你自己的上下文处理器
A context processor has a simple interface: It’s a Python function that takes one argument, an [HttpRequest](</read/django-5.0-zh/894f850ad4eb1a49.md#django.http.HttpRequest> "django.http.HttpRequest") object, and returns a dictionary that gets added to the template context.
For example, to add the [DEFAULT_FROM_EMAIL](/read/django-5.0-zh/d219.md#std-setting-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](/read/django-5.0-zh/d219.md#std-setting-TEMPLATES) 配置中的 `'context_processors'` 选项所指向,如果你直接使用的话,则是 Engine 的 `context_processors` 参数。
## 加载模板
一般来说,你会把模板存储在文件系统的文件中,而不是自己使用低级的 [template](</read/django-5.0-zh/deb4f3536f2d698f.md#module-django.template> "django.template: Django's template system") API。将模板保存在指定的 **template 目录** 中。
Django 会在很多地方搜索模板目录,这取决于你的模板加载设置(见下面的“加载器类型”),但最基本的指定模板目录的方法是使用 `DIRS` 选项。
### [DIRS](/read/django-5.0-zh/d219.md#std-setting-TEMPLATES-DIRS) 选项
通过使用配置文件中的 [TEMPLATES](/read/django-5.0-zh/d219.md#std-setting-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](/read/django-5.0-zh/d219.md#std-setting-TEMPLATES) 配置中为你的 `DjangoTemplates` 后端添加一个 `'loaders'` 选项来激活它们,或者向 Engine 传递一个 `loaders` 参数。`loaders` 应该是一个字符串或元组的列表,每个元组代表一个模板加载器类。下面是 Django 自带的模板加载器。
`django.template.loaders.filesystem.Loader`
_class_ `filesystem.Loader`
根据 [DIRS](/read/django-5.0-zh/d219.md#std-setting-TEMPLATES-DIRS) 从文件系统加载模板。
这个加载器默认是启用的。然而,它不会找到任何模板,直到你将 [DIRS](/read/django-5.0-zh/d219.md#std-setting-TEMPLATES-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](/read/django-5.0-zh/d219.md#std-setting-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](/read/django-5.0-zh/d219.md#std-setting-INSTALLED_APPS) 的顺序很重要!例如,如果你想自定义 Django 管理,你可能会选择覆盖标准 `django.contrib.admin` 的 `admin/base_site.html` 模板,,用你自己 `myproject.polls` 中的 `admin/base_site.html`。然后你必须确保你的 `myproject.polls` 在 [INSTALLED_APPS](/read/django-5.0-zh/d219.md#std-setting-INSTALLED_APPS) 中在 `django.contrib.admin` _之前_ ,否则 `django.contrib.admin` 的会先被加载,你的会被忽略。
请注意,加载器在第一次运行时进行了优化:它缓存了一个列表,显示哪些 [INSTALLED_APPS](/read/django-5.0-zh/d219.md#std-setting-INSTALLED_APPS) 包有一个 `templates` 子目录。
你可以通过设置 [APP_DIRS](/read/django-5.0-zh/d219.md#std-setting-TEMPLATES-APP_DIRS) 为 `True` 来启用该加载器:
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’]](/read/django-5.0-zh/d219.md#std-setting-TEMPLATES-OPTIONS) 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 模板标签都可以安全地使用缓存加载器,但如果你使用的是来自第三方包的自定义模板标签,或者你自己编写的模板标签,你应该确保每个标签的“节点”实现是线程安全的。更多信息,请参见 [模板标签线程安全注意事项](/read/django-5.0-zh/d082.md#template-tag-thread-safety)。
`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=self 的 Origin。
