让我们深入研究模板系统,你将会明白它是如何工作的。但我们暂不打算将它与先前创建的视图结合在一起,因为我们现在的目的是了解它是如何独立工作的。(换言之,通常你会将模板和视图一起使用,但是我们只是想突出模板系统是一个 Python 库,你可以在任何地方使用它,而不仅仅是在 Django 视图中。)
在 Python 代码中使用 Django 模板的最基本方式如下:
可以用原始的模板代码字符串创建一个 Template
对象,Django 同样支持用指定模板文件路径的方式来创建 Template
对象;
调用模板对象的 render 方法,并且传入一套变量 context。它将返回一个基于模板的展现字符串,模板中的变量和标签会被 context 值替换。
代码如下:
>>> from django import template >>> t = template.Template('我的名字叫 {{ name }}。') >>> c = template.Context({'name': '如花'}) >>> print(t.render(c)) '我的名字叫 如花。' >>> c = template.Context({'name': '周星星'}) >>> print(t.render(c)) '我的名字叫 周星星。'
以下部分逐步的详细介绍
创建一个 Template
对象最简单的方法就是直接实例化它。Template
类就在 django.template
模块中,构造函数接受一个参数,原始模板代码。让我们深入挖掘一下
Python 的解释器看看它是怎么工作的。
转到项目目录(在第二章由 django-admin.exe startproject
命令创建),输入命令 python manage.py shell
启动交互界面。
一个特殊的 Python 提示符
如果你曾经使用过 Python,你一定好奇,为什么我们运行 python manage.py shell
而不是 python
。这两个命令都会启动交互解释器,但是 manage.py
shell
命令有一个重要的不同:在启动解释器之前,它告诉 Django 使用哪个设置文件。Django
框架的大部分子系统,包括模板系统,都依赖于配置文件;如果 Django
不知道使用哪个配置文件,这些系统将不能工作。
如果你想知道,这里将向你解释它背后是如何工作的。Django 搜索 DJANGO_SETTINGS_MODULE 环境变量,它被设置在 settings.py 中。例如,假设 mysite 在你的 Python 搜索路径中,那么 DJANGO_SETTINGS_MODULE 应该被设置为:’mysite.settings’。
当你运行命令:python manage.py shell
,它将自动帮你处理
DJANGO_SETTINGS_MODULE。在当前的这些示例中,我们鼓励你使用 python manage.py
shell
这个方法,这样可以免去你大费周章地去配置那些你不熟悉的环境变量。
随着你越来越熟悉 Django,你可能会偏向于废弃使用
manage.py shell
,而是在你的配置文件 .bash_profile
中手动添加
DJANGO_SETTINGS_MODULE
这个环境变量。
让我们来了解一些模板系统的基本知识:
>>> from django.template import Template >>> t = Template('我的名字叫 {{ name }}。') >>> print t
如果你跟我们一起做,你将会看到下面的内容:
<django.template.Template object at 0xb7d5f24c>
0xb7d5f24c
每次都会不一样,这没什么关系;这只是 Python 运行时
Template
对象的ID。
当你创建一个 Template
对象,模板系统在内部编译这个模板到内部格式,并做优化,做好 渲染的准备。如果你的模板语法有错误,那么在调用
Template()
时就会抛出 TemplateSyntaxError
异常:
>>> from django.template import Template >>> t = Template('{% notatag %}') Traceback (most recent call last): File "<stdin>", line 1, in ? ... django.template.TemplateSyntaxError: Invalid block tag: 'notatag'
这里,块标签(block tag)指向的是 {% notatag %}
,块标签与模板标签是同义的。
系统会在下面的情形抛出 TemplateSyntaxError
异常:
无效的tags
标签的参数无效
无效的过滤器
过滤器的参数无效
无效的模板语法
未封闭的块标签 (针对需要封闭的块标签)
一旦你创建一个 Template
对象,你可以用 context
来传递数据给它。一个 context 是一系列变量和它们值的集合。
context 在 Django 里表现为 Context
类,在 django.template
模块里。她的构造函数带有一个可选的参数:一个字典映射变量和它们的值。调用
Template
对象的 render()
方法并传递 context 来填充模板:
>>> from django.template import Context, Template >>> t = Template('我的名字叫 {{ name }}。') >>> c = Context({'name': '赵日天'}) >>> t.render(c) '我的名字叫 赵日天'
我们必须指出的一点是,t.render(c)
返回的值是一个
django.utils.safestring.SafeText'
对象,不是普通的 Python
字符串。你可以用 print(type(t.render(c))) 打印出该类型。在框架中,Django
会一直使用自己封装的字符串的对象类型而不是普通的字符串。如果你明白这样做给你带来了多大便利的话,尽可能地感激
Django 在幕后有条不紊地为你所做这这么多工作吧。如果不明白你从中获益了什么,别担心。你只需要知道 Django
对世界语言的支持,将让你的应用程序轻松地处理各式各样的字符集,而不仅仅是基本的 A-Z 英文字符。
Python 的字典数据类型就是关键字和它们值的一个映射。Context
和字典很类似,Context
还提供更多的功能,后面我们会学到。
变量名必须由英文字符开始(A-Z或a-z)并可以包含数字字符、下划线和小数点。(小数点在这里有特别的用途,稍后我们会讲到)变量是大小写敏感的。
下面是编写模板并渲染的示例:
>>> from django.template import Template, Context >>> raw_template = """<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> ... ... {% if ordered_warranty %} ... <p>Your warranty information will be included in the packaging.</p> ... {% else %} ... <p>You didn't order a warranty, so you're on your own when ... the products inevitably stop working.</p> ... {% endif %} ... ... <p>Sincerely,<br />{{ company }}</p>""" >>> t = Template(raw_template) >>> import datetime >>> c = Context({'person_name': 'John Smith', ... 'company': 'Outdoor Equipment', ... 'ship_date': datetime.date(2009, 4, 2), ... 'ordered_warranty': False}) >>> t.render(c) "<p>Dear John Smith,</p>\n\n<p>Thanks for placing an order from Outdoor Equipment. It's scheduled to\nship on April 2, 2009.</p>\n\n\n<p>You didn't order a warranty, so you're on your own when\nthe products inevitably stop working.</p>\n\n\n<p>Sincerely,<br />Outdoor Equipment </p>"
让我们逐步来分析下这段代码:
首先我们导入 (import)类Template
和Context
,它们都在模块django.template
里。我们把模板原始文本保存到变量rawtemplate
。注意到我们使用了三个引号来 标识这些文本,因为这样可以包含多行。接下来,我们创建了一个模板对象t
,把raw_template
作为Template
类构造函数的参数。我们从Python的标准库导入datetime
模块,以后我们将会使用它。然后,我们创建一个Context
对象,c
。Context
构造的参数是Python 字典数据类型。 在这里,我们指定参数person_name
的值是'John Smith'
, 参数company 的值为 ‘Outdoor Equipment’ ,等等。最后,我们在模板对象上调用render()
方法,传递 context参数给它。 这是返回渲染后的模板的方法,它会替换模板变量为真实的值和执行块标签。注意,warranty paragraph显示是因为ordered_warranty
的值为True
. 注意时间的显示,April 2, 2009
,它是按'F j, Y'
格式显示的。如果你是Python初学者,你可能在想为什么输出里有回车换行的字符('\n'
)而不是 显示回车换行?因为这是Python交互解释器的缘故:调用t.render(c)
返回字符串,解释器缺省显示这些字符串的 真实内容呈现_ ,而不是打印这个变量的值。要显示换行而不是'\n'
,使用print t.render(c)
。
这就是使用 Django 模板系统的基本规则:写模板,创建 Template
对象,创建
Context
,调用 render()
方法。
一旦有了 模板
对象,你就可以通过它渲染多个 context,例如:
>>> from django.template import Template, Context >>> t = Template('Hello, {{ name }}') >>> print t.render(Context({'name': 'John'})) Hello, John >>> print t.render(Context({'name': 'Julie'})) Hello, Julie >>> print t.render(Context({'name': 'Pat'})) Hello, Pat
无论何时我们都可以像这样使用同一模板源渲染多个 context,只进行 一次
模板创建然后多次调用 render() 方法渲染会更为高效:
# Bad for name in ('John', 'Julie', 'Pat'): t = Template('Hello, {{ name }}') print t.render(Context({'name': name})) # Good t = Template('Hello, {{ name }}') for name in ('John', 'Julie', 'Pat'): print t.render(Context({'name': name}))
Django 模板解析非常快捷。大部分的解析工作都是在后台通过对简短正则表达式一次性调用来完成。这和基于 XML 的模板引擎形成鲜明对比,那些引擎承担了 XML 解析器的开销,且往往比 Django 模板渲染引擎要慢上几个数量级。
在到目前为止的例子中,我们通过 context 传递的简单参数值主要是字符串,还有一个
datetime.date
范例。然而,模板系统能够非常简洁地处理更加复杂的数据结构,例如
list、dictionary 和自定义的对象。
在 Django 模板中遍历复杂数据结构的关键是句点字符 (.
)。
最好是用几个例子来说明一下。比如,假设你要向模板传递一个 Python 字典。要通过字典键访问该字典的值,可使用一个句点:
>>> from django.template import Template, Context >>> person = {'name': '如花', 'age': '18'} >>> t = Template('{{ person.name }}今年 {{ person.age }} 岁了。') >>> c = Context({'person': person}) >>> t.render(c) '如花今年 18 岁了。'
同样,也可以通过句点来访问对象的属性。比方说,Python 的 datetime.date
对象有 year
、 month
和 day
几个属性,你同样可以在模板中使用句点来访问这些属性:
>>> from django.template import Template, Context >>> import datetime >>> d = datetime.date(1993, 5, 2) >>> d.year 1993 >>> d.month 5 >>> d.day 2 >>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.') >>> c = Context({'date': d}) >>> t.render(c) 'The month is 5 and the year is 1993.'
这个例子使用了一个自定义的类,演示了通过实例变量加一点(dots) 来访问它的属性,这个方法适用于任意的对象。
>>> from django.template import Template, Context >>> class Person(object): ... def __init__(self, first_name, last_name): ... self.first_name, self.last_name = first_name, last_name >>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.') >>> c = Context({'person': Person('John', 'Smith')}) >>> t.render(c) 'Hello, John Smith.'
点语法也可以用来引用对象的方法。例如,每个 Python 字符串都有
upper()
和 isdigit()
方法,你在模板中可以使用同样的句点语法来调用它们:
>>> from django.template import Template, Context >>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}') >>> t.render(Context({'var': 'hello'})) u'hello -- HELLO -- False' >>> t.render(Context({'var': '123'})) '123 -- 123 -- True'
注意这里调用方法时并 没有 使用圆括号 而且也无法给该方法传递参数;你只能调用不需参数的方法。(我们将在本章稍后部分解释该设计观。)
最后,句点也可用于访问列表索引,例如:
>>> from django.template import Template, Context >>> t = Template('Item 2 is {{ items.2 }}.') >>> c = Context({'items': ['apples', 'bananas', 'carrots']}) >>> t.render(c) 'Item 2 is carrots.'
不允许使用负数列表索引。像 {{ items.-1 }}
这样的模板变量将会引发
TemplateSyntaxError
一点提示:Python 的列表是从 0 开始索引。第一项的索引是 0,第二项的是 1,依此类推。
句点查找规则可概括为:当模板系统在变量名中遇到点时,按照以下顺序尝试进行查找:
字典类型查找 (比如 foo["bar"]
)
属性查找 (比如 foo.bar
)
方法调用 (比如 foo.bar()
)
列表类型索引查找 (比如 foo[bar]
)
系统使用找到的第一个有效类型。这是一种短路逻辑。
句点查找可以多级深度嵌套。例如在下面这个例子中 {{person.name.upper}}
会转换成字典类型查找( person['name']
) 然后是方法调用(upper()
):
>>> from django.template import Template, Context >>> person = {'name': '赵日天', 'age': '17'} >>> t = Template('{{ person.name.upper }}今年 {{ person.age }} 岁了。') >>> c = Context({'person': person}) >>> t.render(c) '赵日天今年 17 岁了。'
方法调用比其他类型的查找略为复杂一点。以下是一些注意事项:
在方法查找过程中,如果某方法抛出一个异常,除非该异常有一个silent_variable_failure
属性并且值为True
,否则的话它将被传播。如果异常被传播,模板里的指定变量会被置为空字符串,比如:
>>> t = Template("My name is {{ person.first_name }}.") >>> class PersonClass3: ... def first_name(self): ... raise AssertionError, "foo" >>> p = PersonClass3() >>> t.render(Context({"person": p})) Traceback (most recent call last): ... AssertionError: foo >>> class SilentAssertionError(AssertionError): ... silent_variable_failure = True >>> class PersonClass4: ... def first_name(self): ... raise SilentAssertionError >>> p = PersonClass4() >>> t.render(Context({"person": p})) 'My name is .'
仅在方法无需传入参数时,其调用才有效。否则,系统将会转移到下一个查找类型(列表索引查找)。显然,有些方法是有副作用的,好的情况下允许模板系统访问它们可能只是干件蠢事,坏的情况下甚至会引发安全漏洞。例如,你的一个BankAccount
对象有一个delete()
方法。如果某个模板中包含了像{{ account.delete }}
这样的标签,其中account
又是BankAccount
的一个实例,请注意在这个模板载入时,account 对象将被删除。要防止这样的事情发生,必须设置该方法的alters_data
函数属性:
def delete(self): # Delete the account delete.alters_data = True
模板系统不会执行任何以该方式进行标记的方法。接上面的例子,如果模板文件里包含了{{ account.delete }}
,对象又具有delete()
方法,而且delete()
有alters_data=True
这个属性,那么在模板载入时,delete()
方法将不会被执行。它将静静地错误退出。
默认情况下,如果一个变量不存在,模板系统会把它展示为空字符串,不做任何事情来表示失败。例如:
>>> from django.template import Template, Context >>> t = Template('Your name is {{ name }}.') >>> t.render(Context()) 'Your name is .' >>> t.render(Context({'var': 'hello'})) 'Your name is .' >>> t.render(Context({'NAME': 'hello'})) 'Your name is .' >>> t.render(Context({'Name': 'hello'})) 'Your name is .'
系统静悄悄地表示失败,而不是引发一个异常,因为这通常是人为错误造成的。这种情况下,因为变量名有错误的状况或名称,所有的查询都会失败。现实世界中,对于一个 web 站点来说,如果仅仅因为一个小的模板语法错误而造成无法访问,这是不可接受的。
多数时间,你可以通过传递一个完全填充(full po你运行命令:python manage.py shepulated)的字典给 Context()
来初始化 上下文 (Context)
。但是初始化以后,你也可以使用标准的 Python
字典语法 (syntax) 向上下文 (Context)
对象添加或者删除条目:
>>> from django.template import Context >>> c = Context({"foo": "bar"}) >>> c['foo'] 'bar' >>> del c['foo'] >>> c['foo'] Traceback (most recent call last): ... KeyError: 'foo' >>> c['newvariable'] = 'hello' >>> c['newvariable'] 'hello'