概述
如果你已经阅读过上一个章节,那么你应该已经完成了充分的准备工作并且创建了一个很简单的具有如下文件结构的Web应用:
microblog
|-flask文件夹
|-<一些虚拟环境的文件>
|-app文件夹
| |-static文件夹
| |-templates文件夹
| |-__init__.py文件
| |-views.py文件
|-tmp文件夹
|-run.py文件
亲,想要运行这个程序么?那就运行这个run.py文件,然后在你的浏览器里边打开http://localhost:5000这个地址.
我们在后面的章节会不断地从前一章节结束的地方继续开发我们的应用,所以你要确保你的环境已经正确安装,并且你的应用能够正常运行.
为什么我们需要使用模板系统
让我们来考虑一下,我们接下来怎么样扩展我们的小程序.
我们要在微博应用里边实现一个非常基本的功能,在首页上面显示一个欢迎已登录用户的标题.暂且忽略当前应用里边没有用户的状况,我将会在稍后解决这个问题.
要显示一个美观大方的标题,最简单的方法就是改变我们提供视图的方式来显示一些HTML代码,不如这样子:\
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
from app import app @app .route( '/' ) @app .route( '/index' ) def index(): user = { 'nickname' : 'Miguel' } # fake user return ''' <html> <head> <title>Home Page</title> </head> <body> <h1>Hello, ''' + user['nickname '] + ' ''< / h1> < / body> < / html> |
现在,在你的浏览器里面刷新一下看看,是不是很爽?
因为我们这个小程序还支持用户功能,所以咱用了一个用户占位对象,通常它被亲切的称呼为假数据或测试数据。它可以让我们关注程序中急需解决的部分。
爽完了,面对实际吧,我希望你会觉得上面这个混合着html代码的小程序相当恶心。想想看,如果你用一些动态内容生成的一个复杂的HTML页面,并且想要在由这样的程序组成的网站中改变一些页面内容,这将会一件非常蛋疼的事情。
模板系统拯救世界
如果保持业务逻辑和表现的分离,你的网站结构将会组织的更好。不要不信,如果你用python完成了业务代码,你甚至可以请一个网站设计师帮你完成剩下的部分。模板系统就是帮你实现业务和表现分离的。
让我们完成第一个模板(fileapp/templates/index.html):
1
2
3
4
5
6
7
8
|
< html > < head > < title >{{title}} - microblog</ title > </ head > < body > < h1 >Hello, {{user.nickname}}!</ h1 > </ body > </ html > |
如你所见,我们只是写了一个普通的HTML页面,唯一跟HTML有区别的就是里面掺杂了一些以 {{ ... }} 组成的动态内容占位符。
现在让我们来看看视图函数中是如何处理这个模板的(fileapp/views.py):
1
2
3
4
5
6
7
8
9
10
|
from flask import render_template from app import app @app .route( '/' ) @app .route( '/index' ) def index(): user = { 'nickname' : 'Miguel' } # fake user return render_template( "index.html" , title = 'Home' , user = user) |
测试这段程序的重点就是看看模板系统是如何工作的。你可以比较浏览器中渲染后的html页面源码与模板文件源码之间的区别。
在上面的程序中,我们从 Flask 框架 import 了一个叫 render_template 的新函数,并用这个函数来渲染模板。并给这个函数赋予了模板文件名和一些变量作为参数。它将导入的变量替换掉模板中的变量占位符,并返回渲染后的模板。
让我们了解的更深入点。在 Flask 底层,render_template 函数实际上是调用了 Flask 的一个组件: Jinja2 模板处理引擎。是 Jinjia2 用导入的变量替换掉了模板中对应的 {{ ... }} 代码块。
模板中的流程控制
Jinja2 模板系统还支持流程控制语句,我们来尝试一下在模板中添加一个 if 流程控制语句 (fileapp/templates/index.html):
1
2
3
4
5
6
7
8
9
10
11
12
|
< html > < head > {% if title %} < title >{{title}} - microblog</ title > {% else %} < title >Welcome to microblog</ title > {% endif %} </ head > < body > < h1 >Hello, {{user.nickname}}!</ h1 > </ body > </ html > |
现在我们的模板文件有点智能了。如果我们在视图函数中忘了定义页面标题变量 title,它将会使用自已的标题替代。把视图函数中 render_template 里的 title 变量取消试试,看看这个 if 流程语句是如何工作的。
模板中使用循环
也许用户想在他的主页上展示好友最近写的文章,有点像人人,或者新浪微博那样的好友动态,接下来我们就要看看如何来完成这个功能。
首先,创建用户和文章 (fileapp/views.py):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def index(): user = { 'nickname' : 'Miguel' } # fake user posts = [ # fake array of posts { 'author' : { 'nickname' : 'John' }, 'body' : 'Beautiful day in Portland!' }, { 'author' : { 'nickname' : 'Susan' }, 'body' : 'The Avengers movie was so cool!' } ] return render_template( "index.html" , title = 'Home' , user = user, posts = posts) |
用数组存储用户的文章,每一个数组元素都是一个字典,如上代码所示,这个dict的key是author和body,用来存储文章的作者和文章内容。当我们决定使用数据库来存储这些信息时,这个字典的key可以隐射为表的一个字段,这里为了给大家演示模板的使用,没有使用数据库相关的技术,简单地使用字典和数组模拟用户和他的好友最近发表的文章。
我们的模板文件现在有了一个新问题。我们刚刚创建了一个包含用户文章的内容数据,这个数组可能包含任意数量的文章。怎样才能让模板根据这个数组的数量自动渲染内容。
要解决这个问题,就需要一个新的流程控制语句:for循环。让我们来把 for循环添加到模板文件 (fileapp/templates/index.html)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
< html > < head > {% if title %} < title >{{title}} - microblog</ title > {% else %} < title >microblog</ title > {% endif %} </ head > < body > < h1 >Hi, {{user.nickname}}!</ h1 > {% for post in posts %} < p >{{post.author.nickname}} says: < b >{{post.body}}</ b ></ p > {% endfor %} </ body > </ html > |
很简单吧,你还可以在数组中添加更多的内容看看效果。
模板继承
接来下,我们需要给这个微博(microblog)添加一个导航菜单,里面包含 修改个人资料,退出登录 等类似的链接。
直接在 index.html 模板文件中直接加入这个导航条也是可行的,但是当我们有很多模板文件都包含这个导航条时,就会陷入为了修改导航条的某个地方而不得不在多个文件中往返编辑的尴尬境地。当包含这个导航条的文件越来越多时,你想死的心就会有了。
我们可以使用 Jinja2 的模板继承功能,它可以使我们把模板中一些公共的内容组合在一起创建一个基础模板,然后让其它模板继承这个基础模板。
我们先来定义一个基础模板,里面包含了导航条和那个最开始写的关于页面标题(title)的流程控制语句。(fileapp/templates/base.html):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
< html > < head > {% if title %} < title >{{title}} - microblog</ title > {% else %} < title >microblog</ title > {% endif %} </ head > < body > < div >Microblog: < a href = "/index" >Home</ a ></ div > < hr > {% block content %}{% endblock %} </ body > </ html > |
在这个模板中,我们使用了 block 控制语句来定义继承模板内容的显示位置。注意:这个 block 语句中设置的名称必须唯一。
接下来让我们修改一下 index.html 模板,让它继承于刚刚添加的基础模板 base.html (fileapp/templates/index.html):
1
2
3
4
5
6
7
|
{ % extends "base.html" % } { % block content % } <h1>Hi, {{user.nickname}}!< / h1> { % for post in posts % } <div><p>{{post.author.nickname}} says: <b>{{post.body}}< / b>< / p>< / div> { % endfor % } { % endblock % } |
基础模板 base.html 帮我们搞定了页面结构和公共内容,所以这个 index.html 模板就成了这幅衰样了。extends 语句使两个模板关联了起来。Jinja2 在渲染 index.html 模板时,发现 extends 语句,就会自动先引入 base.html 基础模板,并对两个模板中名为 content 的 block 语句进行匹配。Jinja2知道如何把两个模板合并到一起。我们以后创建新的模板时,同样也会使用这种从基础模板继承的方法。
结束语
如果你想节省时间,懒得敲代码,可以从以下地址下载本章内容的示例代码:
下载地址:microblog-0.2.zip