当前位置:首页 > 网站旧栏目 > 学习园地 > 设计软件教程 > 翻译www.djangobook.com之第四章:Django模板系统

翻译www.djangobook.com之第四章:Django模板系统
2010-01-13 23:35:21  作者:  来源:
前面的章节我们看到如何在视图中返回HTML,但是HTML是硬编码在Python代码中的
这会导致几个问题:
1,显然,任何页面的改动会牵扯到Python代码的改动
网站的设计改动会比Python代码改动更频繁,所以如果我们将两者分离开会更方便
2,其次,写后台Python代码与设计HTML是不同的工作,更专业的Web开发应该将两者分开
页面设计者和HTML/CSS程序员不应该编辑Python代码,他们应该与HTML打交道
3,程序员写Python代码同时页面设计者写HTML模板会更高效,而不是一个人等待另一个人编辑同样的文件
因此,使用Django的模板系统分离设计和Python代码会更干净更易维护

模板系统基础
Django模板是一个string文本,它用来分离一个文档的展现和数据
模板定义了placeholder和表示多种逻辑的tags来规定文档如何展现
通常模板用来输出HTML,但是Django模板也能生成其它基于文本的形式
让我们来看看一个简单的模板例子:
Java代码 复制代码
  1. <html>   
  2. <head><title>Ordering notice</title></head>   
  3. <body>   
  4. <p>Dear {{ person_name }},</p>   
  5. <p>Thanks for placing an order from {{ company }}. It's scheduled to   
  6. ship on {{ ship_date|date:"F j, Y" }}.</p>   
  7. <p>Here are the items you've ordered:</p>   
  8. <ul>   
  9. {% for item in item_list %}   
  10. <li>{{ item }}</li>   
  11. {% endfor %}   
  12. </ul>   
  13. {% if ordered_warranty %}   
  14. <p>Your warranty information will be included in the packaging.</p>   
  15. {% endif %}   
  16. <p>Sincerely,<br />{{ company }}</p>   
  17. </body>   
  18. </html>  

这个模板本质上是HTML,但是夹杂了一些变量和模板标签:
1,用{{}}包围的是变量,如{{person_name}},这表示把给定变量的值插入,如何指定这些变量的值我们即将说明
2,用{%%}包围的是块标签,如{%if ordered_warranty%}
块标签的含义很丰富,它告诉模板系统做一些事情
在这个例子模板中包含两个块标签:for标签表现为一个简单的循环结构,让你按顺序遍历每条数据
if标签则表现为逻辑的if语句
在这里,上面的标签检查ordered_warranty变量的值是否为True
如果是True,模板系统会显示{%if ordered_warranty%}和{%endif%}之间的内容
否则,模板系统不会显示这些内容
模板系统也支持{%else%}等其它逻辑语句
3,上面还有一个过滤器的例子,过滤器是改变变量显示的方式
上面的例子中{{ship_date|date:"F j, Y"}}把ship_date变量传递给过滤器
并给date过滤器传递了一个参数“F j, Y”,date过滤器以给定参数的形式格式化日期
类似于Unix,过滤器使用管道字符“|”
Django模板支持多种内建的块标签,并且你可以写你自己的标签

使用模板系统
在Python代码中使用模板系统,请按照下面的步骤:
1,用模板代码创建一个Template对象
Django也提供指定模板文件路径的方式创建Template对象
2,使用一些给定变量context调用Template对象的render()方法
这将返回一个完全渲染的模板,它是一个string,其中所有的变量和块标签都会根据context得到值

创建模板对象
最简单的方式是直接初始化它,Template类在django.template模块中,初始化方法需要一个参数
下面进入Python交互环境看看:
Java代码 复制代码
  1. >>> from django.template import Template   
  2. >>> t = Template("My name is {{my_name}}.")   
  3. >>> print t  

你将看到如下信息
Java代码 复制代码
  1. <django.template.Template object at 0xb7d5f24c>  

0xb7d5f24c每次都会改变,但是无所谓,它是Template对象的Python“identity”
在这本书中,我们会在Python的交互式会话环境中展示一些示例。当你看到三个大于号'>>>',就可以确定是在交互环境中了。
如果你从本书中拷贝代码,记得不要拷贝这些大于号。
当你创建Template对象,模板系统会编译模板代码,并准备渲染
如果你的模板代码有语法错误,调用Template()方法会触发TemplateSyntaxError异常
Java代码 复制代码
  1. >>> from django.template import Template   
  2. >>> t = Template('{%notatag%}')   
  3. Traceback (most recent call last):   
  4.     File "<stdin>", line 1, in ?   
  5.     ...   
  6.    django.template.TemplateSyntaxError: Invalid block tag: 'notatag'  

系统触发TemplateSyntaxError异常可能出于以下情况:
1,不合法的块标签
2,合法块标签接受不合法的参数
3,不合法的过滤器
4,合法过滤器接受不合法的参数
5,不合法的模板语法
6,块标签没关

渲染模板
一旦你拥有一个Template对象,你可以通过给一个context来给它传递数据
context是一个变量及赋予的值的集合,模板使用它来得到变量的值,或者对于块标签求值
这个context由django.template模块的Context类表示
它的初始化函数有一个可选的参数:一个映射变量名和变量值的字典
通过context调用Template对象的render()方法来填充模板,例如:
Java代码 复制代码
  1. >>> from django.template import Context, Template   
  2. >>> t = Template("My name is {{name}}.")   
  3. >>> c = Context({"name""Stephane"})   
  4. >>> t.render(c)   
  5. 'My name is Stephane.'  

变量名必须以字母(A-Z或a-z)开始,可以包含数字,下划线和小数点,变量名大小写敏感
下面是一个模板编译和渲染的例子,使用这章开始时的模板例子:
Java代码 复制代码
  1. >>> from django.template import Template, Context   
  2. >>> raw_template = """<p>Dear {{ person_name }},</p>   
  3. ...   
  4. ... <p>Thanks for ordering {{ product }} from {{ company }}. It's scheduled to   
  5. ... ship on {{ ship_date|date:"F j, Y" }}.</p>   
  6. ...   
  7. ... {% if ordered_warranty %}   
  8. ... <p>Your warranty information will be included in the packaging.</p>   
  9. ... {% endif %}   
  10. ...   
  11. ... <p>Sincerely,<br />{{ company }}</p>"""   
  12. >>> t = Template(raw_template)   
  13. >>> import datetime   
  14. >>> c = Context({'person_name''John Smith',   
  15. ...     'product''Super Lawn Mower',   
  16. ...     'company''Outdoor Equipment',   
  17. ...     'ship_date': datetime.date(200942),   
  18. ...     'ordered_warranty': True})   
  19. >>> t.render(c)   
  20. "<p>Dear John Smith,</p>\n\n<p>Thanks for ordering Super Lawn Mower from Outdoor Equipment.   
  21. It's scheduled to ship on April 22009.</p>\n\n<p>Your warranty information will be included   
  22. in the packaging.</p>\n\n\n<p>Sincerely,<br />Outdoor Equipment</p>"  

让我们来看看都做了些什么:
1,我们import Template和Context类,它们都在django.template模块里面
2,我们把模板文本存储在raw_template变量里,我们使用"""来构建string,它可以跨越多行
3,我们创建模板对象t,并给Template类的初始化函数传递raw_template变量
4,我们从Python的标准库import datetime模块,下面会用到它
5,我们创建一个context对象c,它的初始化函数使用一个映射变量名和变量值的字典
例如我们指定person_name的值为'John Smith',product的值为'Super Lawn Mower'等等
6,最后,我们调用模板对象的render()方法,参数为context对象c
这将返回渲染后的模板,将模板中的变量值替换并计算块标签的结果
如果你刚接触Python,你可能会问为什么输出中包含了新行字符'\n'而不是换行
这是因为Python交互环境中调用t.render(c)会显示string的representation而不是string的值
如果你想看到换行而不是'\n',使用print t.render(c)即可
上面是使用Django模板系统的基础,只是创建一个模板对象和context对象然后调用render()方法
同一个模板,多个context的情况:
一旦你创建了一个模板对象,你可以渲染多个context,例如:
Java代码 复制代码
  1. >>> from django.template import Template, Context   
  2. >>> t = Template('Hello, {{ name }}')   
  3. >>> print t.render(Context({'name''John'}))   
  4. Hello, John   
  5. >>> print t.render(Context({'name''Julie'}))   
  6. Hello, Julie   
  7. >>> print t.render(Context({'name''Pat'}))   
  8. Hello, Pat  

无论何时,你使用同一个模板来渲染多个context的话,创建一次Template对象然后调用render()多次会更高效
Java代码 复制代码
  1. # Bad   
  2. for name in ('John''Julie''Pat'):   
  3.     t = Template('Hello, {{ name }}')   
  4.     print t.render(Context({'name': name}))   
  5. # Good   
  6. t = Template('Hello, {{ name }}')   
  7. for name in ('John''Julie''Pat'):   
  8.     print t.render(Context({'name': name}))  

Django的模板解析非常快,在后台,大部分的解析通过一个单独的对正则表达式的调用来做
这与基于XML的模板引擎形成鲜明对比,XML解析器比Django的模板渲染系统慢很多

Context变量查找
上面的例子中,我们给模板context传递了简单的值,大部分是string,以及一个datetime.date
尽管如此,模板系统优雅的处理更复杂的数据结构,如列表,字典和自定义对象
在Django模板系统中处理复杂数据结构的关键是使用(.)字符
使用小数点来得到字典的key,属性,对象的索引和方法
下面通过例子来解释,通过(.)访问字典的key:
Java代码 复制代码
  1. >>> from django.template import Template, Context   
  2. >>> person = {'name''Sally''age''43'}   
  3. >>> t = Template('{{ person.name }} is {{ person.age }} years old.')   
  4. >>> c= Context({'person': person})   
  5. >>> t.render(c)   
  6. 'Sally is 43 years old.'  

通过(.)来访问对象的属性:
Java代码 复制代码
  1. >>> from django.template import Template, Context   
  2. >>> import datetime   
  3. >>> d = datetime.date(199352)   
  4. >>> d.year   
  5. 1993  
  6. >>> d.month   
  7. 5  
  8. >>> d.day   
  9. 2  
  10. >>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.')   
  11. >>> c = Context({'date': d})   
  12. >>> t.render(c)   
  13. 'The month is 5 and the year is 1993.'  

下面的例子使用一个自定义类:
Java代码 复制代码
  1. >>> from django.template import Template, Context   
  2. >>> class Person(object):   
  3. ...    def __init__(self, first_name, last_name):   
  4. ...        self.first_name, self.last_name = first_name, last_name   
  5. >>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.')   
  6. >>> c = Context({'person': Person('John''Smith')})   
  7. >>> t.render(c)   
  8. 'Hello, John Smith.'  

小数点也可以用来调用列表的索引:
Java代码 复制代码
  1. >>> from django.template import Template, Context   
  2. >>> t = Template('Item 2 is {{ items.2 }}.')   
  3. >>> c = Contexst({'items': ['apples''bananas''carrots']})   
  4. >>> t.render(c)   
  5. 'Item 2 is carrots.'  

负数的列表索引是不允许的,例如模板变量{{ items.-1 }}将触发TemplateSyntaxError
最后小数点还可以用来访问对象的方法,例如Python的string有upper()和isdigit()方法:
Java代码 复制代码
  1. >>> from django.template import Template, Context   
  2. >>> t = Template('{{ var }} -- {{var.upper }} -- {{ var.isdigit }}')   
  3. >>> t.render(Context({'var''hello'}))   
  4. 'hello -- HELLO -- False'  
  5. >>> t.render(Context({'var''123'}))   
  6. '123 - 123 - True'  

注意,调用方法时你不能加括号,你也不能给方法传递参数
你只能调用没有参数的方法,后面我们会解释这些
总结一下,当模板系统遇到变量名里有小数点时会按以下顺序查找:
1,字典查找,如foo["bar"]
2,属性查找,如foo.bar
3,方法调用,如foo.bar()
3,列表的索引查找,如foo[bar]
小数点可以多级纵深查询,例如{{ person.name.upper }}表示字典查询person['name']然后调用upper()方法
Java代码 复制代码
  1. >>> from django.template import Template, Context   
  2. >>> person = {'name''Sally''age''43'}   
  3. >>> t = Template('{{ person.name.upper }} is {{ person.age }} years old.')   
  4. >>> c = Context({'person': person})   
  5. >>> t.render(c)   
  6. 'SALLY is 43 years old.'  


关于方法调用
方法调用要比其他的查询稍微复杂一点,下面是需要记住的几点:
1,在方法查询的时候,如果一个方法触发了异常,这个异常会传递从而导致渲染失
败,但是如果异常有一个值为True的silent_variable_failure属性,这个变量会渲染成空string:
Java代码 复制代码
  1. >>> t = Template("My name is {{ person.first_name }}.")   
  2. >>> class PersonClas3:   
  3. ...     def first_name(self):   
  4. ...         raise AssertionError, "foo"  
  5. >>> p = PersonClass3()   
  6. >>> t.render(Context({"person": p}))   
  7. Traceback (most recent call last):   
  8. ...   
  9. AssertionError: foo   
  10. >>> class SilentAssetionError(AssertionError):   
  11. ...     silent_variable_failure = True   
  12. >>> class PersonClass4:   
  13. ...     def first_name(self):   
  14. ...         raise SilentAssertionError   
  15. >>> p = PersonClass4()   
  16. >>> t.render(Context({"person": p}))   
  17. "My name is ."  

2,方法调用仅仅在它没有参数时起作用,否则系统将继续查找下一个类型(列表索引查询)
3,显然一些方法有副作用,让系统访问它们是很愚蠢的,而且很可能会造成安全性问
题。
例如你有一个BankAccount对象,该对象有一个delete()方法,模板系统不应该允许做下面的事情
I will now delete this valuable data. {{ account.delete }}
为了防止这种状况,可以在方法里设置一个方法属性alters_data
如果设置了alters_data=True的话模板系统就不会执行这个方法:
Java代码 复制代码
  1. def delete(self):   
  2.     # Delete the account   
  3. delete.alters_data = True  


不合法的变量怎样处理
默认情况下如果变量不存在,模板系统会把它渲染成空string,例如:
Java代码 复制代码
  1. >>> from django.template import Template, Context   
  2. >>> t = Template('Your name is {{ name }}.')   
  3. >>> t.render(Context())   
  4. 'Your name is .'  
  5. >>> t.render(Context({'var''hello'}))   
  6. 'Your name is .'  
  7. >>> t.render(Context({'NAME''hello'}))   
  8. 'Your name is .'  
  9. >>> t.render(Context({'Name''hello'}))   
  10. 'Your name is .'  

系统会静悄悄地显示错误的页面,而不是产生一个异常,因为这种情况通常是人为的错误。
在现实情形下,一个web站点因为一个模板代码语法的错误而变得不可用是不可接受的。
我们可以通过设置Django配置更改Django的缺省行为,第10章扩展模板引擎会我们会讨论这个

玩玩Context对象
大多数情况下你初始化Context对象会传递一个字典给Context()
一旦你初始化了Context,你可以使用标准Python字典语法增减Context对象的items:
Java代码 复制代码
  1. >>> from django.template import Context   
  2. >>> c = Context({"foo""bar"})   
  3. >>> c['foo']   
  4. 'bar'  
  5. >>> del c['foo']   
  6. >>> c['foo']   
  7. ''  
  8. >>> c['newvariable'] = 'hello'  
  9. >>> c['newvariable']   
  10. 'hello'  

Context对象是一个stack,你可以push()和pop()
如果你pop()的太多的话它将触发django.template.ContextPopException:
Java代码 复制代码
  1. >>> c = Context()   
  2. >>> c['foo'] = 'first level'  
  3. >>> c.push()   
  4. >>> c['foo'] = 'second level'  
  5. >>> c['foo']   
  6. 'second level'  
  7. >>> c.pop()   
  8. >>> c['foo']   
  9. 'first level'  
  10. >>> c['foo'] = 'overwritten'  
  11. >>> c['foo']   
  12. 'overwritten'  
  13. >>> c.pop()   
  14. Traceback (most recent call last):   
  15. ...   
  16. django.template.ContextPopException  

第10章你会看到使用Context作为stack自定义模板标签

模板标签和过滤器基础
我们已经提到模板系统使用内建的标签和过滤器
这里我们看看常见的,附录6包含了完整的内建标签和过滤器,你自己熟悉那个列表来了解可以做什么是个好主意

if/else
{% if %}标签计算一个变量值,如果是“true”,即它存在、不为空并且不是false的boolean值
系统则会显示{% if %}和{% endif %}间的所有内容:
Java代码 复制代码
  1. {% if today_is_weekend %}   
  2.     <p>Welcome to the weekend!</p>   
  3. {% else %}   
  4.     <p>Get back to work.</p>   
  5. {% endif %}  

{% if %}标签接受and,or或者not来测试多个变量值或者否定一个给定的变量,例如:
Java代码 复制代码
  1. {% if athlete_list and coach_list %}   
  2.     Both athletes and coaches are available.   
  3. {% endif %}   
  4. {% if not athlete_list %}   
  5.     There are no athletes.   
  6. {% endif %}   
  7. {% if athlete_list or coach_list %}   
  8.     There are some athletes or some coaches.   
  9. {% endif %}   
  10. {% if not athlete_list or coach_list %}   
  11.     There are no athletes or there are some coaches.   
  12. {% endif %}   
  13. {% if athlete_list and not coach_list %}   
  14.     There are some athletes and absolutely no coaches.   
  15. {% endif %}  

{% if %}标签不允许同一标签里同时出现and和or,否则逻辑容易产生歧义,例如下面的标签是不合法的:
Java代码 复制代码
  1. {% if athlete_list and coach_list or cheerleader_list %}  

如果你想结合and和or来做高级逻辑,只需使用嵌套的{% if %}标签即可:
Java代码 复制代码
  1. {% if athlete_list %}   
  2.     {% if coach_list or cheerleader_list %}   
  3.         We have athletes, and either coaches or cheerleaders!   
  4.     {% endif %}   
  5. {% endif %}  

多次使用同一个逻辑符号是合法的:
Java代码 复制代码
  1. {% if athlete_list or coach_list or parent_list or teacher_list %}  

没有{% elif %}标签,使用嵌套的{% if %}标签可以做到同样的事情:
Java代码 复制代码
  1. {% if athlete_list %}   
  2.     <p>Here are the athletes: {{ athlete_list }}.</p>   
  3. {% else %}   
  4.     <p>No athletes are available.</p>   
  5.     {% if coach_list %}   
  6.         <p>Here are the coaches: {{ coach_list }}.</p>   
  7.     {% endif %}   
  8. {% endif %}  

确认使用{% endif %}来关闭{% if %}标签,否则Django触发TemplateSyntaxError

for
{% for %}标签允许你按顺序遍历一个序列中的各个元素
Python的for语句语法为for X in Y,X是用来遍历Y的变量
每次循环模板系统都会渲染{% for %}和{% endfor %}之间的所有内容
例如,显示给定athlete_list变量来显示athlete列表:
Java代码 复制代码
  1. <ul>   
  2. {% for athlete in athlete_list %}   
  3.     <li>{{ athlete.name }}</li>   
  4. {% endfor %}   
  5. </ul>  

在标签里添加reversed来反序循环列表:
Java代码 复制代码
  1. {% for athlete in athlete_list reversed %}   
  2. ...   
  3. {% endfor %}   
  4. {% for %}标签可以嵌套:   
  5. {% for country in countries %}   
  6.     <h1>{{ country.name }}</h1>   
  7.     <ul>   
  8.     {% for city in country.city_list %}   
  9.         <li>{{ city }}</li>   
  10.     {% endfor %}   
  11.     </ul>   
  12. {% endfor %}  

系统不支持中断循环,如果你想这样,你可以改变你想遍历的变量来使得变量只包含你想遍历的值
类似的,系统也不支持continue语句,本章后面的“哲学和限制”会解释设计的原则
{% for %}标签内置了一个forloop模板变量,这个变量含有一些属性可以提供给你一些关于循环的信息
1,forloop.counter表示循环的次数,它从1开始计数,第一次循环设为1,例如:
Java代码 复制代码
  1. {% for item in todo_list %}   
  2.     <p>{{ forloop.counter }}: {{ item }}</p>   
  3. {% endfor %}  

2,forloop.counter0类似于forloop.counter,但它是从0开始计数,第一次循环设为0
3,forloop.revcounter表示循环中剩下的items数量,第一次循环时设为items总数,最后一次设为1
4,forloop.revcounter0类似于forloop.revcounter,但它是表示的数量少一个,即最后一次循环时设为0
5,forloop.first当第一次循环时值为True,在特别情况下很有用
安徽新华电脑学校专业职业规划师为你提供更多帮助【在线咨询