Django模板(基础篇)
将页面的设计和Python的代码分离开会更干净简洁更容易维护。 我们可以使用 Django的 模板系统 (Template System)来实现这种模式,这就是本章要具体讨论的问题。
模板是一个文本,用于分离文档的表现形式和内容。 模板定义了占位符以及各种用于规范文档该如何显示的各部分基本逻辑(模板标签)。 模板通常用于产生HTML,但是Django的模板也能产生任何基于文本格式的文档。
1. 一个简单例子
该模板描述了一个向某个与公司签单人员致谢 HTML 页面。 可将其视为一个格式信函:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<html> <head><title>Ordering notice</title></head> <body> <h1>Ordering notice</h1> <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> {% 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> </body> </html>
代码说明:
- 用两个大括号括起来的文字(例如 {{ person_name }} )称为 变量(variable) 。这意味着在此处插入指定变量的值。
- 被大括号和百分号包围的文本(例如 {% 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 %}是可选的。
- 这个模板的第二段中有一个关于filter过滤器的例子,它是一种最便捷的转换变量输出格式的方式。 如这个例子中的{{ship_date|date:”F j, Y” }},我们将变量ship_date传递给date过滤器,同时指定参数”F j,Y”。date过滤器根据参数进行格式输出。 过滤器是用管道符(|)来调用的,具体可以参见Unix管道符
2. 使用模板系统
先不和视图一起使用,以独立的方式使用,在Python代码中使用Django模板的最基本方式如下:
- 可以用原始的模板代码字符串创建一个 Template 对象, Django同样支持用指定模板文件路径的方式来创建 Template 对象;
- 调用模板对象的render方法,并且传入一套变量context。它将返回一个基于模板的展现字符串,模板中的变量和标签会被context值替换。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> from django import template >>> t = template.Template('My name is {{ name }}.') >>> c = template.Context({'name': 'Adrian'}) >>> print t.render(c) My name is Adrian. >>> c = template.Context({'name': 'Fred'}) >>> print t.render(c) My name is Fred.
创建了Template对象,可以使用context来传递数据给它。一个context是一系列变量和它们值的集合。context在Django里表现为 Context 类,在 django.template 模块里。 她的构造函数带有一个可选的参数: 一个字典映射变量和它们的值。 调用 Template 对象 的 render() 方法并传递context来填充模板:
3. 同一个模板,多个上下文
一旦有了 模板 对象,你就可以通过它渲染多个context, 例如:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> 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
4. 深度变量的查找
模板系统能够非常简洁地处理更加复杂的数据结构,例如list、dictionary和自定义的对象。在 Django 模板中遍历复杂数据结构的关键是句点字符 (.)。比如,假设你要向模板传递一个 Python 字典。 要通过字典键访问该字典的值,可使用一个句点:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> from django.template import Template, Context >>> person = {'name': 'Sally', 'age': '43'} >>> t = Template('{{ person.name }} is {{ person.age }} years old.') >>> c = Context({'person': person}) >>> t.render(c) u'Sally is 43 years old.'
同样,也可以通过句点来访问对象的属性。 比方说, Python 的 datetime.date 对象有 year 、 month 和 day 几个属性,你同样可以在模板中使用句点来访问这些属性:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> 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) u'The month is 5 and the year is 1993.'
句点也可用于访问列表索引,例如:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> 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.'
注意:不允许使用负数列表索引。 像 {{ items.-1 }} 这样的模板变量将会引发`` TemplateSyntaxError``。
5. 如何处理无效变量
默认情况下,如果一个变量不存在,模板系统会把它展示为空字符串,不做任何事情来表示失败。 例如:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> from django.template import Template, Context >>> t = Template('Your name is {{ name }}.') >>> t.render(Context()) u'Your name is .' >>> t.render(Context({'var': 'hello'})) u'Your name is .' >>> t.render(Context({'NAME': 'hello'})) u'Your name is .' >>> t.render(Context({'Name': 'hello'})) u'Your name is .'
6.上下文(context)对象
多数时间,你可以通过传递一个完全填充(full populated)的字典给 Context() 来初始化 上下文(Context) 。 但是初始化以后,你也可以使用标准的Python字典语法(syntax)向``上下文(Context)`` 对象添加或者删除条目。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> 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'
7. 模板标签
-
if/else
{% if %} 标签检查(evaluate)一个变量,如果这个变量为真(即,变量存在,非空,不是布尔值假),系统会显示在 {% if %} 和 {% endif %} 之间的任何内容,例如:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% 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 %}
-
for
{% for %} 允许我们在一个序列上迭代。 与Python的 for 语句的情形类似,循环语法是 for X in Y ,Y是要迭代的序列而X是在每一个特定的循环中使用的变量名称。 每一次循环中,模板系统会渲染在 {% for %} 和 {% endfor %} 之间的所有内容。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<ul> {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %} </ul>
也可以嵌套使用 {% for %} 标签:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% for athlete in athlete_list %} <h1>{{ athlete.name }}</h1> <ul> {% for sport in athlete.sports_played %} <li>{{ sport }}</li> {% endfor %} </ul> {% endfor %}
`` for`` 标签支持一个可选的`` {% empty %}`` 分句,通过它我们可以定义当列表为空时的输出内容:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% for athlete in athlete_list %} <h1>{{ athlete.name }}</h1> <ul> {% for sport in athlete.sports_played %} <li>{{ sport }}</li> {% endfor %} </ul> {% endfor %}
Django不支持退出循环操作。 如果我们想退出循环,可以改变正在迭代的变量,让其仅仅包含需要迭代的项目。 同理,Django也不支持continue语句,我们无法让当前迭代操作跳回到循环头部。
8. 注释
就像HTML或者Python,Django模板语言同样提供代码注释。 注释使用 {# #} :
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{# This is a comment #}
如果要实现多行注释,可以使用`` {% comment %}`` 模板标签,就像这样:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% comment %} This is a multi-line comment. {% endcomment %}
9. 过滤器
就象本章前面提到的一样,模板过滤器是在变量被显示前修改它的值的一个简单方法。 过滤器使用管道字符,如下所示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{{ name|lower }} # 显示的内容是变量 {{ name }} 被过滤器 lower 处理后的结果,它功能是转换文本为小写。 {{ my_list|first|upper }} # 过滤管道可以被* 套接* ,既是说,一个过滤器管道的输出又可以作为下一个管道的输入,如此下去。 下面的例子实现查找列表的第一个元素并将其转化为大写。 {{ bio|truncatewords:"30" }} # 这个将显示变量 bio 的前30个词。 {{ pub_date|date:"F j, Y" }} # date : 按指定的格式字符串参数格式化 date 或者 datetime 对象
10. 视图中使用模板
(1)配置settings.py, 修改 TEMPLATES的DIRS值,如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(os.path.dirname(__file__), 'templates').replace('\\', '/')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
(2)views.py代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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})
(3)前端模板页面放到templates目录下,这个和上面TEMPLATES里面DIRS目录定义的路径是一致的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>time</title> </head> <body> <p>It is now {{ current_date }}.</p> </body> </html>
11. locals()技巧
locals() 可以传入函数执行到该时间点时所定义的一切变量。这算是一种偷懒的方法~~。例如上面的例子可以改写如下代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def current_datetime(request): now = datetime.datetime.now() return render_to_response('current_datetime.html', {'current_date': locals()})
如前面 视图中使用模板 的例子,如果把所有模板都放到一个目录下,那么可能会让事情变得难以掌控,会考虑把模板存放分好类的子目录下。那么get_template()提供了这种方式的实现。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
t = get_template('dateapp/current_datetime.html') # render_to_response()对get_template()做了封装,使用方式如下: return render_to_response('dateapp/current_datetime.html', {'current_date': now})
12 include模板标签
内建模板标签: {% include %} 。该标签允许在(模板中)包含其它的模板的内容。 标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串。 每当在多个模板中出现相同的代码时,就应该考虑是否要使用 {% include %} 来减少重复。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 下面这两个例子都包含了 nav.html 模板。这两个例子是等价的,它们证明单/双引号都是允许的 {% include 'nav.html' %} {% include "nav.html" %} # 下面的例子包含了 includes/nav.html 模板的内容 {% include 'includes/nav.html' %} # 下面的例子包含了以变量 template_name 的值为名称的模板内容 {% include template_name %}
13 模板继承
在实际应用中,你将用 Django 模板系统来创建整个 HTML 页面。 这就带来一个常见的 Web 开发问题: 在整个网站中,如何减少共用页面区域(比如站点导航)所引起的重复和冗余代码?解决该问题的传统做法是使用 服务器端的 includes ,你可以在 HTML 页面中使用该指令将一个网页嵌入到另一个中。 事实上, Django 通过刚才讲的 {% include %} 支持了这种方法。 但是用 Django 解决此类问题的首选方法是使用更加优雅的策略—— 模板继承 。
本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点公用部分和定义块进行重载。
(1)编写基础框架模板 templates/base.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <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 框架文档,我们将在本站点的所有页面中使用。 子模板的作用就是重载、添加或保留那些块的内容。 (如果你一直按顺序学习到这里,保存这个文件到你的templates目录下,命名为 base.html .)我们使用一个以前已经见过的模板标签: {% block %} 。 所有的 {% block %} 标签告诉模板引擎,子模板可以重载这些部分。 每个{% block %}标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖。
(2)编写子模板
可以修改 current_datetime.html 模板,代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% extends "base.html" %} {% block title %}The current time{% endblock %} {% block content %} <p>It is now {{ current_date }}.</p> {% endblock %}
再为 hours_ahead 视图创建一个模板,看起来是这样的:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% extends "base.html" %} {% block title %}Future time{% endblock %} {% block content %} <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> {% endblock %}
关于使用模板继承的一些技巧:
- 如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。 否则,模板继承将不起作用。
-
一般来说,基础模板中的 {% block %} 标签越多越好。 记住,子模板不必定义父模板中所有的代码块,因此你可以用合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行(重)定义。 俗话说,钩子越多越好。
-
如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的某个 {% block %} 中。
-
如果你需要访问父模板中的块的内容,使用 {{ block.super }}这个标签吧,这一个魔法变量将会表现出父模板中的内容。 如果只想在上级代码块基础上添加内容,而不是全部重载,该变量就显得非常有用了。
-
不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因为block 标签的工作方式是双向的。 也就是说,block 标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充的内容。如果模板中出现了两个相同名称的 {% block %} 标签,父模板将无从得知要使用哪个块的内容。
-
{% extends %} 对所传入模板名称使用的加载方法和 get_template() 相同。 也就是说,会将模板名称被添加到 TEMPLATE_DIRS 设置之后。
-
多数情况下, {% extends %} 的参数应该是字符串,但是如果直到运行时方能确定父模板名,这个参数也可以是个变量。 这使得你能够实现一些很酷的动态功能。