《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
蓝本
其他
后续章节就是实战了,代码量太多了,没有做笔记了。。。