为了减少模板加载调用过程及模板本身的冗余代码,Django 提供了一种使用方便且功能强大的 API,用于从磁盘中加载模板。
要使用此模板加载API,首先你必须将模板的保存位置告诉框架。设置的保存文件就是我们前一章节讲述
ROOT_URLCONF
配置的时候提到的 settings.py
。
如果你是一步步跟随我们学习过来的,马上打开你的 settings.py
配置文件,找到
TEMPLATES
这项设置吧。它的默认设置是一个空列表(list),列表元素值是字典类型。
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
该设置告诉 Django 的模板加载机制在哪里查找模板。TEMPLATES
中有一个字典元素,我们选择一个目录用于存放模板并将其添加到该元素中的 'DIRS'
键对应的值(list)中,为了简单描述代码,我们下面只展示要修改的 'DIRD' 字段项:
'DIRS': ['E:/djproject/mysite/templates']
下面是一些注意事项:
你可以任意指定想要的目录,只要运行 Web 服务器的用户可以读取该目录的子目录和模板文件。如果实在想不出合适的位置来放置模板,我们建议在 Django 项目中创建一个templates
目录(也就是说,如果你一直都按本书的范例操作的话,在第二章创建的mysite
目录中)。
以上的路径写法,也可以使用 Windows 路径表达方式,比如:
'E:\\djproject\\mysitemysite\\templates'
,而上面代码的路径写法,我们使用了
Unix 路径书写方式(E:/djproject/mysite/templates
)。你不用担心,Django 会把这种 Unix 路径风格转为和 Windows 操作系统对应的路径表达方式。但是,如果我们的项目运行在
类 Unix(Linux,mac)操作系统环境下,则需要写成 Unix 路径风格。所以,为了代码的通用性,我们都写成
Unix 风格的路径书写方式。
最省事的方式是使用绝对路径(即从文件系统根目录开始的目录路径)。如果想要更灵活一点并减少一些负面干扰,可利用 Django 配置文件就是 Python 代码这一点来动态构建TEMPLATES
的内容,例如:
import os TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
在 settings.py 文件中,你可以找到 'BASE_DIR' 这个变量对应的值为 os.path.dirname(os.path.dirname(os.path.abspath(__file__)))'),我们来仔细分析一下该值,该值里面有个神奇的 Python 内部变量__file__
,该变量被自动设置为代码所在的 Python 模块文件名。os.path.dirname(os.path.abspath(__file__))
将会获取自身所在的文件,即settings.py
所在的目录,然后我们在使用 os.path.dirname 获取 mysite 文件夹所在目录(项目根目录),最后由os.path.join
这个方法将项目根目录与templates
目录进行连接。在这里我们面对的是动态语言 python 代码,我需要提醒你的是,不要在你的设置文件里写入错误的代码,这很重要。如果你在这里引入了语法错误,或运行错误,你的 Django-powered 站点将很可能就要被崩溃掉。
完成 TEMPLATES
设置后,下一步就是修改视图代码和编写 urls.py 配置。下面我们先看一下
urls.py 配置,这个配置很简单,后面讲解中,我可能不在鳌述关于 urls.py 配置的问题,你应该可以根据视图文件写好这个配置。
from django.contrib import admin from django.urls import path from django.urls import re_path from mysite.views import current_datetime urlpatterns = [ path('admin/', admin.site.urls), re_path(r'^time/$', current_datetime), ]
下面,我们仔细分析一下视图文件里面的代码,在此我们使用 Django
模板加载功能而不是对模板路径硬编码,对视图函数 current_datetime
,进行如下修改:
from django.template.loader import get_template from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() t = get_template('current_datetime.html') context = {'current_date': now} html = t.render(context) return HttpResponse(html)
此范例中,我们使用了函数 django.template.loader.get_template()
,而不是手动从文件系统加载模板。该
get_template()
函数以模板名称为参数,在文件系统中找出模块的位置,打开文件并返回一个编译好的
Template
对象。
在这个例子里,我们选择的模板文件是 current_datetime.html
,但这个与
.html
后缀没有直接的联系。你可以选择任意后缀的任意文件,只要是符合逻辑的都行。甚至选择没有后缀的文件也不会有问题。
要确定某个模板文件在你的系统里的位置,get_template()
方法会自动为你连接已经设置的 TEMPLATES
目录和你传入该法的模板名称参数。比如,你的
TEMPLATES
目录设置为 'E:/djproject/mysite/templates'
,上面的
get_template()
调用就会为你找到
E:/djproject/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
错误。
该页面与我们在第三章解释过的错误页面相似,只不过多了一块调试信息区:模板加载器事后检查区。该区域显示 Django 要加载哪个模板、每次尝试出错的原因(如:文件不存在等)。当你尝试调试模板加载错误时,这些信息会非常有帮助。
接下来,在模板目录中创建包括以下模板代码 current_datetime.html
文件:
<html><body>It is now {{ current_date }}.</body></html>
在网页浏览器中刷新该页,你将会看到完整解析后的页面。
我们已经告诉你如何载入一个模板文件,然后用 render 函数
渲染它,最后返回这个处理好的 HttpResponse
对象给用户。我们已经优化了方案,使用
get_template()
方法代替繁杂的用代码来处理模板及其路径的工作。但这仍然需要一定量的时间来敲出这些简化的代码。这是一个普遍存在的重复苦力劳动。Django
为此提供了一个捷径,让你一次性地载入某个模板文件,渲染它,然后将此作为
HttpResponse
返回。
该捷径就是位于 django.shortcuts
模块中名为 render_to_response()
的函数。大多数情况下,你会使用该对象,除非你的老板以代码行数来衡量你的工作。
下面就是使用 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()
使用一个空字典。
思考一下我们对 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
。对此如何取舍取决你的应用程序。
把所有的模板都存放在一个目录下可能会让事情变得难以掌控。你可能会考虑把模板存放在你模板目录的子目录中,这非常好。事实上,我们推荐这样做;一些 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})
对子目录树的深度没有限制,你想要多少层都可以。只要你喜欢,用多少层的子目录都无所谓。
在讲解了模板加载机制之后,我们再介绍一个利用该机制的内建模板标签:{% include %}
。该标签允许在(模板中)包含其它的模板的内容。标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串。 每当在多个模板中出现相同的代码时,就应该考虑是否要使用
{% include %}
来减少重复。
下面这两个例子都包含了 nav.html
模板。这两个例子是等价的,它们证明单/双引号都是允许的。
{% include 'nav.html' %} {% include "nav.html" %}
下面的例子包含了 includes/nav.html
模板的内容:
{% include 'includes/nav.html' %}
下面的例子包含了以变量 template_name
的值为名称的模板内容:
{% include template_name %}
和在 get_template()
中一样,对模板的文件名进行判断时会在所调取的模板名称之前加上来自 TEMPLATES
的模板目录。
所包含的模板执行时的 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
,该标签不会引发错误信息,在标签位置不显示任何东西。