上一篇Django模板3最后的问题,我们需要把数据和展现分离开。
你可能首先考虑把模板保存在文件系统的某个位置并用 Python 内建的文件操作函数来读取文件内容。 假设文件保存在 E:\djangosite\mysite\mysite\templates\tempTime.html 中的话,代码就会像下面这样:
<html><body>From tempTime.html====time now is {{current_date}}</body></html>
1 def temp_time(request): 2 now= datetime.datetime.now() 3 #f = open(BASE_DIR + ‘\\mysite\\templates\\tempTime.html‘) 4 f=open(r‘E:\djangosite\mysite\mysite\templates\tempTime.html‘) 5 content = f.read() 6 f.close() 7 t=Template(content) 8 c=Context({‘current_date‘:now}) 9 html = t.render(c) 10 return HttpResponse(html)
时刻记得在urls.py中去加配置哦
url(r‘^temptime/$‘,temp_time),
运行效果如下:
基于以下几个原因,该方法还算不上简洁:
- 它没有对文件丢失的情况做出处理。 如果文件 tempTime.html 不存在或者不可读, open() 函数调用将会引发 IOError 异常。
- 这里对模板文件的位置进行了硬编码。
- 它包含了大量令人生厌的重复代码。 与其在每次加载模板时都调用 open() 、 fp.read() 和 fp.close() ,还不如做出更佳选择。
为了解决这些问题,我们采用了 模板自加载 跟 模板目录 的技巧。
模板加载
为了减少模板加载调用过程及模板本身的冗余代码,Django 提供了一种使用方便且功能强大的 API,用于从磁盘中加载模板,要使用此模板加载API,首先你必须将模板的保存位置告诉框架。 打开settings.py配置文件,找到TEMPLATE_DIRS这项设置。(我本地新建的站点中,settings.py没有该项设置,但是官方文档中确实有它的介绍,自行加上即可)
这里有相关介绍,截图如下:
我新建的TEMPLATE_DIRS如下
import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) #这中间省去了其他配置项和说明 TEMPLATE_DIRS=( os.path.join(BASE_DIR,‘mysite\\templates‘).replace(‘\\‘,‘/‘), #r"E:\djangosite\mysite\mysite\templates".replace(‘\\‘,‘/‘), )
如果使用的是 Windows 平台,请包含驱动器符号并使用Unix风格的斜杠(/)而不是反斜杠(),就像下面这样
TEMPLATE_DIRS = ( ‘C:/www/django/templates‘, )
BASE_DIR:Python 内部变量 __file__ ,该变量被自动设置为代码所在的 Python 模块文件名。os.path.dirname(__file__)将会获取自身所在的文件,即settings.py 所在的目录
完成 TEMPLATE_DIRS 设置后,下一步就是修改视图代码,让它使用 Django 模板加载功能而不是对模板路径硬编码
get_template方式
进行如下修改:区别就在注释那8和9两行
1 import datetime 2 from django.template.loader import get_template 3 from django.template import Template,Context 4 from django.http import HttpResponse 5 6 def temp_load(request): 7 now= datetime.datetime.now() 8 #t = get_template(BASE_DIR + ‘\\mysite\\templates\\tempTime.html‘) 9 t = get_template(‘tempTime.html‘) 10 c=Context({‘current_date‘:now}) 11 html = t.render(c) 12 return HttpResponse(html)
同样不要忘记去配置urls.py,这里就不多说了。
此范例中,我们使用了函数 django.template.loader.get_template() ,而不是手动从文件系统加载模板。 该get_template() 函数以模板名称为参数,在文件系统中找出模块的位置,打开文件并返回一个编译好的 Template对象。
我们选择的模板文件是tempTime.html,但这个与.html后缀没有直接的联系。 你可以选择任意后缀的任意文件,只要是符合逻辑的都行。甚至选择没有后缀的文件也不会有问题。
要确定某个模板文件在你的系统里的位置, get_template()方法会自动为你连接已经设置的 TEMPLATE_DIRS目录和你传入该方法的模板名称参数。
运行效果如下:
render_to_response方式
前面已经说明如何通过Template对象载入一个模板文件,然后用 Context渲染它,最后返回这个处理好的HttpResponse对象给用户。 之后优化方案,使用 get_template() 方法代替繁杂的用代码来处理模板及其路径的工作。 但这仍然需要一定量的时间来敲出这些简化的代码。 这是一个普遍存在的重复苦力劳动。Django为此提供了一个捷径,让你一次性地载入某个模板文件,渲染它,然后将此作为 HttpResponse返回。
该捷径就是位于 django.shortcuts 模块中名为 render_to_response() 的函数。
1 from django.shortcuts import render_to_response 2 import datetime 3 4 def temp_render(request): 5 now = datetime.datetime.now() 6 return render_to_response(‘tempTime.html‘, {‘current_date‘: now})
render_to_response() 的第一个参数必须是要使用的模板名称。 如果要给定第二个参数,那么该参数必须是为该模板创建 Context 时所使用的字典。 如果不提供第二个参数, render_to_response() 使用一个空字典。
很多时候,我们要穿入到模板中的Context值很多,我们需要自己定义临时变量,然后组装Context对象,是不是觉得很麻烦?
Python内建函数 locals(),它返回的字典对所有局部变量的名称与值进行映射。 因此,前面的视图可以重写成下面这个样子:
1 def temp_locals(request): 2 current_date = datetime.datetime.now() 3 return render_to_response(‘tempTime.html‘, locals())
我们只是将临时变量now变成了模板需要的变量名称current_date,然后传递到render_to_response的第二个参数不在我们去构造Context对象,直接使用locals()即可。
当遇到需要传递很多参数时,只需要定义对应名称的临时变量,通过locals即可一次性传递过去,着实方便很多。
还记得Django模板1最后说的吗,解决无效参数,即便locals传递冗余没用的或者少传递了必要参数,django也不会报错的,而是将该值复制为空。
render_to_response()是对get_template()简单封装,他们都可以传递子目录,类似于
t = get_template(‘dateapp/current_datetime.html‘)
需要注意的是 Windows用户必须使用斜杠(/)而不是反斜杠(\)
include 模板标签
在讲解了模板加载机制之后,我们再介绍一个利用该机制的内建模板标签: {% include %} 。该标签允许在(模板中)包含其它的模板的内容。 标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串。 每当在多个模板中出现相同的代码时,就应该考虑是否要使用 {% include %} 来减少重复。
下面的例子包含了 includes/nav.html 模板的内容:
在templates下创建mypage.html和includes文件夹,文件夹下创建nav.html,如下
1 # mypage.html 2 3 <html> 4 <body> 5 {% include "includes/nav.html" %} 6 <h1>{{ title }}</h1> 7 </body> 8 </html>
1 # includes/nav.html 2 3 <div id="nav"> 4 You are in: {{ current_section }} 5 </div>
1 def temp_include(request): 2 now = datetime.datetime.now() 3 c= Context({‘title‘:‘my title‘,‘current_section‘:‘mysection‘}) 4 return render_to_response(‘mypage.html‘,c)
运行效果
模板继承
使用include标签可以解决一定层面上的代码代码重用问题,但是考虑一下,include是怎么一个情况呢?
定义页面公共部分的代码分别放置在不同的文件中,某一个页面需要时便include进来。这样页面布局一致,展示内容不同的页面都要分别取做页面,并include不同的文件。
这里强调了页面布局一致。
由此想起面向对象的类继承,我们抽象一个一致的页面布局,然后让内容不同的子页面扩展该布局怎么样?就是说和include思想相逆的方式。
Django确实提供了这样一种方式---模板继承 。
本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含的公用部分和定义块进行重载。
定义基础模板,该框架之后将由 子模板 所继承。 以下是我们目前所讲述范例的基础模板:
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> 2 <html lang="en"> 3 <head> 4 <title>{% block title%}{% endblock %}</title> 5 </head> 6 <body> 7 <h1>My helpful timestamp site</h1> 8 {% block content %}{% endblock %} 9 {% block footer %} 10 <hr> 11 <p>Thanks for visiting my site.</p> 12 {% endblock %} 13 </body> 14 </html>
这个叫做 base.html 的模板定义了一个简单的 HTML 框架文档,我们将在本站点的所有页面中使用。 子模板的作用就是重载、添加或保留那些块的内容。
所有的 {% block %} 标签告诉模板引擎,子模板可以重载这些部分。 每个{% block %}标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖。
现在我们已经有了一个基本模板,我们可以修改 current_datetime.html 模板来 使用它:
1 {% extends "base.html" %} 2 3 {% block title %}The current time{% endblock %} 4 5 {% block content %} 6 <p>It is now {{ current_date }}.</p> 7 {% endblock %}
工作方式
在加载 current_datetime.html 模板时,模板引擎发现了 {% extends %} 标签, 注意到该模板是一个子模板。 模板引擎立即装载其父模板,即本例中的 base.html 。
此时,模板引擎注意到 base.html 中的三个 {% block %} 标签,并用子模板的内容替换这些 block 。因此,引擎将会使用我们在 { block title %} 中定义的标题,对 {% block content %} 也是如此。
注意由于子模板并没有定义 footer 块,模板系统将使用在父模板中定义的值。 父模板 {% block %} 标签中的内容总是被当作一条退路。
继承并不会影响到模板的上下文。 换句话说,任何处在继承树上的模板都可以访问到你传到模板中的每一个模板变量。
使用模板继承的一些说明:
- 如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。 否则,模板继承将不起作用。
- 一般来说,基础模板中的 {% block %} 标签越多越好。 记住,子模板不必定义父模板中所有的代码块,因此你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行(重)定义。
- 如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的某个 {% block %} 中。
- 如果你需要访问父模板中的块的内容,使用 {{ block.super }}这个标签吧,这一个魔法变量将会表现出父模板中的内容。 如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了。
- 不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因为block 标签的工作方式是双向的。 也就是说,block 标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充的内容。如果模板中出现了两个相同名称的 {% block %} 标签,父模板将无从得知要使用哪个块的内容。
- {% extends %} 对所传入模板名称使用的加载方法和 get_template() 相同。 也就是说,会将模板名称被添加到TEMPLATE_DIRS 设置之后。
- 多数情况下, {% extends %} 的参数应该是字符串,但是如果直到运行时方能确定父模板名,这个参数也可以是个变量。
{{ block.super }}示例
1 {% extends "base.html" %} 2 3 {% block title %}Block.surper {% endblock %} 4 {% block content %} 5 {{ block.super }}<br /> 6 From current_datetime.html====time now is {{current_date}} 7 {% endblock %}
和上边current_datetime.html相比之增加了红色第五行代码
意思很明白了:子模板中,重写的{% block %}内包含{{ block.super }}就可以先展示父模板内容,再添加重写的内容。就像Java中super和c#中base。
小结
模板终于算是简单写完了,东西很多很杂乱,但是不难。大多数都是从DjangoBook摘过来的,如果写的不够清晰,请查看原文。
Django模板4