岁月轻狂

【Flask Web开发:基于Python的Web应用开发实战】读书笔记

前言

Flask 有两个主要依赖:路由、调试和 Web 服务器网关接口( Web Server Gateway Interface,WSGI)子系统由Werkzeug(http://werkzeug.pocoo.org/)提供;模板系统由 Jinja2( http://jinja.pocoo.org/)提供。 Werkzeug 和 Jinjia2 都是由 Flask 的核心开发者开发而成。

Flask 并不原生支持数据库访问、Web表单验证和用户认证等高级功能。这些功能以及其他大多数Web程序中需要的核心服务都以扩展的形式实现,然后再与核心包集成。开发者可以任意挑选符合项目需求的扩展,甚至可以自行开发。这和大型框架的做法相反,大型框架往往已经替你做出了大多数决定,难以(有时甚至不允许)使用替代方案。

第一章

1.1 使用虚拟环境

1
$ sudo apt-get install python-virtualenv

1
2
3
$ git clone https://github.com/miguelgrinberg/flasky.git
$ cd flasky
$ git checkout 1a
1
$ virtualenv venv 新建虚拟环境
1
$ source venv/bin/activate 激活
1
$ deactivate 退出虚拟环境
1
(venv) $ pip install flask 安装flask

第二章

程序的基本结构

初始化

1
2
from flask import Flask
app = Flask(__name__)

将构造函数的 name 参数传给 Flask 程序,这一点可能会让 Flask 开发新手心
生迷惑。 Flask 用这个参数决定程序的根目录,以便稍后能够找到相对于程
序根目录的资源文件位置。

路由和视图函数

客户端(例如 Web 浏览器)把请求发送给Web服务器,Web服务器再把请求发送给 Flask程序实例。程序实例需要知道对每个URL请求运行哪些代码,所以保存了一个 URL 到Python函数的映射关系。处理URL和函数之间关系的程序称为路由

1
2
3
@app.route('/')
def index():
return '<h1>Hello World!</h1>'

在浏览器中访问 http://www.example.com 后, 会触发服务器执行 index() 函数。这个函数的返回值称为响应.像 index() 这样的函数称为视图函数( view function)。视图函数返回的响应可以是包含HTML的简单字符串,也可以是复杂的表单.

启动服务器

1
2
if __name__ == '__main__'
app.run(debug=True)

一个完整的程序

1
2
3
4
5
6
7
8
9
10
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
@app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s!</h1>' % name
if __name__ == '__main__':
app.run(debug=True)

请求响应循环

为了避免大量可有可无的参数把视图函数弄得一团糟,Flask使用上下文临时把某些对象变为全局可访问

线程是可单独管理的最小指令集。进程经常使享内存或文件句柄等资源。 多线程 Web 服务器程池中选择一个线程用于处理接收到的请求。

程序收到客户端发来的请求时,要找到处理该请求的视图函数。为了完成这个任务, Flask会在程序的 URL 映射中查找请求的 URL。 URL 映射是 URL 和视图函数之间的对应关系。

URL 映射中的 HEAD、 Options、 GET 是请求方法,由路由进行处理。Flask 为每个路由都指定了请求方法, 这样不同的请求方法发送到相同的 URL 上时,会使用不同的视图函数进行处理。

有时在处理请求之前或之后执行代码会很有用。例如,在请求开始时,我们可能需要创建数据库连接或者认证发起请求的用户。为了避免在每个视图函数中都使用重复的代码,Flask提供了注册通用函数的功能,注册的函数可在请求被分发到视图函数之前或之后调用。请求钩子使用修饰器实现。 Flask 支持以下 4 种钩子。

• before_first_request:注册一个函数,在处理第一个请求之前运行。
• before_request:注册一个函数,在每次请求之前运行。
• after_request:注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。
• teardown_request:注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行

Flask 扩展

lask 的开发 Web 服务器支持很多启动设置选项,但只能在脚本中作为参数传给 app.run()函数。这种方式并不十分方便,传递设置选项的理想方式是使用命令行参数。Flask-Script是一个Flask扩展,为Flask程序添加了一个命令行解析器。 Flask-Script自带了一组常用选项,而且还支持自定义命令。

第三章

如,用户在网站中注册了一个新账户。用户在表单中输入电子邮件地址和密码,然后点击提交按钮。服务器接收到包含用户输入数据的请求,然后Flask 把请求分发到处理注册请求的视图函数。这个视图函数需要访问数据库,添加新用户,然后生成响应回送浏览器。这两个过程分别称为业务逻辑和表现逻辑。
把业务逻辑和表现逻辑混在一起会导致代码难以理解和维护。假设要为一个大型表格构建HTML 代码, 表格中的数据由数据库中读取的数据以及必要的 HTML 字符串连接在一起。把表现逻辑移到模板中能够提升程序的可维护性。
模板是一个包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为渲染。为了渲染模板,Flask使用了一个名为 Jinja2 的强大模板引擎。

Jinja2模板引擎

形式最简单的 Jinja2 模板就是一个包含响应文本的文件

渲染模板

默认情况下, Flask 在程序文件夹中的 templates 子文件夹中寻找模板。

1
2
3
4
5
6
7
8
9
hello.py: 渲染模板
from flask import Flask, render_template
# ...
@app.route('/')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)

Flask 提供的 render_template 函数把 Jinja2 模板引擎集成到了程序中。 render_template 函数的第一个参数是模板的文件名。随后的参数都是键值对,表示模板中变量对应的真实值。

变量

Jinja2 能识别所有类型的变量,甚至是一些复杂的类型,例如列表、字典和对象。
可以使用过滤器修改变量,过滤器名添加在变量名之后,中间使用竖线分隔。

控制结构

在模板中使用条件控制语句:

1
2
3
4
5
{% if user %}
Hello, {{ user }}!
{% else %}
Hello, Stranger!
{% endif %}

如何使用 for 循环实现模板中渲染一组元素:

1
2
3
4
5
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>

inja2 还支持宏,类似函数:

1
2
3
4
5
6
7
8
{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<ul>
{% for comment in comments %}
{{ render_comment(comment) }}
{% endfor %}
</ul>

另一种重复使用代码的强大方式是模板继承,它类似于Python代码中的类继承。

使用Flask-Bootstrap集成Twitter Bootstrap

Bootstrap( http://getbootstrap.com/)是Twitter开发的一个开源框架,它提供的用户界面组件可用于创建整洁且具有吸引力的网页,而且这些网页还能兼容所有现代 Web浏览器。Bootstrap是客户端框架,因此不会直接涉及服务器。服务器需要做的只是提供引用了Bootstrap 层 叠 样 式 表(CSS) 和 JavaScript 文 件 的 HTML 响 应, 并 在 HTML、 CSS 和JavaScript 代码中实例化所需组件。这些操作最理想的执行场所就是模板。

安装Flask扩展:

1
(venv) $ pip install flask-bootstrap

自定义错误页面

链接

在模板中直接编写简单路由的URL链接不难,但对于包含可变部分的动态路由,在模板中构建正确的URL就很困难。而且,直接编写URL会对代码中定义的路由产生不必要的依赖关系。如果重新定义路由,模板中的链接可能会失效。
了避免这些问题, Flask 提供了 url_for() 辅助函数,它可以使用程序 URL 映射中保存的信息生成URL。url_for()函数最简单的用法是以视图函数名(或者 app.add_url_route()定义路由时使用的端点名)作为参数,返回对应的 URL。

静态文件

使用Flask-Moment本地化日期和时间

安装Flask-Moment

1
(venv) $ pip install flask-moment

第四章 Web表单

安装Flask-WTF及其依赖(此扩展可以把处理 Web 表单的过程变成一
种愉悦的体验。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
(venv) $ pip install flask-wtf
```python
### 跨站请求伪造保护
### 表单类
### 重定向和用户会话
默认情况下,用户会话保存在客户端 cookie 中,使用设置的 SECRET_KEY 进行加密签名。 如果篡改了cookie中的内容,签名就会失效,会话也会随之
失效。
### Flash消息
请求完成后,有时需要让用户知道状态发生了变化。这里可以使用确认消息、警告或者错误提醒。一个典型例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上面显示一个消息,提示用户用户名或密码错误。
flash()
### 第5章
数据库按照一定规则保存程序数据, 程序再发起查询取回所需的数据。 Web 程序最常用基于关系模型的数据库,这种数据库也称为 SQL 数据库, 因为它们使用结构化查询语言。不过最近几年文档数据库和键值对数据库成了流行的替代选择, 这两种数据库合称 NoSQL数据库。
### SQL数据库
关系型数据库把数据存储在表中,表模拟程序中不同的实体。例如,订单管理程序的数据库中可能有表customers、products和orders。表的列数是固定的,行数是可变的。列定义表所表示的实体的数据属性。例如,customers表中可能有name、address、phone等列。表中的行定义各列对应的真实数据。
表中有个特殊的列,称为主键,其值为表中各行的唯一标识符。表中还可以有称为外键的列,引用同一个表或不同表中某行的主键。行之间的这种联系称为关系,这是关系型数据库模型的基础。
### NoSQL数据库
集合代替表,文档代替记录
### 使用SQL还是NoSQL
SQL 数据库擅于用高效且紧凑的形式存储结构化数据。这种数据库需要花费大量精力保证数据的一致性。NoSQL数据库放宽了对这种一致性的要求,从而获得性能上的优势。
### Python数据库框架
选择数据库框架时,你要考虑很多因素。
- 易用性
- 性能
- 可移植性
- Flask集成度
### 使用Flask-SQLAlchemy管理数据库
```python
(venv) $ pip install flask-sqlalchemy

定义模型

模型这个术语表示程序使用的持久化实体。 在 ORM 中,模型一般是一个 Python 类,类中的属性对应数据库表中的列。Flask-SQLAlchemy创建的数据库实例为模型提供了一个基类以及一系列辅助类和辅助函数,可用于定义模型的结构。

关系

关系型数据库使用关系把不同表中的行联系起来。

数据库操作

  1. 创建表
  2. 插入行
  3. 修改行
  4. 删除行
  5. 查询行

在视图函数中操作数据库

集成Python shell

使用Flask-Migrate实现数据库迁移

  1. 创建迁移仓库
  2. 创建迁移脚本
  3. 更新数据库

第6章 电子邮件

很多类型的应用程序都需要在特定事件发生时提醒用户,而常用的通信方法是电子邮件。

使用Flask-Mail提供电子邮件支持

1
(venv) $ pip install flask-mail

在程序中集成发送电子邮件功能

为了避免每次都手动编写电子邮件消息,我们最好把程序发送电子邮件的通用部分抽象出来,定义成一个函数。 这么做还有个好处,即该函数可以使用 Jinja2 模板渲染邮件正文,灵活性极高。

异步发送电子邮件

为了避免处理请求过程中不必要的延迟,我们可以把发送电子邮件的函数移到后台线程中。
程序要发送大量电子邮件时,使
用专门发送电子邮件的作业要比给每封邮件都新建一个线程更合适。例如,我们可以把执行 send_async_email()函数的操作发给Celery(http://www.celeryproject.org/)任务队列。

第七章 大型程序的结构

结构

• Flask 程序一般都保存在名为 app 的包中;
• migrations 文件夹包含数据库迁移脚本;
• 单元测试编写在 tests 包中;
• venv 文件夹包含 Python 虚拟环境。
• requirements.txt 列出了所有依赖包,便于在其他电脑中重新生成相同的虚拟环境;
• config.py 存储配置;
• manage.py 用于启动程序以及其他的程序任务。

程序包

程序包用来保存程序的所有代码、模板和静态文件。我们可以把这个包直接称为 app(应用),如果有需求, 也可使用一个程序专用名字。 templates 和 static 文件夹是程序包的一部分,因此这两个文件夹被移到了 app 中。 数据库模型和电子邮件支持函数也被移到了这个包中,分别保存为app/models.py 和 app/email.py。

使用程序工厂函数

在单个文件中开发程序很方便,但却有个很大的缺点,因为程序在全局作用域中创建,所以无法动态修改配置。运行脚本时,程序实例已经创建,再修改配置为时已晚。这一点对单元测试尤其重要,因为有时为了提高测试覆盖度,必须在不同的配置环境中运行程序。这个问题的解决方法是延迟创建程序实例, 把创建过程移到可显式调用的工厂函数中。这种方法不仅可以给脚本留出配置程序的时间,还能够创建多个程序实例,这些实例有时在测试中非常有用。

在蓝本中实现程序功能

转换成程序工厂函数的操作让定义路由变复杂了。在单脚本程序中,程序实例存在于全局作用域中,路由可以直接使用app.route修饰器定义。但现在程序在运行时创建,只有调用create_app()之后才能使用app.route修饰器,这时定义路由就太晚了。和路由一样,自定义的错误页面处理程序也面临相同的困难,因为错误页面处理程序使用 app.errorhandler 修饰器定义。
幸好 Flask 使用蓝本提供了更好的解决方法。 蓝本和程序类似,也可以定义路由。不同的是,在蓝本中定义的路由处于休眠状态, 直到蓝本注册到程序上后,路由才真正成为程序的一部分。使用位于全局作用域中的蓝本时,定义路由的方法几乎和单脚本程序一样。和程序一样, 蓝本可以在单个文件中定义,也可使用更结构化的方式在包中的多个模块中创建。为了获得最大的灵活性,程序包中创建了一个子包,用于保存蓝本。

1
2
3
from flask import Blueprint
main = Blueprint('main', __name__)
from . import views, errors

启动脚本

需求文件

程序中必须包含一个requirements.txt文件,用于记录所有依赖包及其精确的版本号。如果要在另一台电脑上重新生成虚拟环境,这个文件的重要性就体现出来了,例如部署程序时使用的电脑。pip可以使用如下命令自动生成这个文件:

1
(venv) $ pip freeze >requirements.txt

如果你要创建这个虚拟环境的完全副本,可以创建一个新的虚拟环境,并在其上运行以下命令:

1
(venv) $ pip install -r requirements.txt

单元测试

unittest 包

创建数据库

重组后的程序和单脚本版本使用不同的数据库。首选从环境变量中读取数据库的 URL, 同时还提供了一个默认的SQLite数据库做备用。3种配置环境中的环境变量名和 SQLite数据库文件名都不一样。例如,在开发环境中,数据
库 URL 从环境变量DEV_DATABASE_URL中读取,如果没有定义这个环境变量,则使用名为data-dev.sqlite 的 SQLite 数据库。
不管从哪里获取数据库URL,都要在新数据库中创建数据表。如果使用Flask-Migrate 跟踪迁移,可使用如下命令创建数据表或者升级到最新修订版本:

1
(venv) $ python manage.py db upgrade

mindthink wechat
扫一扫,关注我的微信公众号!