《Flask Web 开发》第二版

豆瓣地址:https://book.douban.com/subject/30293851/

第 1 章 - 安装

Flask 哲学

Flask 自开发伊始就被设计为可扩展的框架,它具有一个包含基本服务的强健核心,其他功能则可通过扩展实现。

Flask 有 3 个主要依赖:

  • 路由、调试和 Web 服务器网关接口(WSGI,Web server gateway interface)子系统由 Werkzeug 提供;
  • 模板系统由 Jinja2 提供;
  • 命令行集成由 Click 提供。

开发者可以任意挑选符合项目需求的扩展,甚至可以自行开发。这和大型框架的做法相反,大型框架往往已经替你做出了大多数决定,难以(有时甚至不允许)使用替代方案。

venv

在 Python 3 中由标准库 venv 创建虚拟环境:

$ python3 -m venv virtual-environment-name

激活虚拟环境:

$ source venv/bin/activate

取消:

$ deactivate

另一种使用虚拟环境的方法:

在终端中执行 $ venv/bin/python 直接激活虚拟环境中的交互模式,退出时直接按下 ctrl + D。


第 2 章 - 应用的基本结构

初始化

所有 Flask 应用都必须创建一个应用实例,通常由下述代码创建:

from flask import Flask
app = Flask(__name__)

Web 服务器使用 WSGI 协议,把接受自客户端的所有请求转发给这个对象处理。

路由和视图函数

客户端(浏览器)把请求发送给 Web 服务器,Web 服务器再把请求发送给 Flask 应用实例。

应用实例需要知道对每个 URL 的请求要运行哪些代码,所有保存了一个 URL 到 Python 函数的映射关系。

处理 URL 和函数之间关系的程序称为路由

Flask 使用 app.route 装饰器或者更传统的 app.add_url_rule() 方法构建映射:

@app.route('/user/<name>')
def user(name):
    return '<h1>Hello {}!</h1>'.format(name)
def index():
    return '<h1>Hello World!</h1>'
app.add_url_rule('/', 'index', index)

index() 这样处理入站请求的函数称为视图函数

一个完整的应用

from flask import Flask
app = Flask(__name__)

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

Web 开发服务器

Flask 自带 Web 开发服务器,通过 flask run 命令启动。这个命令在 FLASK_APP 环境变量指定的 Python 脚本中寻找应用实例。

$ export FLASK_APP=hello.py
$ flask run

动态路由

@app.route('/user/<name>')
def user(name):
    return f'<h1>Hello, {name}!</h1>'

调试模式

Flask 应用可以在调试模式中运行,在调试模式下,开发服务器会默认加载重载器调试器

**重载器:**Flask 会监视项目中的所有源码文件,发现变动时自动重启服务器。

**调试器:**调试器是一个基于 Web 的工具,当应用抛出未处理的异常时,它会出现在浏览器中。

$ export FLASK_APP=hello.py
$ export FLASK_DEBUG=1
$ export FLASK_ENV=development (效果同上)
$ flask run

命令行选项

flask shell 命令在应用上下文中打开一个 Python shell 会话,可以运行维护任务、测试、调试问题。

请求-响应循环

上下文

为了避免大量可有可无的参数把视图函数弄得一团糟,Flask 使用上下文临时把某些对象变为全局可访问。在多线程服务器中,Flask 使用上下文让特点的变量在一个线程中全局可访问,与此同时不会干扰其他线程。

变量名 上下文 说明
current_app 应用上下文 当前应用的应用实例
g 应用上下文 处理请求时用作临时存储的对象,每次请求都会重设这个变量
request 请求上下文 请求对象,封装了客户端发出的 HTTP 请求中的内容
session 请求上下文 用户会话,值为一个字典,存储请求之间需要『记住』的值

Flask 请求对象 (request)

属性或方法 说明
form 一个字典,存储请求提交的所有表单字段
args 一个字典,存储通过 URL 查询字符串传递的所有参数
values 一个字典,form 和 args 的合集
cookies 一个字典,存储请求的所有 cookies
headers 一个字典,存储请求的所有 HTTP 首部
files 一个字典,存储请求上传的所有文件
get_data() 返回请求主体缓冲的数据
get_json() 返回一个 Python 字典,包含解析请求主体后得到的 JSON
blueprint 处理请求的 Flask 蓝本的名称;蓝本在第 7 章介绍
endpoint 处理请求的 Flask 端点的名称;Flask 把视图函数的名称用作路由端点的名称
scheme URL 方案(http 或 https)
is_secure() 通过安全的连接(HTTPS)发送请求时返回 True
host 请求定义的主机名,如果客户端定义了端口号,还包括端口号
path URL 的路径部分
query_string URL 的查询字符串部分,返回原始二进制值
full_path URL 的路径和查询字符串部分
url 客户端请求的完整 URL
base_url 同 url,但没有查询字符串部分
remote_addr 客户端的 IP 地址
environ 请求的原始 WSGI 环境字典

请求钩子

有时在处理请求之前或之后执行代码会很有用,但为了避免每个视图函数中都重复编写代码,Flask 提供了注册通用函数的功能,通过装饰器实现。

装饰器 说明
before_request 注册一个函数,在每次请求之前运行。
before_first_request 注册一个函数,只在处理第一个请求之前运行。可以通过这个钩子添加服务器初始化任务。
after_request 注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。
teardown_request 注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行。

在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量 g

响应

如果视图函数返回的响应需要使用不同的状态码,可以把数字代码作为第二个返回值。

Flask 视图函数还可以返回一个响应对象——make_response()

重定向,由于使用频繁,Flask 提供了 redirect() 辅助函数。


第 3 章 - 模板

Jinja2 模板引擎

渲染模板

Flask 提供的 render_template() 函数把 Jinja2 模板引擎集成到了应用中。

变量

模板中使用 {{ name }} 结构表示一个变量,支持复杂类型如列表、字典、对象。

支持过滤器:{{ name|capitalize }}

支持结构控制、宏(类似函数)。

支持模板继承:{% extends "bootstrap/base.html" %}{{ super() }}

初始化扩展

bootstrap = Bootstrap(app)

# 第 7 章中大型应用初始化扩展的方式:
bootstrap = Bootstrap()
def create_app(config_name):
  # ...
  bootstrap.init_app(app)
  # ...

自定义错误页面

使用 @app.errorhandler(404) 装饰器。

链接

url_for() 辅助函数帮助在模板中构建正确的 URL。

用法:

# 以视图函数名作为参数,以 app.add_url_route() 定义路由时,使用端点名
url_for('index') # /

# 绝对地址
url_for('index', _external=True) # http://localhost:5000/

# 动态 URL
url_for('user', name='john', page=2, version=1) # /user/john?page=2&version=1

第 4 章 - Web 表单

表单

使用 Flask-WTF 定义表单:

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired

class NameForm(FlaskForm):
    name = StringField('What is your name?', validators=[DataRequired()])
    submit = SubmitField('Submit')

使用 Flask-Bootstrap 提供的高层级辅助函数 quick_form() 渲染 Flask-WTF 表单:

{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}

在视图函数中处理表单:

@app.route('/', methods=['GET', 'POST'])
def index():
    name = None
    form = NameForm()
    if form.validate_on_submit():
        name = form.name.data
        form.name.data = ''
    return render_template('index.html', form=form, name=name)

Post / Redirect / Get 模式

Post / Redirect / Get 简称 PRG,是一种用来防止表单重复提交数据的一种 Web 设计模式。

闪现消息

flaks.flash 的简单使用。


第 5 章 数据库

Flask-SQLAlchemy 简单配置 SQLite 数据库

import os
from flask_sqlalchemy import SQLAlchemy

basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

模型 (一对多)

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship('User', backref='role')

    def __repr__(self):
        return '<Role %r>' % self.name


class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    role_id = db.column(db.Integer, db.ForeignKey('roles.id'))

    def __repr__(self):
        return '<User %r>' % self.username

数据库操作 (flask shell)

创建表

>>> from hello import db
>>> db.create_all()
# 如果数据库文件已经存在,暴力删除再重新生成:
>>> db.drop_all()
>>> db.create_all()

插入行

>>> from hello import Role,User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> user_role = Role(name='User')
>>> user_john = User(username='john', role=admin_role)
>>> user_susan = User(username='susan', role=user_role)
>>> user_david = User(username='david', role=user_role)
# 数据库会话(事务)
>>> db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david])
>>> db.session.commit()
# 数据库会话支持回滚
>>> db.session.rollback()

修改行

>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()

删除行

>>> db.session.delete(mod_role)
>>> db.session.commit()

查询行

# 基本 all()
>>> Role.query.all()
[<Role 'Administrator'>, <Role 'User'>]
>>> User.query.all()
[<User 'john'>, <User 'susan'>, <User 'david'>]

# 过滤器
>>> User.query.filter_by(role=user_role).all()
[<User 'susan'>, <User 'david'>]

# 查看生成的原生 SQL 语句
>>> str(User.query.filter_by(role=user_role))
'SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id \nFROM users \nWHERE ? = users.role_id'

# 如果中途退出 shell 会话,加载示例:
user_role = Role.query.filter_by(name='User').first()

# 关闭自动执行查询
>>> user_role.users
[<User 'susan'>, <User 'david'>]
# 在模型中加入 lazy='dynamic' 参数
users = db.relationship('User', backref='role', lazy='dynamic')
# 可以使用过滤器了
>>> user_role.users.order_by(User.username).all()
[<User 'david'>, <User 'susan'>]

集成 Python shell

通过 app.shell_context_processor 装饰器,在每次启动 flask shell 会话时自动导入数据库示例和模型:

@app.shell_context_processor
def make_shell_context():
    return dict(db=db, User=User, Role=Role)

第 6 章 电子邮件

使用 Flask-Mail 提供电子邮件支持,Python 的 smtplib 库不支持 OAuth2 验证方法,需要设置 Google 账户的「允许不够安全的应用的访问权限」。

配置:

app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[前缀]'
app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <xxx@gmail.com>'
app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN')

将敏感信息写入环境变量:

$ export MAIL_USERNAME=xxx@gmail.com       
$ export MAIL_PASSWORD=xxxxxxxx               
$ export FLASKY_ADMIN=xxx@gmail.com # 接收邮件的管理员

集成邮件发送功能:

from flask_mail import Mail, Message
mail = Mail(app)
def send_email(to, subject, template, **kwargs):
    msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
                  sender=app.config['FLASKY_MAIL_SENDER'],
                  recipients=[to])
    msg.body = render_template(template + '.txt', **kwargs)
    msg.html = render_template(template + '.html', **kwargs)
    mail.send(msg)

编写视图函数:

# ...
if app.config['FLASKY_ADMIN']:
    send_email(app.config['FLASKY_ADMIN'], 'New User', 'mail/new_user', user=user)
# ...

第 7 章 大型应用的结构

提供一个可供中大型程序使用的应用结构

项目结构:

.
├── app/
│   ├── /__init__.py  
│   ├── /email.py
│   ├── /models.py
│   ├── /static/
│   ├── /templates/
│   ├── /main/
│      ├── /__init__.py
│      ├── /errors.py	
│      ├── /forms.py
│      ├── /views.py
├── migrations/
├── tests/
└── venv/
├── config.py
├── flasky.py
├── requirements.txt

编写配置模块,配合工厂函数可以创建多个实例,便于测试。

Flask 提供蓝本(blueprint)功能,蓝本中定义的路由和错误处理程序处于休眠状态,直到蓝本注册到应用上后才生效。

定义相关的环境变量启动 Flask:

$ export FLASKY_ADMIN=xxx@xxx.com
$ export MAIL_USERNAME=xxx@xxx.com
$ export MAIL_PASSWORD=xxx

$ export FLASK_APP=flasky.py
$ export FLASK_DEBUG=1

$ flask run

需求文件 requirements.txt

用 pip 生成 requirements.txt 文件:

$ pip freeze >requirements.txt

安装或升级包后,需手动更新。

其他人利用 requirements.txt 文件创建副本,先创建好虚拟环境,执行以下命令:

$ pip install -r requirements.txt

蓝本

Flask 之旅

如何理解Flask中的蓝本

其他

后续章节就是实战了,代码量太多了,没有做笔记了。。。