在学习这一章之前,我们先谈以下MVC架构(如下图)。MVC架构指的是业务模型(Model),用户界面(View)和控制器(Controller)。这是进行一种软件设计的典范,在web设计的时候,常常采用这种架构。
看看前面第二章的hello.py,在Flask中一般把其中的hello_world()
函数称为视图(view)函数,但是实际上这种称法并不确切。但是这个程序中,hello_world()
函数确实返回了响应的内容,就是 return 'hello world'
。这种做法在实际web开发的时候却会遇到问题,因为真实呈现给用户的网页往往非常复杂,把后端的处理方式(书中称为业务逻辑)和前端呈现给大家的内容(书中称为表现逻辑)混在一起,导致视图函数(一定要记住,Flask中的视图函数,实际上并非MVC中的V,而是属于MVC中的C)非常臃肿,变得难以维护。
简单地说,用户看不见的部分是业务逻辑。而用户看见的部分是表现逻辑。
一个类比:
这里突然想到,进行web开发与组织一场大型晚会很类似,如果80个人计划筹备一场晚会,如果所有人都是幕后工作人员,来指挥如何开展各项工作,所有人也都是演员,都需要上台演出,仓库里的道具大家都可以安排使用。这样在布置舞台,排练节目,分配道具资源的时候,一定会乱成一团。
于是,大家分了一下工,成立了一个以导演为主的管理团队来进行总体负责,另外一部分人成立了演出团队,专门排练节目呈现给观众,还有一部分人负责各项资源的统一管理。这样就形成了一个MVC架构,M就是资源管理团队,管理道具仓库和制定仓库资源调配规则,V就是演员团队,负责把节目呈现给观众,C就是管理团队,根据观众的喜好和反馈安排节目。当然,这个类比并不绝对,因为晚会安排好之后,和观众的互动与web不太一样,但大体上还是很类似的。
那么,实际的Flask中,需要将业务逻辑和表现逻辑进行分离,而呈现给用户端的部分,采用模板来实现。
模板是一个包含响应文本的文件, 其中包含用占位变量表示的动态部分,其具体值只在请求的上下文中才能知道。 使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为渲染(render)。为了渲染模板, Flask 使用了一个名为 Jinja2 的强大模板引擎。
1. Jinja2模板引擎
模板才是MVC架构中真正的View。所谓模板,就是专门负责浏览器能够理解的HTML部分。
那么,你可能会问,我们做好一个模板放在那里调用就可以了,这个Jinga2,这个“神社[じんじゃ]”,这个模板引擎到底是干什么的呢?
从上图中看到,模板引擎就模板文件和数据结合在一起,生成了最终的HTML文档。由此我们可以总结如下:
- 模板文件并不是直接的HTML文档,而只是HTML文档的一个框架,其中也包含了一些地方需要你去添加。
- 但是模板应该包含了你需要反复用到的大部分HTML内容,代码重用是模板的一个主要目的,不然也不能称之为模板了。
- 模板中需要添加的地方,是你需要利用控制器,也就是Flask中的视图函数进行赋值。
- 对模板的赋值,有时候需要依赖一些逻辑,不然模板的功能就会受到极大的限制,因此需要对模板内的一些逻辑语句进行处理
- 根据上面四点需求,模板引擎的主要功能:一是提供渲染模板的规则,二是生成最终的HTML文档。
我想说:模板引擎不生产模板,只是模板的加工匠。
我还想说:模板引擎是模板文件及数据与HTML文档实现之间的一座桥梁。
而在Flask中集成的模板引擎就是jinja2模板引擎,而满足jinja2模板引擎要求的模板就是jinja2模板。
2. 模板的渲染方法
不知道为什么,说到渲染(render)这个词语,总感觉特别高大上。但其实通俗一点的比喻:模板就是一个毛坯房,房型格局都定下来了。渲染就是装修,你可以根据需求进行装修,放置家具,最终可以入住。
那这个装修工就是Flask的视图函数。Flask提供的render_template
函数把Jinja2模板引擎集成到程序中。以如下的代码来示例模板的渲染:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('\')
def index():
return render_template('index.html')
@app.route('\use\')
def user(name):
return render_template('user.html', name=name)
而在这里,视图函数index()
中,通过render_template
函数引入了模板index.html, 而视图函数user(name)
通过render_template
函数引入了模板user.html, 并将值赋给了模板中的变量name
。这里,render_templates
函数的第一个参数是模板的文件名,后面的参数都应该是键值对,表示模板中变量对应的真实值。
此处,这两个模板分别为如下:
- templates/index.html
Hello World!
- templates/user.html
Hello, {{ name }}!
这里我们就来看看模板是如何渲染的。很明显,视图函数index()
直接返回了index.html文件,并没有对模板作任何改动。
而视图函数user(name)
对模板给出了一个数字,就是参数name
,这个参数可以赋值给模板user.html
中的某个变量。我们接着从render_template
函数的第二个参数name=name
可以知道,user.html中存在一个变量名name
,视图函数的输入值被赋给了这个变量。我们看看user.html中的这个变量吧。它的形式是{{ name }}
,在Jinja2模板中,这种{{ name }}
结构表示一个变量,它是一种特殊的占位符,它告诉模板引擎从模板的这个位置插入渲染模板时使用的数据。
Jinja2能识别所有类型的变量,包括列表、字典和对象。示例如下:
A value from a dictionary: {{ mydict['key'] }}.
A value from a list: {{ mylist[3] }}.
A value from a list, with a variable index: {{ mylist[myintvar] }}.
A value from an object's method: {{ myobj.somemethod() }}.
也可以使用过滤器修改变量,如下为使用过滤器的示例:
Hello, {{ name|capitalize }}
Jinja2提供部分常用的过滤器如下表所列。
除了上面所用到的直接对模板占位符处进行赋值,Jinja2还提供了多种控制流程,用来改变模板的渲染过程。这些控制结构包括:
- 条件控制语句,其可以根据视图函数的赋值而返回不同的内容
{% if user %}
Hello, {{ user }}!
{% else %}
Hello, Stranger!
{% endif %}
- 循环控制语句,可以在模板中渲染一组元素。
{% for comment in comments %}
- {{ comment }}
{% endfor %}
- 宏(macro),类似于Python代码中的函数。
{% macro render_comment(comment) %}
{{ comment }}
{% endmacro %}
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
这里解释一下:在上面代码的前三行中,定义了一个宏。这个宏可以看作是python的函数render_comment(comment)
,其作用是在模板中插入comment的值。后面五行中,完成了对这个宏的调用。
另外一点是,macro可能会反复使用到,可以将其保存在单独的文件中,然后在需要的时候导入到模板中。假如macro被保存在macros.html
这个文件中,而在导入的时候,需要用到的语句是{% import 'macros.html' as macros %}
。
- 模板代码片段复用:对于需要多次重复使用的模板代码片,可以单独保存在一个文件中,再包含在所有的模板中,可以避免重复。例如有一个
common.html
的文件,包含了这些重复利用的代码片,在模板中可以通过{% include 'common.html' %}
包括进去。 - 模板继承:类似于python中类的继承。对于一个基类模板,例如
base.html
,如果在新的模板中需要继承这个基类模板,使用到的语句是{% extends "base.html" %}
。
现在对Jinja2模板引擎的控制结构作一下小结如下图:
3. 使用Flask-Bootstrap集成Twitter Bootstrap
Bootstrap是Twitter开发的一个开源框架。如下图LOGO下面的描述:简洁、直观、强悍的前端开发框架,让web开发更迅速、简单。因此,这是一个前端开发框架,不会涉及到服务器。
而这样一个开源框架,提供了前端所用到的一些层叠样式(CSS)和JavaScript文件。而Flask-Bootstrap这个Flask扩展,可以简化我们将Bootstrap集成到程序这个过程。
一个注意的地方,前面也提到过,在示例3-4中,提到的这种导入Flask-Bootstrap扩展的方式是错误的,在python3中,不采用
from flask.ext.bootstrap import Bootstrap
这个语句来导入,而是采用如下方式:
from flask_bootstrap import Bootstrap
# ...
bootstrap = Bootstrap(app)
Flask-Bootstrap的使用方式参考书中的方式应该很好理解,这里就不在赘述。
4. 定义错误页面
常见的错误代码有如下这两个,可以分别自定义错误页面,在对应错误发生时显示该页面。
- 404: 客户端请求未知页面或路由时显示
- 500:有未处理的异常时显示
对于错误页面,继承基模板来显示错误信息,界面更美观。
5. 链接
url_for()
函数可以使用程序URL映射中保存的信息生成URL。其最简单的用法是使用视图函数名为参数,返回对应的URL。
例如:url_for('index')
,会返回视图函数index()
所对应的URL。
url_for()
函数在生成动态地址时,动态部分可以作为关键字参数传入。例如url_for ('user', name='john', _external=True)
这种形式。
传入url_for()
的关键字参数不仅限于动态路由中的参数。函数能将任何额外参数添加到查询字符串中。例如,url_for('index', page=2)
的返回结果是/?page=2
。
6. 静态文件
静态文件:包括网页使用到的图片,JavaScript源码文件和CSS。
默认设置下,Flask 在程序根目录中名为 static 的子目录中寻找静态文件。