第四章:模板系统篇
如果HTML被硬性地直接写入 Python 代码之中,这种处理会导致一些问题:
-
对页面设计进行的任何改变都必须对 Python 代码进行相应的修改。站点设计的修改往往比底层 Python 代码的修改要频繁得多,因此如果可以在不进行 Python 代码修改的情况下变更设计,那将会方便得多。
-
Python 代码编写和 HTML 设计是两项不同的工作,大多数专业的网站开发环境都将他们分配给不同的人员(甚至不同部门)来完成。设计人员和 HTML/CSS 编写人员都不应该通过编辑 Python 代码来完成自己的工作;他们应该处理的是 HTML。
-
同理,程序员编写 Python 代码和设计人员制作模板同时进行的工作方式效率是最高的,远胜于让一个人等待另一个人完成对某个既包含 Python 又包含 HTML 的文件的编辑工作。
解决方案,使用模板就可以解决
模板系统基本知识
1、模板系统基本知识
6
<html>
<head><title>Ordering notice</title></head>
<body>
<p>Dear {{ person_name }},</p>
<p>Thanks for placing an order from {{ company }}. It's scheduled to
ship on {{ ship_date|date:"F j, Y" }}.</p>
<p>Here are the items you've ordered:</p>
<ul>
{% for item in item_list %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% if ordered_warranty %}
<p>Your warranty information will be included in the packaging.</p>
{% endif %}
<p>Sincerely,<br />{{ company }}</p>
</body>
</html>
Django 模板含有很多内置的tags和filters,我们将陆续进行学习. 附录F列出了很多的tags和filters的列表,熟悉这些列表对你来说是个好建议. 学习完第十章,你就明白怎么去创建自己的filters和tags了.
2、如何使用模板系统
两个步骤:
-
可以用原始的模板代码字符串创建一个 Template 对象, Django同样支持用指定模板文件路径的方式来创建 Template 对象;
-
调用 Template 对象的 render() 方法并提供给他变量(i.e., 内容). 它将返回一个完整的模板字符串内容,包含了所有标签块与变量解析后的内容
3、创建模板对象
创建一个 Template 对象最简单的方法就是直接实例化它。 Template 类就在 django.template 模块中,构造函数接受一个参数,原始模板代码。
>>> from django.template import Template
>>> t = Template("My name is {{ name }}.")
>>> print t
4、模板渲染
一旦你创建一个 Template 对象,你可以用 context 来传递数据给它。一个context是一系列变量和它们值的集合。模板使用它来赋值模板变量标签和执行块标签。
context在Django里表现为 Context 类,在 django.template 模块里。它构造是有一个可选参数:一个字典映射变量和它们的值。调用 Template 对象的 render() 方法并传递context来填充模板:
>>> from django.template import Context, Template
>>> t = Template("My name is {{ name }}.")
>>> c = Context({"name": "Stephane"})
>>> t.render(c)
'My name is Stephane.'
基本的模板标签和过滤器
1、标签 if/else : {% if %} {% else %} {% endif %}
{% if today_is_weekend %}
<p>Welcome to the weekend</p>
{% endif %}
{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% else %}
<p>Get back to work.</p>
{% endif %}
2、标签for :{% for n in list %} {% endfor %}
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
{% for country in countries %}
<h1>{{ country.name }}</h1>
<ul>
{% for city in country.city_list %}
<li>{{ city }}</li>
{% endfor %}
</ul>
{% endfor %}
3、标签ifequal/ifnotequal:
{% ifequal %} {% else %} {% endifqual %}
{% ifnotequal%} {% else %} {% endifnotequal %}
{% ifequal user currentuser %}
<h1>Welcome!</h1>
{% endifequal %}
{% ifequal section 'sitenews' %}
<h1>Site News</h1>
{% else %}
<h1>No News Here</h1>
{% endifequal %}
4、注释
象HTML和其他的语言例如python一样,Django模板系统也允许注释。注释使用 {# #} :
{# This is a comment #}
5、过滤器
模板过滤器是在变量被显示前修改它的值的一个简单方法
#显示的内容是变量 {{ name }} 被过滤器 lower 处理后的结果,它功能是转换文
#本为小写。使用 | 来应用过滤器。
{{ name|lower }}
#date : 按指定的格式字符串参数格式化 date 或者 datetime 对象
{{ pub_date|date:"F j, Y" }}
在视图中使用模板
1、简单模版使用
from django.template import Template, Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
# Simple way of using templates from the filesystem.
# This doesn't account for missing files!
fp = open('/home/djangouser/templates/mytemplate.html')
t = Template(fp.read())
fp.close()
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
该方法还算不上简洁:
-
它没有对文件丢失的情况做出处理。如果文件 mytemplate.html 不存在或者不可读, open() 函数调用将会引发 IOError 异常。
-
这里对模板文件的位置进行了硬编码。如果你在每个视图函数都用该技术,就要不断复制这些模板的位置。更不用说还要带来大量的输入工作!
-
它包含了大量令人生厌的重复代码。与其在每次加载模板时都调用 open() 、 fp.read() 和 fp.close() ,还不如做出更佳选择。
2、加载模版
为了减少模板加载调用过程及模板本身的冗余代码,Django 提供了一种使用方便且功能强大的 API ,用于从磁盘中加载模板,要使用此模板加载API,首先你必须将模板的保存位置告诉框架
修改settings.py文件中的配置
查看下面配置
DEBUG = True
TIME_ZONE = 'America/Chicago'
USE_I18N = True
ROOT_URLCONF = 'mysite.urls'
配置模版参数 TEMPLATE_DIRS
注意:如果使用的是 Windows 平台,请包含驱动器符号并使用Unix风格的斜杠(/)而不是反斜杠(\)
方式一:
TEMPLATE_DIRS = (
'/mysite/templates',
)
方式二:
TEMPLATE_DIRS = (
'C:/www/django/templates',
)
方式三:
import os.path
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
)
返回 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)
3、 找不到给定名称的模板
出现TemplateDoesNotExist 错误信息页面。
4、render_to_response()
由于加载模板、填充 context 、将经解析的模板结果返回为 HttpResponse 对象这一系列操作实在太常用了,Django 提供了一条仅用一行代码就完成所有这些工作的捷径。该捷径就是位于 django.shortcuts 模块中名为 render_to_response() 的函数。大多数时候,你将使用 render_to_response() ,而不是手动加载模板、创建 Context 和 HttpResponse 对象。
下面就是使用 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 该值。
5、locals() 技巧
如果你是个喜欢偷懒的程序员并想让代码看起来更加简明,可以利用 Python 的内建函数 locals() 。它返回的字典对所有局部变量的名称与值进行映射。因此,前面的视图可以重写成下面这个样子:
def current_datetime(request):
current_date = datetime.datetime.now()
return render_to_response('current_datetime.html', locals())
6、get_template()中使用子目录
把所有的模板都存放在一个目录下可能会让事情变得难以掌控。你可能会考虑把模板存放在你模板目录的子目录中,这非常好。事实上,我们推荐这样做;一些Django的高级特性(例如将在第九章讲到的通用视图系统)的缺省约定就是期望使用这种模板布局。
7、include 模板标签
内建模板标签: {% include %}
下面这两个例子都包含了 nav.html 模板。两个例子的作用完全相同,只不过是为了说明单、双引号都可以通用。
{% include 'nav.html' %} {% include "nav.html" %}
下面的例子包含了 includes/nav.html 模板的内容:
{% include 'includes/nav.html' %}
下面的例子包含了以变量 template_name 的值为名称的模板内容:
{% include template_name %}
8、模板继承
到目前为止,我们的模板范例都只是些零星的 HTML 片段,但在实际应用中,你将用 Django 模板系统来创建整个 HTML 页面。这就带来一个常见的 Web 开发问题:在整个网站中,如何减少共用页面区域(比如站点导航)所引起的重复和冗余代码?
解决该问题的传统做法是使用 服务器端的 includes ,你可以在 HTML 页面中使用该指令将一个网页嵌入到另一个中。事实上, Django 通过刚才讲述的 {% include %} 支持了这种方法。但是用 Django 解决此类问题的首选方法是使用更加简洁的策略—— 模板继承 。
header.html 文件
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
footer.html :
<hr>
<p>Thanks for visiting my site.</p>
</body>
</html>
base.html
第一步是定义 基础模板 , 该框架之后将由 子模板 所继承。以下是我们目前所讲述范例的基础模板
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<h1>My helpful timestamp site</h1>
{% block content %}{% endblock %}
{% block footer %}
<hr>
<p>Thanks for visiting my site.</p>
{% endblock %}
</body>
</html>
我们将在本站点的所有页面中使用。子模板的作用就是重载、添加或保留那些块的内容。(如果一直按我们的范例做话,请将此文件保存到模板目录。)
我们使用一个以前没有见过的模板标签: {% block %} 。所有的 {% block %} 标签告诉模板引擎,子模板可以重载这些部分。
现在我们已经有了一个基本模板,我们可以修改 current_datetime.html 模板来使用它:
我们可以修改 current_datetime.html 模板来使用它:
{% extends "base.html" %}
{% block title %}The current time{% endblock %}
{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}
每个模板只包含对自己而言 独一无二 的代码。无需多余的部分。如果想进行站点级的设计修改,仅需修改 base.html ,所有其它模板会立即反映出所作修改。
使用继承的一种常见方式是下面的三层法:
-
创建 base.html 模板,在其中定义站点的主要外观感受。这些都是不常修改甚至从不修改的部分。
-
为网站的每个区域创建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。这些模板对 base.html 进行拓展,并包含区域特定的风格与设计。
-
为每种类型的页面创建独立的模板,例如论坛页面或者图片库。这些模板拓展相应的区域模板。
使用模板继承的一些诀窍:
-
如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。否则,模板继承将不起作用
-
一般来说,基础模板中的 {% block %} 标签越多越好。记住,子模板不必定义父模板中所有的代码块,因此你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行(重)定义。俗话说,钩子越多越好。
-
如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的某个 {% block %} 中。
-
如果需要获得父模板中代码块的内容,可以使用 {{ block.super }} 变量。如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了。
-
不可同一个模板中定义多个同名的 {% block %} 。存在这样的限制是因为block 标签的工作方式是双向的。也就是说,block 标签不仅挖了一个要填的坑,也定义了在 父 模板中这个坑所填充的内容。如果模板中出现了两个相同名称的 {% block %} 标签,父模板将无从得知要使用哪个块的内容。
-
{% extends %} 对所传入模板名称使用的加载方法和 get_template() 相同。也就是说,会将模板名称被添加到 TEMPLATE_DIRS 设置之后。
-
多数情况下, {% extends %} 的参数应该是字符串,但是如果直到运行时方能确定父模板名,这个参数也可以是个变量。这使得你能够实现一些很酷的动态功能。