模板加载
为了减少模板加载调用过程及模板本身的冗余代码,Django 提供了一种使用方便且功能强大的 API ,用于从磁盘中加载模板,
要使用此模板加载API,首先你必须将模板的保存位置告诉框架。 设置的保存文件就是我们前一章节讲述ROOT_URLCONF
配置的时候提到的 settings.py
。
如果你是一步步跟随我们学习过来的,马上打开你的settings.py
配置文件,找到TEMPLATE_DIRS
这项设置吧。 它的默认设置是一个空元组(tuple),加上一些自动生成的注释。
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
该设置告诉 Django 的模板加载机制在哪里查找模板。 选择一个目录用于存放模板并将其添加到 TEMPLATE_DIRS
中:
TEMPLATE_DIRS = (
'/home/django/mysite/templates',
)
下面是一些注意事项:
你可以任意指定想要的目录,只要运行 Web 服务器的用户可以读取该目录的子目录和模板文件。 如果实在想不出合适的位置来放置模板,我们建议在 Django 项目中创建一个templates
目录(也就是说,如果你一直都按本书的范例操作的话,在第二章创建的mysite
目录中)。如果你的TEMPLATE_DIRS
只包含一个目录,别忘了在该目录后加上个逗号。Bad:
# Missing comma!
TEMPLATE_DIRS = (
'/home/django/mysite/templates'
)
Good:
# Comma correctly in place.
TEMPLATE_DIRS = (
'/home/django/mysite/templates',
)
Python 要求单元素元组中必须使用逗号,以此消除与圆括号表达式之间的歧义。 这是新手常犯的错误。如果使用的是 Windows 平台,请包含驱动器符号并使用Unix风格的斜杠(/)而不是反斜杠(),就像下面这样:
TEMPLATE_DIRS = (
'C:/www/django/templates',
)
最省事的方式是使用绝对路径(即从文件系统根目录开始的目录路径)。 如果想要更灵活一点并减少一些负面干扰,可利用 Django 配置文件就是 Python 代码这一点来动态构建TEMPLATE_DIRS
的内容,如: 例如:
import os.path
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
)
这个例子使用了神奇的 Python 内部变量file
,该变量被自动设置为代码所在的 Python 模块文件名。os.path.dirname(file)
将会获取自身所在的文件,即settings.py
所在的目录,然后由os.path.join
这个方法将这目录与templates
进行连接。如果在windows下,它会智能地选择正确的后向斜杠”“进行连接,而不是前向斜杠”/”。在这里我们面对的是动态语言python代码,我需要提醒你的是,不要在你的设置文件里写入错误的代码,这很重要。 如果你在这里引入了语法错误,或运行错误,你的Django-powered站点将很可能就要被崩溃掉。
完成 TEMPLATE_DIRS
设置后,下一步就是修改视图代码,让它使用 Django 模板加载功能而不是对模板路径硬编码。 返回 current_datetime
视图,进行如下修改:
from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
t = get_template('current_datetime.html')
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
此范例中,我们使用了函数 django.template.loader.get_template()
,而不是手动从文件系统加载模板。 该 get_template()
函数以模板名称为参数,在文件系统中找出模块的位置,打开文件并返回一个编译好的 Template
对象。
在这个例子里,我们选择的模板文件是current_datetime.html
,但这个与.html
后缀没有直接的联系。 你可以选择任意后缀的任意文件,只要是符合逻辑的都行。甚至选择没有后缀的文件也不会有问题。
要确定某个模板文件在你的系统里的位置, get_template()
方法会自动为你连接已经设置的 TEMPLATE_DIRS
目录和你传入该法的模板名称参数。比如,你的 TEMPLATE_DIRS
目录设置为 '/home/django/mysite/templates'
,上面的 get_template()
调用就会为你找到 /home/django/mysite/templates/current_datetime.html
这样一个位置。
如果 get_template()
找不到给定名称的模板,将会引发一个 TemplateDoesNotExist
异常。 要了解究竟会发生什么,让我们按照第三章内容,在 Django 项目目录中运行 python manage.py runserver
命令,再次启动Django开发服务器。 接着,告诉你的浏览器,使其定位到指定页面以激活current_datetime
视图(如 http://127.0.0.1:8000/time/
)。假设你的 DEBUG
项设置为 True
,而你有没有建立current_datetime.html
这个模板文件,你会看到Django的错误提示网页,告诉你发生了 TemplateDoesNotExist
错误。
图 4-1: 模板文件无法找到时,将会发送提示错误的网页给用户。
该页面与我们在第三章解释过的错误页面相似,只不过多了一块调试信息区: 模板加载器事后检查区。 该区域显示 Django 要加载哪个模板、每次尝试出错的原因(如:文件不存在等)。 当你尝试调试模板加载错误时,这些信息会非常有帮助。
接下来,在模板目录中创建包括以下模板代码 current_datetime.html
文件:
<html><body>It is now {{ current_date }}.</body></html>
在网页浏览器中刷新该页,你将会看到完整解析后的页面。
render_to_response()
我们已经告诉你如何载入一个模板文件,然后用 Context
渲染它,最后返回这个处理好的HttpResponse
对象给用户。 我们已经优化了方案,使用 get_template()
方法代替繁杂的用代码来处理模板及其路径的工作。 但这仍然需要一定量的时间来敲出这些简化的代码。 这是一个普遍存在的重复苦力劳动。Django为此提供了一个捷径,让你一次性地载入某个模板文件,渲染它,然后将此作为 HttpResponse
返回。
该捷径就是位于 django.shortcuts
模块中名为 render_to_response()
的函数。大多数情况下,你会使用``\ <code>
``对象,除非你的老板以代码行数来衡量你的工作。
System Message: WARNING/2 (<string>
, line 1736); backlink
Inline literal start-string without end-string.
System Message: WARNING/2 (<string>
, line 1736); backlink
Inline literal start-string without end-string.
System Message: WARNING/2 (<string>
, line 1736); backlink
Inline literal start-string without end-string.
下面就是使用 render_to_response()
重新编写过的 current_datetime
范例。
from django.shortcuts import render_to_response
import datetime
def current_datetime(request):
now = datetime.datetime.now()
return render_to_response('current_datetime.html', {'current_date': now})
大变样了! 让我们逐句看看代码发生的变化:
我们不再需要导入
get_template
、Template
、Context
和HttpResponse
。相反,我们导入django.shortcuts.render_to_response
。import datetime
继续保留.在
current_datetime
函数中,我们仍然进行now
计算,但模板加载、上下文创建、模板解析和HttpResponse
创建工作均在对render_to_response()
的调用中完成了。 由于render_to_response()
返回HttpResponse
对象,因此我们仅需在视图中return
该值。
render_to_response()
的第一个参数必须是要使用的模板名称。 如果要给定第二个参数,那么该参数必须是为该模板创建 Context
时所使用的字典。 如果不提供第二个参数, render_to_response()
使用一个空字典。
locals() 技巧
思考一下我们对 current_datetime
的最后一次赋值:
def current_datetime(request):
now = datetime.datetime.now()
return render_to_response('current_datetime.html', {'current_date': now})
很多时候,就像在这个范例中那样,你发现自己一直在计算某个变量,保存结果到变量中(比如前面代码中的 now ),然后将这些变量发送给模板。 尤其喜欢偷懒的程序员应该注意到了,不断地为临时变量和临时模板命名有那么一点点多余。 不仅多余,而且需要额外的输入。
如果你是个喜欢偷懒的程序员并想让代码看起来更加简明,可以利用 Python 的内建函数 locals()
。它返回的字典对所有局部变量的名称与值进行映射。 因此,前面的视图可以重写成下面这个样子:
def current_datetime(request):
current_date = datetime.datetime.now()
return render_to_response('current_datetime.html', locals())
在此,我们没有像之前那样手工指定 context 字典,而是传入了 locals()
的值,它囊括了函数执行到该时间点时所定义的一切变量。 因此,我们将 now
变量重命名为 currentdate
,因为那才是模板所预期的变量名称。 在本例中, locals()
并没有带来多 大_ 的改进,但是如果有多个模板变量要界定而你又想偷懒,这种技术可以减少一些键盘输入。
使用 locals()
时要注意是它将包括 所有 的局部变量,它们可能比你想让模板访问的要多。 在前例中, locals()
还包含了 request
。对此如何取舍取决你的应用程序。
get_template()中使用子目录
把所有的模板都存放在一个目录下可能会让事情变得难以掌控。 你可能会考虑把模板存放在你模板目录的子目录中,这非常好。 事实上,我们推荐这样做;一些Django的高级特性(例如将在第十一章讲到的通用视图系统)的缺省约定就是期望使用这种模板布局。
把模板存放于模板目录的子目录中是件很轻松的事情。 只需在调用 get_template()
时,把子目录名和一条斜杠添加到模板名称之前,如:
t = get_template('dateapp/current_datetime.html')
由于 render_to_response()
只是对 get_template()
的简单封装, 你可以对 render_to_response()
的第一个参数做相同处理。
return render_to_response('dateapp/current_datetime.html', {'current_date': now})
对子目录树的深度没有限制,你想要多少层都可以。 只要你喜欢,用多少层的子目录都无所谓。
注意
Windows用户必须使用斜杠而不是反斜杠。 get_template()
假定的是 Unix 风格的文件名符号约定。
include 模板标签
在讲解了模板加载机制之后,我们再介绍一个利用该机制的内建模板标签: {% include %}
。该标签允许在(模板中)包含其它的模板的内容。 标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串。 每当在多个模板中出现相同的代码时,就应该考虑是否要使用 {% include %}
来减少重复。
下面这两个例子都包含了 nav.html
模板。这两个例子是等价的,它们证明单/双引号都是允许的。
{% include 'nav.html' %}
{% include "nav.html" %}
下面的例子包含了 includes/nav.html
模板的内容:
{% include 'includes/nav.html' %}
下面的例子包含了以变量 template_name
的值为名称的模板内容:
{% include template_name %}
和在 get_template()
中一样, 对模板的文件名进行判断时会在所调取的模板名称之前加上来自 TEMPLATE_DIRS
的模板目录。
所包含的模板执行时的 context 和包含它们的模板是一样的。 举例说,考虑下面两个模板文件:
# mypage.html
<html>
<body>
{% include "includes/nav.html" %}
<h1>{{ title }}</h1>
</body>
</html>
# includes/nav.html
<div id="nav">
You are in: {{ current_section }}
</div>
如果你用一个包含 current_section
的上下文去渲染 mypage.html
这个模板文件,这个变量将存在于它所包含(include)的模板里,就像你想象的那样。
如果{% include %}
标签指定的模板没找到,Django将会在下面两个处理方法中选择一个:
如果
DEBUG
设置为True
,你将会在 Django 错误信息页面看到TemplateDoesNotExist
异常。如果
DEBUG
设置为False
,该标签不会引发错误信息,在标签位置不显示任何东西。