django_book学习笔记3-模板
Python代码编写和HTML设计是两项不同的工作,将页面的设计和Python的代码分离开会更干净简洁更容易维护。 我们可以使用 Django的 模板系统 (Template System)来实现这种模式.
一、 模板系统基本知识
模板是一个文本,用于分离文档的表现形式和内容。 模板定义了占位符以及各种用于规范文档该如何显示的各部分基本逻辑(模板标签)。 模板通常用于产生HTML,但是Django的模板也能产生任何基于文本格式的文档。
1、变量
用两个大括号括起来的文字(例如 {{ person_name }} )称为 变量(variable) 。这意味着在此处插入指定变量的值。
2、标签
被大括号和百分号包围的文本(例如 {% if ordered_warranty %} )是 模板标签(template tag) 。标签(tag)定义比较明确,即: 仅通知模板系统完成某些工作的标签。
这个例子中的模板包含一个for标签( {% for item in item_list %} )和一个if 标签({% if ordered_warranty %} )
for标签类似Python的for语句,可让你循环访问序列里的每一个项目。 if 标签,正如你所料,是用来执行逻辑判断的。 在这里,tag标签检查ordered_warranty值是否为True。如果是,模板系统将显示{% if ordered_warranty %}和{% else %}之间的内容;否则将显示{% else %}和{% endif %}之间的内容。{% else %}是可选的。
3、过滤输出
filter过滤器的例子,它是一种最便捷的转换变量输出格式的方式。 如这个例子中的{{ship_date|date:”F j, Y” }},我们将变量ship_date传递给date过滤器,同时指定参数”F j,Y”。date过滤器根据参数进行格式输出。 过滤器是用管道符(|)来调用的。
djangi内置的标签和过滤器:http://djangobook.py3k.cn/appendixF/
二、如何使用模板系统
模板系统是一个Python库,你可以在任何地方使用它,而不仅仅是在Django视图中
在Python代码中使用Django模板的最基本方式如下:
1)可以用原始的模板代码字符串创建一个 Template 对象, Django同样支持用指定模板文件路径的方式来创建 Template 对象;
2)调用模板对象的render方法,并且传入一套变量context。它将返回一个基于模板的展现字符串,模板中的变量和标签会被context值替换。
#python manage.py shell
python manage.py shell而不是python。这两个命令都会启动交互解释器,但是manage.py shell命令有一个重要的不同: 在启动解释器之前,它告诉Django使用哪个设置文件。 Django框架的大部分子系统,包括模板系统,都依赖于配置文件;如果Django不知道使用哪个配置文件,这些系统将不能工作。
1、创建模板对象
>>> from django import template
>>> t = template.Template('My name is {{ name }}.') #创建Template对象的实例
2、模板渲染
一旦你创建一个 Template 对象,你可以用 context 来传递数据给它。 一个context是一系列变量和它们值的集合。Context 和字典很类似
context在Django里表现为 Context 类,在 django.template 模块里。 她的构造函数带有一个可选的参数: 一个字典映射变量和它们的值。
调用Template对象的render()方法并传递context来填充模板:
c = template.Context({'name': 'Adrian'}) #传递数据给Template对象#或者dc={'name': 'Adrian'} c = template.Context(dc)
t.render(c) #填充模板
t.render(c)返回的值是一个Unicode对象,不是普通的Python字符串
总结
from django import template #导入模板
tp='My name is {{ name }}.' #定义模板内容格式
dc={'name': 'Adrian'} #定义要传递给Template对象实例的内容
t = template.Template(tp) #创建Template对象的实例,初始化参数为定义模板内容格式tp
c = template.Context(dc) #从原始数据dc制造一个可以被Template对象实例使用render方法读取的对象(测试好像是列表[{'name': 'Adrian'}])
t.render(c) #Template对象实例使用render方法填充
这就是使用Django模板系统的基本规则: 写模板,创建Template对象,创建 Context,调用render() 方法读Context
系统会在下面的情形抛出 TemplateSyntaxError 异常:
无效的tags
标签的参数无效
无效的过滤器
过滤器的参数无效
无效的模板语法
未封闭的块标签 (针对需要封闭的块标签)
3、同一模板,多个上下文
一旦有了 模板 对象,你就可以通过它渲染多个context
t = Template('Hello, {{ name }}')
for name in ('John', 'Julie', 'Pat'):
print t.render(Context({'name': name}))
4、深度变量的查找
模板系统能够非常简洁地处理更加复杂的数据结构,例如list、dictionary和自定义的对象。
在 Django 模板中遍历复杂数据结构的关键是句点字符 (.)。
1)假设你要向模板传递一个 Python 字典。 要通过字典键访问该字典的值,可使用一个句点:
>>> from django.template import Template, Context
>>> ps = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name} } is {{ person.age }} years old.')
>>> c = Context({'person': ps})#这里person是一个字典对象,模板里通过字典对象里面的值方式获得获得值(person.name)
>>> t.render(c)
u'Sally is 43 years old.'
2)同样,也可以通过句点来访问对象的属性。 比方说, Python 的 datetime.date 对象有 year 、 month 和 day 几个属性,你同样可以在模板中使用句点来访问这些属性:
>>> 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 }}.')#这里要注意的是调用使用的是Context({'date')这里的date不是对象d的d.month
>>> c = Context({'date': d})#这里有点date到对象d的应用的意思,也就是date变成了字典对象了
>>> t.render(c)
u'The month is 5 and the year is 1993.'
自定义的类,演示了通过实例变量加一点(dots)来访问它的属性,这个方法适用于任意的对象。
>>> from django.template import Template, Context
>>> class PS(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': PS('John', 'Smith')})
>>> t.render(c)
u'Hello, John Smith.
3)每个 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'}))
u'123 -- 123 -- True'
注意这里调用方法时并* 没有* 使用圆括号 而且也无法给该方法传递参数;你只能调用不需参数的方法。
4)句点也可用于访问列表索引
>>> from django.template import Template, Context
>>> t = Template('Item 2 is {{ items.2 }}.')
>>> c = Context({'items': ['apples', 'bananas', 'carrots']})
>>> t.render(c)
u'Item 2 is carrots.'
句点使用小结
在模板中可以使用同样的句点语法加key来调用字典的值
在模板中可以使用同样的句点语法来调用对象的属性:
在模板中可以使用同样的句点语法来调用对象的方法:
句点查找规则可概括为: 当模板系统在变量名中遇到点时,按照以下顺序尝试进行查找:
字典类型查找 (比如 foo["bar"] )
属性查找 (比如 foo.bar )
方法调用 (比如 foo.bar() )
列表类型索引查找 (比如 foo.1 )。
句点查找可以多级深度嵌套。 例如在下面这个例子中 {{person.name.upper}} 会转换成字典类型查找( person['name'] ) 然后是方法调用( upper() ):
5、方法调用行为
这里翻译有问题
If the exception does have a silent_variable_failure attribute, the variable will render as an empty string
我理解整理为:在方法查找过程中,某方法抛出一个异常。如果该异常用一个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}))
u'My name is .
6、如何处理无效变量
默认情况下,如果一个变量不存在,模板系统会把它展示为空字符串,不做任何事情来表示失败
三、基本的模板标签
1、if/else标签
1)if/else基本用法
{% if %} 标签检查(evaluate)一个变量,如果这个变量为真(即,变量存在,非空,不是布尔值假),系统会显示在 {% if %} 和 {% endif %}内容
{% else %} 标签是可选的:
{% if today_is_weekend %}
<p>Welcome to the weekend!</p>
{% else %}
<p>Get back to work.</p>
{% endif %}
2)常见变量的真假
在Python和Django模板系统中,以下这些对象相当于布尔值的False
空列表([] )
空元组(() )
空字典({} )
空字符串('' )
零值(0 )
特殊对象None
对象False(很明显)
提示:你也可以在自定义的对象里定义他们的布尔值属性(这个是python的高级用法)。
除以上几点以外的所有东西都视为`` True``
3)and,or或者not关键字来对多个变量做判断
{% if %}标签接受and,or或者not关键字来对多个变量做判断,或者对变量取反(not),但是{% if %} 标签不允许在同一个标签中同时使用and和or。
2、for标签
1)基本用法
{% for %} 允许我们在一个序列上迭代。 与Python的 for 语句的情形类似,循环语法是 for X in Y ,Y是要迭代的序列而X是在每一个特定的循环中使用的变量名称。 每一次循环中,模板系统会渲染在 {% for %} 和 {% endfor %} 之间的所有内容。
给标签增加一个 reversed 使得该列表被反向迭代:
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
2)反迭代
{% for athlete in athlete_list reversed %}
...
{% endfor %}
3)for判断空列表
{% for athlete in athlete_list %}
<p>{{ athlete.name }}</p>
{% empty %}
<p>There are no athletes. Only computer programmers.</p>
{% endfor %}
4)模板变量forloop
在每个`` {% for %}``循环里有一个称为`` forloop`` 的模板变量。这个变量有一些提示循环进度信息的属性。
forloop.counter 总是一个表示当前循环的执行次数的整数计数器。 这个计数器是从1开始的,所以在第一次循环时 forloop.counter 将会被设置为1。
forloop.counter0 类似于 forloop.counter ,但是它是从0计数的。 第一次执行循环时这个变量会被设置为0。
forloop.revcounter 是表示循环中剩余项的整型变量。 在循环初次执行时 forloop.revcounter 将被设置为序列中项的总数。
最后一次循环执行中,这个变量将被置1
forloop.revcounter0 类似于 forloop.revcounter ,但它以0做为结束索引。 在第一次执行循环时,该变量会被置为序列的项的个数减1。
forloop.first 是一个布尔值,如果该迭代是第一次执行,那么它被置为````
forloop.last 是一个布尔值;在最后一次执行循环时被置为True
forloop.parentloop 是一个指向当前循环的上一级循环的 forloop 对象的引用(在嵌套循环的情况下)
2、ifequal/ifnotequal 比较两个变量的值并且显示一些结果
完整格式:
{% ifequal a b %}
<h1>Site News</h1>
{% else %}
<h1>No News Here</h1>
{% endifequal %}
3、注释
单行注释
{# This is a comment #}
多行注释
{% comment %}
This is a
multi-line comment.
{% endcomment %}
四、过滤器
模板过滤器是在变量被显示前修改它的值的一个简单方法。 过滤器使用管道字符。
{{ name|lower }}
显示的内容是变量 {{ name }} 被过滤器 lower 处理后的结果,它功能是转换文本为小写。
过滤管道可以被* 套接* ,既是说,一个过滤器管道的输出又可以作为下一个管道的输入,如此下去。 下面的例子实现查找列表的第一个元素并将其转化为大写。
{{ my_list|first|upper }}
有些过滤器有参数。 过滤器的参数跟随冒号之后并且总是以双引号包含。 例如:
{{ bio|truncatewords:"30" }}
这个将显示变量 bio 的前30个词。
以下几个是最为重要的过滤器的一部分。
addslashes : 添加反斜杠到任何反斜杠、单引号或者双引号前面。 这在处理包含JavaScript的文本时是非常有用的。
date : 按指定的格式字符串参数格式化 date 或者 datetime 对象, 范例:
{{ pub_date|date:"F j, Y" }}
length : 返回变量的长度。 对于列表,这个参数将返回列表元素的个数。 对于字符串,这个参数将返回字符串中字符的个数。 你可以对列表或者字符串,或者任何知道怎么测定长度的Python 对象使用这个方法(也就是说,有 __len__() 方法的对象)。
五、理念与局限
业务逻辑应该和表现逻辑相对分开 。我们将模板系统视为控制表现及表现相关逻辑的工具,仅此而已。 模板系统不应提供超出此基本目标的功能。
语法不应受到 HTML/XML 的束缚
假定设计师精通 HTML 编码 。
Django 要求模板创作人员对直接编辑 HTML 非常熟悉。
目标并不是要发明一种编程语言 。目标是恰到好处地提供如分支和循环这一类编程式功能,这是进行与表现相关判断的基础。
六、在视图中使用模板
1、老的方式
from django.template import Template, Context
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
with open('/data/codedir/mysite/mysite/templates/mytemplate.html') as fp:Template(fp.read())
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
2、设置模板加载的目录
在项目下setting.py里面设置的TEMPLATE_DIRS路径即为模板搜索路径。该设置告诉Django的模板加载机制在哪里查找模板。
默认设置是一个空元组(tuple)
TEMPLATE_DIRS = (
'/data/codedir/mysite/mysite/templates',
)
如果使用的是 Windows 平台,请包含驱动器符号并使用Unix风格的斜杠(/)而不是反斜杠.
TEMPLATE_DIRS = (
'C:/www/django/templates',
)
一般在Django项目中创建一个templates目录保存模板。
# mkdir /data/codedir/mysite/mysite/templates
用Python代码来动态构建 TEMPLATE_DIRS 的内容
import os.path
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
)
3、django.template.loader.get_template()方式 模板加载
用了函数django.template.loader.get_template(),而不是手动从文件系统加载模板.该get_template()函数以模板名称为参数,
在文件系统中找出模块的位置,打开文件并返回一个编译好的Template对象。
get_template()方法会自动为你连接已经设置的TEMPLATE_DIRS目录和你传入该法的模板名称参数.
改进代码:
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)
4、render_to_response() 方式 再次改进
一次性地载入某个模板文件,渲染它,然后将此作为 HttpResponse返回。
render_to_response()函数 第一个参数必须是要使用的模板名称。 如果要给定第二个参数,那么该参数必须是为该模板创建 Context 时所使用的字典。
(注意是Context列表里字典,不是Context列表)如果不提供第二个参数, render_to_response() 使用一个空字典。
再次改进后的代码
from django.shortcuts import render_to_response
#直接python进交互模式无法导入ImportError: Settings cannot be imported, because environment variable DJANGO_SETTINGS_MODULE is undefined.
## ./manage.py shell 这样才能进去调试
import datetime
def current_datetime(request):
now = datetime.datetime.now()
return render_to_response('current_datetime.html', {'current_date': 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())#locals()就是每次调用视图时产生的current_date和对应值的字典
在本例中, locals() 并没有带来多 大 的改进,但是如果有多个模板变量要界定而你又想偷懒,这种技术可以减少一些键盘输入。
注意:locals() 时要注意是它将包括 所有 的局部变量,它们可能比你想让模板访问的要多。
6、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 模板标签和模板继承
1、include 模板标签
内建模板标签: {% include %} 。该标签允许在(模板中)包含其它的模板的内容。 标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串。 每当在多个模板中出现相同的代码时,就应该考虑是否要使用 {% include %} 来减少重复。
{% include 'nav.html' %}
变量 template_name 的值为名称的模板内容:
{% include template_name %}
如果{% include %}标签指定的模板没找到,Django将会在下面两个处理方法中选择一个:
如果 DEBUG 设置为 True ,你将会在 Django 错误信息页面看到 TemplateDoesNotExist 异常。
如果 DEBUG 设置为 False ,该标签不会引发错误信息,在标签位置不显示任何东西。
2、模板继承
在整个网站中,如何减少共用页面区域(比如站点导航)所引起的重复和冗余代码?
继承和include区别用一句话概括就是,继承解决不同页面的重复内容,include解决同一页面内相同内容,相对来说include的使用其实比较少
Django 解决此类问题的首选方法是使用更加优雅的策略—— 模板继承 。
本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载。
定义一个top模板
# vi mysite/templates/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>
修改 current_datetime.html
# vi mysite/templates/current_datetime.html
{% extends "base.html" %} #继承基本模板
{% block title %}The current time{% endblock %}
{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}
hours_ahead 视图创建一个模板
# vi mysite/templates/hours_ahead.html
{% extends "base.html" %}
{% block title %}Future time{% endblock %}
{% block content %}
<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
{% endblock %}
说明
{% extends %} 标签:模板引擎立即装载其父模板,即本例中的 base.html 。
{% block %} 标签:用子模板的内容替换这些 block.父模板中{% block title %}{% endblock %}和{% block content %}{% endblock %}、
中间没有内容,需要子模块去填充。如果子模板没填充则显示为空
父模板 {% block %} 标签中的内容总是被当作一条退路
3、继承的一种常见方式是下面的三层法
可以根据需要使用任意多的继承次数。 使用继承的一种常见方式是下面的三层法:
创建 base.html 模板,在其中定义站点的主要外观感受。 这些都是不常修改甚至从不修改的部分。
为网站的每个区域创建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。这些模板对 base.html 进行拓展,并包含区域特定的风格与设计。
为每种类型的页面创建独立的模板,例如论坛页面或者图片库。 这些模板拓展相应的区域模板。
4、模板继承的一些诀窍:
1)如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。 否则,模板继承将不起作用。
2)一般来说,基础模板中的 {% block %} 标签越多越好。
记住,子模板不必定义父模板中所有的代码块,因此你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行(重)定义。
俗话说,钩子越多越好。
3)如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的某个 {% block %} 中。
4)如果你需要访问父模板中的块的内容,使用 {{ block.super }}这个标签吧,这一个魔法变量将会表现出父模板中的内容。
如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了。
5)不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因为block 标签的工作方式是双向的。 也就是说,block
标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充的内容。如果模板中出现了两个相同名称的 {% block %}
标签,父模板将无从得知要使用哪个块的内容。
6){% extends %} 对所传入模板名称使用的加载方法和 get_template() 相同。 也就是说,会将模板名称被添加到 TEMPLATE_DIRS 设置之后。3
7)多数情况下, {% extends %} 的参数应该是字符串,但是如果直到运行时方能确定父模板名,这个参数也可以是个变量。
这使得你能够实现一些很酷的动态功能。
将上章的两个views使用模块重写
1 1、修改views.py 2 注释掉为使用locals() 3 # vim mysite/views.py 4 from django.http import HttpResponse,Http404 5 from django.shortcuts import render_to_response 6 import datetime 7 8 def hello(request): 9 return HttpResponse('Hello world') 10 11 def current_datetime(request): 12 #current_date = datetime.datetime.now() 13 #return render_to_response('current_datetime.html',locals()) 14 now = datetime.datetime.now() 15 return render_to_response('current_datetime.html',{'current_date':now}) 16 17 def hours_ahead(request,offset): 18 try: 19 offset=int(offset) 20 #hour_offset=int(offset) 21 except ValueError: 22 raise Http404() 23 #next_time = datetime.datetime.now() + datetime.timedelta(hours=hour_offset) 24 now = datetime.datetime.now() + datetime.timedelta(hours=offset) 25 #return render_to_response('hours_ahead.html',locals()) 26 return render_to_response('hours_ahead.html',{'hour_offset':offset,'next_time':now}) 27 2、设置基本模块 28 #vim mysite/templates/base.html 29 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> 30 <html lang="en"> 31 <head> 32 <title>{% block title %}{% endblock %}</title> 33 </head> 34 <body> 35 <h1>My helpful timestamp site</h1> 36 {% block content %}{% endblock %} 37 {% block footer %} 38 <hr> 39 <p>Thanks for visiting my site.</p> 40 {% endblock %} 41 </body> 42 </html> 43 3、子模板current_datetime.html和hours_ahead.html 44 #vim mysite/templates/current_datetime.html 45 {% extends "base.html" %} 46 47 {% block title %}The current time{% endblock %} 48 49 {% block content %} 50 <p>It is now {{ current_date }}.</p> 51 {% endblock %} 52 #vim mysite/templates/hours_ahead.html 53 {% extends "base.html" %} 54 55 {% block title %}Future time{% endblock %} 56 57 {% block content %} 58 <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> 59 {% endblock %}