Django模板修炼
引言:由于我们在使用Django框架时,不会将HTML代码采用硬编码的方式,因为会有以下缺点:
1 1:对页面设计进行的任何改变都必须对 Python 代码进行相应的修改。 站点设计的修改往往比底层 Python代码的修改要频繁得多,因此如果可以在不进行 Python 代码修改的情况下变更设计,那将会方便得多。 2 3 2:Python 代码编写和 HTML 设计是两项不同的工作,大多数专业的网站开发环境都将他们分配给不同的人员(甚至不同部门)来完成。 设计者和HTML/CSS的编码人员不应该被要求去编辑Python的代码来完成他们的工作。 4 5 3:程序员编写 Python代码和设计人员制作模板两项工作同时进行的效率是最高的,远胜于让一个人等待另一个人完成对某个既包含 Python又包含 HTML 的文件的编辑工作。
基于这些原因,将页面的设计和Python的代码分离开会更干净简洁更容易维护。 我们可以使用 Django的模板系统 (Template System)来实现这种模式。
模板系统的基本知识
1 模板是一个文本,用于分离文档的表现形式和内容。 模板定义了占位符以及各种用于规范文档该如何显示的各部分基本逻辑(模板标签)。 模板通常用于产生HTML,但是Django的模板也能产生任何基于文本格式的文档。
首先从一个使用模板的HTML页面开始了解Django模板:
1 1:用两个大括号括起来的文字(例如 {{ person_name }} )称为 变量(variable) 。这意味着在此处插入指定变量的值。 如何指定变量的值呢? 稍后就会说明。 2 3 2:被大括号和百分号包围的文本(例如 {% if ordered_warranty %} )是 模板标签(template tag) 。标签(tag)定义比较明确,即: 仅通知模板系统完成某些工作的标签。 4 5 3:这个例子中的模板包含一个for标签( {% for item in item_list %} )和一个if 标签({% if ordered_warranty %} ) 6 7 4:for标签类似Python的for语句,可让你循环访问序列里的每一个项目。 if 标签,正如你所料,是用来执行逻辑判断的。 在这里,tag标签检查ordered_warranty值是否为True。如果是,模板系统将显示{% if ordered_warranty %}和 8 {% else %}之间的内容;否则将显示{% else %}和{% endif %}之间的内容。{% else %}是
可选的。 9 10 5:最后,这个模板的第二段中有一个关于filter过滤器的例子,它是一种最便捷的转换变量输出格式的方式。如这个例子中的{{ship_date|date:”F j, Y” }},我们将变量ship_date传递给date过滤器,同时指定参数”F,j,Y”。date过滤器 11 根据参数进行格式输出。 过滤器是用管道符(|)来调用的,具体可以参见Unix管道符。
如何使用模板系统
创建模板对象
创建一个 Template 对象最简单的方法就是直接实例化它。 Template 类就在 django.template 模块中,构造函数接受一个参数,原始模板代码。 让我们深入挖掘一下 Python的解释器看看它是怎么工作的。
转到project目录(在第二章由 django‐admin.py 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这个环境变量。
注:当你创建一个 Template 对象,模板系统在内部编译这个模板到内部格式,并做优化,做好 渲染的准备。 如果你的模板语法有错误,那么在调用 Template() 时就会抛出 TemplateSyntaxError 异常。
出现TemplateSyntaxError 异常的几种情况:
1:无效的tags 2:标签的参数无效 3:无效的过滤器 4:过滤器的参数无效 5:无效的模板语法 6:未封闭的块标签 (针对需要封闭的块标签)
模板渲染
一旦你创建一个 Template 对象,你可以用 context 来传递数据给它。 一个context是一系列变量和它们值的集合。context在Django里表现为 Context 类,在 django.template 模块里。 她的构造函数带有一个可选的参数:
一个字典映射变量和它们的值。 调用 Template 对象 的 render() 方法并传递context来填充模板:
我们必须指出的一点是,t.render(c)返回的值是一个Unicode对象,不是普通的Python字符串。 你可以通过字符串前的u来区分。 在框架中,Django会一直使用Unicode对象而不是普通的字符串。 Django对Unicode支持,将让你的应用程序轻松
地处理各式各样的字符集,而不仅仅是基本的A-Z英文字符。
字典和Contexts
Python的字典数据类型就是关键字和它们值的一个映射。 Context 和字典很类似, Context 还提供更多的功 能。
变量名必须由英文字符开始 (A-Z或a-z)并可以包含数字字符、下划线和小数点。 (小数点在这里有特别的用途,稍后我们会讲到)变量是大小写敏感的。
这就是使用Django模板系统的基本规则: 写模板,创建 Template 对象,创建 Context , 调用 render() 方法。
同一模板,多个上下文
模板属于一次产生,多次使用
Django 模板解析非常快捷。 大部分的解析工作都是在后台通过对简短正则表达式一次性调用来完成。 这和基于 XML 的模板引擎形成鲜明对比,那些引擎承担了 XML 解析器的开销,且往往比 Django 模板渲染引擎要慢上几个数量级。
深度变量查找
在模板中不仅可以传递字符串,还可以传递列表,字典等数据类型。在 Django 模板中遍历复杂数据结构的关键是句点字符 (.)。
假设你要向模板传递一个 Python 字典。 要通过字典键访问该字典的值,可使用一个句点:
同样,也可以通过句点来访问对象的属性。 比方说, Python 的 datetime.date 对象有 year 、 month 和 day几个属性,你同样可以在模板中使用句点来访问这些属性:
自定义一个类,同样可以使用句点号来调用类对象的属性。
点语法也可以用来引用对象的* 方法*。 例如,每个 Python 字符串都有 upper() 和 isdigit() 方法,你在模板中可以使用同样的句点语法来调用它们:
注意:这里调用方法时并* 没有* 使用圆括号 而且也无法给该方法传递参数;你只能调用不需参数的方法。
句点也可用于访问列表索引
注:不允许使用负数列表索引。 像 {{ items.‐1 }} 这样的模板变量将会引发 TemplateSyntaxError 错误。
句点号在使用时,对于几种数据类型调用顺序为:
1:字典类型查找 (比如 foo["bar"] ) 2:属性查找 (比如 foo.bar ) 3:方法调用 (比如 foo.bar() ) 4:列表类型索引查找 (比如 foo[bar] )
句点查找可以多级深度嵌套。
例如在下面这个例子中 {{person.name.upper}} 会转换成字典类型查找(person['name'] ) 然后是方法调用( upper() ):
方法调用行为
方法调用比其他类型的查找略为复杂一点。 以下是一些注意事项:
在方法查找过程中,如果某方法抛出一个异常,除非该异常有一个 silent_variable_failure 属性并且值为 True ,否则的话它将被传播。如果异常被传播,模板里的指定变量会被置为空字符串。
如果要防止模板载入时,避免不想触发函数的执行,可以给方法设置一个特殊的属性。
模板系统不会执行任何以该方式进行标记的方法。 接上面的例子,如果模板文件里包含了{{ account.delete }} ,对象又具有 delete()方法,而且delete() 有alters_data=True这个属性,那么在模板载入时, delete()方法将不会被执行。 它将静静地错误退出。
如何处理无效数据
默认情况下,如果一个变量不存在,模板系统会把它展示为空字符串,不做任何事情来表示失败。
系统静悄悄地表示失败,而不是引发一个异常,因为这通常是人为错误造成的。 这种情况下,因为变量名有错误的状况或名称, 所有的查询都会失败。 现实世界中,对于一个web站点来说,如果仅仅因为一个小的模板语法错误而造成无法访问,这是不可接受的。
了解上下文(context)对象
基本的模板标签和过滤器
标签
if/else
{% if %} 标签检查(evaluate)一个变量,如果这个变量为真(即,变量存在,非空,不是布尔值假),系统会显示在 {% if %} 和 {% endif %} 之间的任何内容,例如:
{% if 条件 %}
执行操作
{% endif %}
{% else %} 标签是可选的:
{% if 条件%}
执行操作1
{% else %}
执行操作2
{% endif %}
拓展:
在Python和Django模板系统中,以下这些对象相当于布尔值的False:
1:空列表([])
2:空元组(())
3:空字典({})
4:空字符串('')
5:零值(0)
6:特殊对象None
7:对象False
8:提示:你也可以在自定义的对象里定义他们的布尔值属性(这个是python的高级用法)。
除以上几点以外的所有东西都视为True。
{% if %} 标签接受 and , or 或者 not 关键字来对多个变量做判断 ,或者对变量取反( not ),例如:
{% if 条件1 and 条件2 %}
执行操作1
{% endif %}
{% if not 条件1 %}
执行操作2
{% endif %}
{% if 条件1 or 条件2 %}
执行操作1
{% endif %}
{% if not 条件1 or 条件2 %}
执行操作1
{% endif %}
{% if 条件1 and not 条件2 %}
执行操作1
{% endif %}
{% if %} 标签不允许在同一个标签中同时使用 and 和 or ,因为逻辑上可能模糊的,例如,如下示例是错误的: 比如这样的代码是不合法的:
{% if 条件1 and 条件2 or 条件3 %} #这是错误示例!!!
系统不支持用圆括号来组合比较操作。 如果你确实需要用到圆括号来组合表达你的逻辑式,考虑将它移到模板之外处理,然后以模板变量的形式传入结果吧。 或者,仅仅用嵌套的{% if %}标签替换吧,就像这样:
{% if 条件1 %}
{% if 条件2 or 条件3 %}
执行操作
{% endif %}
{% endif %}
多次使用同一个逻辑操作符是没有问题的,但是我们不能把不同的操作符组合起来。 例如,这是合法的:
{% if 条件1 or 条件2 or 条件3 or 条件4 %} #这是允许的写法!
并没有 {% elif %} 标签, 请使用嵌套的`` {% if %}`` 标签来达成同样的效果:
{% if 条件1 %} 执行操作1 {% else %} 执行操作2 {% if 条件2 %} 执行操作3 {% endif %} {% endif %}
注:一定要用 {% endif %} 关闭每一个 {% if %} 标签。
for
{% for %} 允许我们在一个序列上迭代。 与Python的 for 语句的情形类似,循环语法是 for X in Y ,Y是要迭代的序列而X是在每一个特定的循环中使用的变量名称。 每一次循环中,模板系统会渲染在 {% for %} 和{% endfor %} 之间的所有内容。
<ul> {% for 元素 in 可迭代对象 %} <li>{{ 元素.属性}}</li> {% endfor %} </ul>
给标签增加一个 reversed 使得该列表被反向迭代:
{% for 元素 in 可迭代对象 reversed %}
....(执行操作)
{% endfor %}
可以嵌套使用 {% for %} 标签(类似于python中的循环嵌套):
{% for 对象1 in 可迭代对象 %} <h1>{{ 对象1.属性}}</h1> <ul> {% for 对象2 in 对象1.属性(集合) %} <li>{{ 对象2}}</li> {% endfor %} </ul> {% endfor %}
在执行循环之前先检测列表的大小是一个通常的做法,当列表为空时输出一些特别的提示。
{% if 条件1 %} {% for 对象 in 可迭代对象 %} <p>{{ 对象 .属性}}</p> {% endfor %} {% else %} 执行操作 {% endif %}
因为这种做法十分常见,所以`` for`` 标签支持一个可选的`` {% empty %}`` 分句,通过它我们可以定义当列表为空时的输出内容 下面的例子与之前那个等价:
{% for 对象 in 可迭代对象 %} 执行操作1 {% empty %} 执行操作2 {% endfor %}
注:Django不支持退出循环操作。 如果我们想退出循环,可以改变正在迭代的变量,让其仅仅包含需要迭代的项目。 同理,Django也不支持continue语句,我们无法让当前迭代操作跳回到循环头部。
在每个`` {% for %}``循环里有一个称为`` forloop`` 的模板变量。这个变量有一些提示循环进度信息的属性。
forloop.counter 总是一个表示当前循环的执行次数的整数计数器。 这个计数器是从1开始的,所以在第一次循环时 forloop.counter 将会被设置为1。
{% for item in demo_list %} <p>{{ forloop.counter }}: {{ item }}</p> {% endfor %}
forloop.counter0 类似于 forloop.counter ,但是它是从0计数的。 第一次执行循环时这个变量会被设置为0。
forloop.revcounter 是表示循环中剩余项的整型变量。 在循环初次执行时 forloop.revcounter 将被设置为序列中项的总数。 最后一次循环执行中,这个变量将被置1。
forloop.revcounter0 类似于 forloop.revcounter ,但它以0做为结束索引。 在第一次执行循环时,该变量会被置为序列的项的个数减1。
forloop.first 是一个布尔值,如果该迭代是第一次执行,那么它被置为True,在下面的情形中这个变量是很有用的:
{% for object in objects %} {% if forloop.first %} <li class="first"> {% else %} <li> {% endif %} {{ object }} </li> {% endfor %}
forloop.last 是一个布尔值;在最后一次执行循环时被置为True。 一个常见的用法是在一系列的链接之间放置管道符(|)
{% for link in links %}
{{ link }}
{% if not forloop.last %}
|
{% endif %}
{% endfor %}
执行结果:Link1 | Link2 | Link3 | Link4
forloop.parentloop 是一个指向当前循环的上一级循环的 forloop 对象的引用(在嵌套循环的情况下)。例子在此:
{% for country in countries %} <table> {% for city in country.city_list %} <tr> <td>Country #{{ forloop.parentloop.counter }}</td> <td>City #{{ forloop.counter }}</td> <td>{{ city }}</td> </tr> {% endfor %} </table> {% endfor %}
注:forloop 变量仅仅能够在循环中使用。 在模板解析器碰到{% endfor %}标签后,forloop就不可访问了。
ifequal|ifnotequal
{% ifequal %} 标签比较两个值,当他们相等时,显示在 {% ifequal %} 和 {% endifequal %} 之中所有的值。
1 {% ifequal section 'sitenews' %} 2 <h1>Site News</h1> 3 {% endifequal %} 4 5 {% ifequal section "community" %} 6 <h1>Community</h1> 7 {% endifequal %}
和 {% if %} 类似, {% ifequal %} 支持可选的 {% else%} 标签:
1 {% ifequal section 'sitenews' %} 2 <h1>Site News</h1> 3 {% else %} 4 <h1>No News Here</h1> 5 {% endifequal %}
只有模板变量,字符串,整数和小数可以作为 {% ifequal %} 标签的参数。下面是合法参数的例子:
{% ifequal variable 1 %} {% ifequal variable 1.23 %} {% ifequal variable 'foo' %} {% ifequal variable "foo" %}
其他任何类型,例如Python的字典类型、列表类型、布尔类型,不能用在 {% ifequal %} 中。
1 {% ifequal variable True %} 2 {% ifequal variable [1, 2, 3] %} 3 {% ifequal variable {'key': 'value'} %}
注释
1 #单行注释 2 {# This is a comment #} 3 4 #多行注释 5 {% comment %} 6 This is a 7 multi‐line comment. 8 {% endcomment %}
过滤器
模板过滤器是在变量被显示前修改它的值的一个简单方法。 过滤器使用管道字符。例如:
{{ name|lower }}
过滤管道可以被* 套接* ,既是说,一个过滤器管道的输出又可以作为下一个管道的输入,如此下去。 下面的例子实现查找列表的第一个元素并将其转化为大写。
1 {{ my_list|first|upper }}
有些过滤器有参数。 过滤器的参数跟随冒号之后并且总是以双引号包含。 例如:
1 {{ bio|truncatewords:"30" }}
还有如下一些过滤器:
addslashes : 添加反斜杠到任何反斜杠、单引号或者双引号前面。 这在处理包含JavaScript的文本时是非常 有用的。 date : 按指定的格式字符串参数格式化 date 或者 datetime 对象, 范例:{{ pub_date|date:"F j, Y" }} length : 返回变量的长度。 对于列表,这个参数将返回列表元素的个数。 对于字符串,这个参数将返回字 符串中字符的个数。
视图中使用模板
示例:
1 from django.shortcuts import render_to_response 2 import datetime 3 4 def current_datetime(request): 5 now = datetime.datetime.now() 6 return render_to_response('current_datetime.html', {'current_date': now})
locals() 技巧
如果你是个喜欢偷懒的程序员并想让代码看起来更加简明,可以利用 Python 的内建函数 l字典对所有局部变量的名称与值进行映射。 因此,前面的视图可以重写成下面这个样子:
1 def current_datetime(request): 2 current_date = datetime.datetime.now() 3 return render_to_response('current_datetime.html', locals())
注:view视图函数中的局部变量一定要和想要渲染的模板名称一致!!!
include 模板标签
{% include %} 。该标签允许在(模板中)包含其它的模板的内容。 标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串。 每当在多个模板中出现相同的代码时,就应该考虑是否要使用 {% include %} 来减少重复。
模板继承
到目前为止,我们的模板范例都只是些零星的 HTML 片段,但在实际应用中,你将用 Django 模板系统来创建整个 HTML 页面。 这就带来一个常见的 Web 开发问题: 在整个网站中,如何减少共用页面区域(比如站点导航)所引起的重复和冗余代码?解决该问题的传统做法是使用 服务器端的 includes ,你可以在 HTML 页面中使用该指令将一个网页嵌入到另一个中。 事实上, Django 通过刚才讲述的 {% include %} 支持了这种方法。 但是用 Django 解决此类问题的首选方法是使用更加优雅的策略—— 模板继承 。本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载。
模板继承实现步骤:
第一步是定义 基础模板 , 该框架之后将由 子模板 所继承。 以下是我们目前所讲述范例的基础模板:
<!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>
这个叫做 base.html 的模板定义了一个简单的 HTML 框架文档,我们将在本站点的所有页面中使用。 子模板的作用就是重载、添加或保留那些块的内容。我们使用一个以前已经见过的模板标签: {% block %} 。 所有的 {% block %} 标签告诉模板引擎,子模板可以重载这些部分。 每个{% block %}标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖。
第二步:在需要继承模板页面首行使用extends引入模板页面,然后使用指定内容替换模板的可变板块。例如:
{% extends "base.html" %} {% block title %}The current time{% endblock %} {% block content %} <p>It is now {{ current_date }}.</p> {% endblock %}
工作方式。 在加载 current_datetime.html 模板时,模板引擎发现了 {% extends %} 标签, 注意到该模板是一个子模板。 模板引擎立即装载其父模板,即本例中的 base.html 。此时,模板引擎注意到 base.html 中的三个 {% block %} 标签,并用
子模板的内容替换这些 block 。因此,引擎将会使用我们在 { block title %} 中定义的标题,对 {% block content %} 也是如此。 所以,网页标题一块将由 {% block title %}替换,同样地,网页的内容一块将由 {% block content %}替换。 注意由于子模板并没有定义 footer 块,模板系统将使用在父模板中定义的值。 父模板 {% block %} 标签中的内容总是被当作一条退路。继承并不会影响到模板的上下文。 换句话说,任何处在继承树上的模板都可以访问到你传到模板中的每一个模板变量。
你可以根据需要使用任意多的继承次数。 使用继承的一种常见方式是下面的三层法: 1. 创建 base.html 模板,在其中定义站点的主要外观感受。 这些都是不常修改甚至从不修改的部分。 2. 为网站的每个区域创建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。这些模 板对 base.html 进行拓展,并包含区域特定的风格与设计。 3. 为每种类型的页面创建独立的模板,例如论坛页面或者图片库。 这些模板拓展相应的区域模板。 这个方法可最大限度地重用代码,并使得向公共区域(如区域级的导航)添加内容成为一件轻松的工作。 以下是使用模板继承的一些诀窍: 如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。 否则,模板继承将不起作 用。
一般来说,基础模板中的 {% block %} 标签越多越好。 记住,子模板不必定义父模板中所有的代码块,因 此你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行(重)定义。 俗话 说,钩子越多越好。 如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的某个 {% block %} 中。 如果你需要访问父模板中的块的内容,使用 {{ block.super }}这个标签吧,这一个魔法变量将会表现出 父模板中的内容。 如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了。 不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因为block 标签的工作方式是双 向的。 也就是说,block 标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充的内容。如果模 板中出现了两个相同名称的 {% block %} 标签,父模板将无从得知要使用哪个块的内容。 {% extends %} 对所传入模板名称使用的加载方法会将模板名称被添加到 TEMPLATE_DIRS 设置之后。 多数情况下, {% extends %} 的参数应该是字符串,但是如果直到运行时方能确定父模板名,这个参数也 可以是个变量。 这使得你能够实现一些很酷的动态功能