虽然大部分与Django模板语言的交互都是模板作者的角色,但您可能需要自定义和扩展模板引擎 - 要么做一些它还没有做的事情,要么用另一种方式让你的工作更轻松一些。
本章深入研究了Django模板系统的内涵。 它涵盖了如果您计划扩展系统或者只是对其工作原理感到好奇,您需要知道的内容。 它还涵盖了自动转义功能,在您继续使用Django时,您将毫无疑问地注意到一个安全措施。
模板语言回顾
首先,让我们快速回顾一下第3章中介绍的一些术语:
- 模板是使用Django模板语言标记的文本文档或普通的Python字符串。 模板可以包含模板标签和变量。
- 模板标签是模板中的一个符号,用于执行某些操作。 这个定义是故意模糊的。 例如,模板标签可以生成内容,用作控制结构(if语句或for循环),从数据库中获取内容或者允许访问其他模板标签。模板标签被{% and %}包围:
{% if is_logged_in %}
Thanks for logging in!
{% else %}
Please log in.
{% endif %}
变量是输出值的模板中的符号。可变标签由{{and}}包围:
My first name is {{ first_name }}. My last name is {{ last_name }}.
- 上下文是传递给模板的名称 - >值映射(类似于Python字典)。
- 模板通过用上下文中的值替换变量“holes”并执行所有模板标记来呈现上下文。
有关这些术语的基本知识的更多详细信息,请参阅第3章。本章其余部分讨论扩展模板引擎的方法。 首先,为了简单起见,我们快速浏览第3章中的一些内部部分。
RequestContext和上下文处理器
渲染模板时,需要一个上下文。 这可以是django.template.Context的一个实例,但是Django也带有一个子类django.template.RequestContext,它的行为稍有不同。
RequestContext默认将一堆变量添加到模板上下文中 - 例如HttpRequest对象或当前登录用户的信息。
render()快捷方式创建一个RequestContext,除非它明确地传递了一个不同的上下文实例。 例如,考虑这两个观点:
from django.template import loader, Context
def view_1(request):
# ...
t = loader.get_template('template1.html')
c = Context({
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR'],
'message': 'I am view 1.'
})
return t.render(c)
def view_2(request):
# ...
t = loader.get_template('template2.html')
c = Context({
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR'],
'message': 'I am the second view.'
})
return t.render(c)
(请注意,在这些示例中我故意不使用render()快捷方式 - 我们正在手动加载模板,构建上下文对象并呈现模板,我正在“拼出”所有步骤 明晰。)
每个视图将相同的三个变量--app,user和ip_address - 传递给它的模板。 如果我们可以删除冗余,不是很好吗? 创建RequestContext和上下文处理器来解决这个问题。 上下文处理器允许您指定一些变量,这些变量在每个上下文中自动设置,而不必在每个render()调用中指定变量。
问题在于,当您呈现模板时,您必须使用RequestContext而不是Context。 使用上下文处理器的最低级的方法是创建一些处理器并将它们传递给RequestContext。 上面的例子可以用上下文处理器来编写:
from django.template import loader, RequestContext
def custom_proc(request):
# A context processor that provides 'app', 'user' and 'ip_address'.
return {
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR']
}
def view_1(request):
# ...
t = loader.get_template('template1.html')
c = RequestContext(request,
{'message': 'I am view 1.'},
processors=[custom_proc])
return t.render(c)
def view_2(request):
# ...
t = loader.get_template('template2.html')
c = RequestContext(request,
{'message': 'I am the second view.'},
processors=[custom_proc])
return t.render(c)
我们来看看这个代码:
- 首先,我们定义一个函数custom_proc。这是一个上下文处理器 - 它需要一个HttpRequest对象并返回一个在模板上下文中使用的变量字典。就是这样。
- 我们已经改变了两个视图函数来使用RequestContext而不是Context。上下文的构建有两个不同之处。一,RequestContext需要第一个参数是一个HttpRequest对象 - 第一个被传递给view函数的请求(request)。二,RequestContext需要一个可选的处理器参数,这是一个上下文处理函数的列表或元组使用。在这里,我们传入了custom_proc,这是我们上面定义的自定义处理器。
- 每个视图不必在其上下文构造中包括app,user或ip_address,因为这些由custom_proc提供。
- 每个视图仍然具有引入可能需要的任何自定义模板变量的灵活性。在这个例子中,消息模板变量在每个视图中设置不同。
在第3章中,我介绍了render()快捷方式,它需要调用loader.get_template(),然后创建一个Context,然后在模板上调用render()方法。
为了演示上下文处理器的低级工作,上面的例子没有使用render()。 但是可能的 - 也是可取的 - 通过render()使用上下文处理器。 使用context_instance参数执行此操作,如下所示:
from django.shortcuts import render
from django.template import RequestContext
def custom_proc(request):
# A context processor that provides 'app', 'user' and 'ip_address'.
return {
'app': 'My app',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR']
}
def view_1(request):
# ...
return render(request, 'template1.html',
{'message': 'I am view 1.'},
context_instance=RequestContext(
request, processors=[custom_proc]
)
)
def view_2(request):
# ...
return render(request, 'template2.html',
{'message': 'I am the second view.'},
context_instance=RequestContext(
request, processors=[custom_proc]
)
)
在这里,我们已经将每个视图的模板渲染代码修剪成单个(包装)线。 这是一个改进,但是,为了评估这个代码的简洁性,我们不得不承认,我们现在几乎已经过剩了。 我们已经删除了冗余数据(我们的模板变量),代价是在代码中添加冗余(在处理器调用中)。
如果您必须始终键入处理器,则使用上下文处理器并不会节省您太多的打字时间。 出于这个原因,Django提供了对全局上下文处理器的支持。 context_processors设置(在您的settings.py中)指定应始终将哪个上下文处理器应用于RequestContext。 这消除了每次使用RequestContext时都需要指定处理器的情况。
默认情况下,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',
],
这个设置是一个使用与我们上面的custom_proc函数相同的接口的可调用列表 - 将请求对象作为参数的函数,并返回要合并到上下文中的项目字典。 请注意,context_processors中的值被指定为字符串,这意味着处理器需要位于Python路径的某处(所以您可以从设置中引用它们)。
每个处理器按顺序应用。 也就是说,如果一个处理器将一个变量添加到上下文中,并且第二个处理器添加一个具有相同名称的变量,则第二个处理器将覆盖第一个。 Django提供了许多简单的上下文处理器,包括默认启用的处理器:
AUTH
django.contrib.auth.context_processors.auth
如果此处理器已启用,则每个RequestContext都将包含这些变量:
- user - 代表当前登录用户的auth.User实例(或者AnonymousUser实例,如果客户端未登录)。
- perms - django.contrib.auth.context_processors.PermWrapper的一个实例,表示当前登录的用户拥有的权限。
DEBUG
django.template.context_processors.debug
如果此处理器已启用,则每个RequestContext都将包含这两个变量 - 但仅当您的DEBUG设置设置为True且请求的IP地址(request.META ['REMOTE_ADDR'])处于INTERNAL_IPS设置时:
- 调试 - true
您可以在模板中使用它来测试您是否处于DEBUG模式。 - sql_queries - {'sql':...,'time':...}字典的列表,表示在请求期间发生的每个SQL查询以及需要多长时间。 该列表按照查询顺序排列,并在访问时延迟生成。
国际化
django.template.context_processors.i18n
如果启用这个处理器,每个RequestContext将包含这两个变量:
- 语言 - LANGUAGES设置的值。
- LANGUAGE_CODE - request.LANGUAGE_CODE,如果存在。 否则,LANGUAGE_CODE设置的值。
媒体
django.template.context_processors.media
如果启用此处理器,则每个RequestContext都将包含一个变量MEDIA_URL,提供MEDIA_URL设置的值。
静态的
django.template.context_processors.static
如果启用此处理器,则每个RequestContext都将包含一个变量STATIC_URL,提供STATIC_URL设置的值。
CSRF
django.template.context_processors.csrf
这个处理器增加了一个csrf_token模板标签所需要的标记来保护跨站请求伪造(见第19章)。
请求
django.template.context_processors.request
如果启用了这个处理器,每个RequestContext将包含一个变量请求,它是当前的HttpRequest。
消息
django.contrib.messages.context_processors.messages
如果启用这个处理器,每个RequestContext将包含这两个变量:
- 消息 - 通过消息框架设置的消息列表(作为字符串)。
- DEFAULT_MESSAGE_LEVELS - 消息级别名称与其数值的映射。