On this page... (hide)
Flask 结构简单,源码清晰,我觉得很符合 Python 简洁易上手的特点。现在这是我最喜欢的一个 Python Web Framework,目前正在尝试基于 Flask 从小到大,边实现功能边扩展架构,看它对实际业务的支撑情况如何。在尝试过程中,将获得的一些经验在本页面记录和分享。
- Flask 官网首页
- Flask 官方文档
- The Flask Mega-Tutorial:比官方文档特性更为完整的 Tutorial ,覆盖了全文检索、邮件发送、多语言支持等常见 Web 应用需求。
1. Model 部分(SQLAlchemy 为主)
- Flask-SQLAlchemy 文档:比 SQLAlchemy 的完整文档上手更简单,可以参考。
- SQLAlchemy 官方文档
1.1 sqla 如何给 Model 设定默认值
办法1:给这个 Model 所属的每一个实例,在进行初始化的时候给对应属性赋予初始值。
id = db.Column(db.Integer, primary_key=True)
valid = db.Column(db.Boolean, default=False) # 默认值总是 False
方法2:在数据库创建数据库表的时候,在对应数据列标记默认值。这样即使不通过 Python 访问这个数据库,数据列也受这一默认值控制。
id = db.Column(db.Integer, primary_key=True)
valid = db.Column(db.Boolean, server_default='0') # 默认值总是 0,无论是何种数据类型,在这里都要写成字符串格式,类似在写 SQL 语句。
方法3:利用 sqla 的数据库事件监听。例如以下代码将 SomeModel 的主键自增起始位置设为 1234 。
SomeModel.__table__,
"after_create",
DDL("ALTER TABLE %(table)s AUTO_INCREMENT = 1234;").execute_if(dialect=('postgresql', 'mysql'))
)
1.2 sqla 的 sqlalchemy.exc.AmbiguousForeignKeysError 错误
主要由于两个 Model 间有多个 Foreign Key 关联,因此 sqla 无法自动猜出具体 relationship 是使用哪个 Foreign Key,因此需要在 relationship 的 foreign_keys 参数中显示指定。具体参考 sqlalchemy foreign key relationship attributes 。
1.3 两数据表相互有外键指向对方,插入新数据死锁问题(sqlalchemy.exc.CircularDependencyError: Circular dependency detected)
当两个表都有外键指向对方时(例如图片表记录图片的拥有用户,用户表又需要一个头像而指向了这个图片表),插入数据就会出现外键死锁——都要求对方的数据先行准备好,才能在自己的表中插入新行。这就需要先在其中一个表中插入数据,然后在另一个表的数据中插入完成插入后,再通过 update 语句来更新第一个表的外键取值。
这个机制在 sqla 中已经有官方的办法处理,可参考 Rows that point to themselves / Mutually Dependent Rows 以及 Mutable Primary Keys / Update Cascades 章节中的描述。
另外可参考 sqlalchemy.exc.CircularDependencyError: Circular dependency detected 的讨论。
2. 组件使用经验
2.1 Flask-Admin 使用经验
- Flask-Admin 官方文档
- Flask-Admin 源代码路径:Flask-Admin 最详细的文档起始就是它的代码,不算长,还比较容易读。
- ModelView 的详细用法参考 ModelView 和 BaseModelView 的源代码。偶尔需要阅读 AdminModelConverter 作为补充。
浮点数(Float)精度不够怎么办?
Flask-Admin 对数据表 Model 中的 Float 类型,在编辑 Form 中默认只保留小数点之后2位,这经常是不够用的,比如用来保存经纬度的时候损失的精度就会比较严重。解决这个问题有以下几个方面需要考虑:
- Float 类型在有些数据库本身精度就比较差,比如 MySQL 下大约只有小数点后4位(二进制小数其实我这个描述不准确,具体请查询相关文档)。因此首先要在 model 中选择足够高的数据存储格式。比如如果想让 MySQL 使用 Double 类型的数据列,可以在 SQLAlchemy 的 Model 里面这样写:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
db.app = app
db.init_app(app)
class City(db.Model):
id = db.Column(db.Integer, primary_key=True)
longitude = db.Column(db.REAL)
latitude = db.Column(db.REAL) - 完成了上一步,会发现在 Flask-Admin 表单里提交的高精度浮点数可以被正确保存到数据库中,但点击进入编辑状态时,原先高精度的数值还是会被截断成小数点之后2位。因此我们还得调整 Flask-Admin 的表单生成参数,如下一步。
- 查看 Flask-Admin 的源代码,在 flask-admin.contrib.sqla.form.AdminModelConverter 中有这么一个函数
@converts('Numeric', 'Float')
def handle_decimal_types(self, column, field_args, **extra):
places = getattr(column.type, 'scale', 2)
if places is not None:
field_args['places'] = places
return fields.DecimalField(**field_args)class MyReal(db.REAL):
scale = 10
class City(db.Model):
id = db.Column(db.Integer, primary_key=True)
longitude = db.Column(MyReal)
latitude = db.Column(MyReal)
如何给特定字段增加额外的验证检查逻辑
可以利用 ModelView 的 form_args 参数来添加,详细参考问答 Flask-Admin ModelView custom validation?
- 其背后的机制是 Form Validation with WTForms 。
- WTForms 里头的 Validator 都可以组合使用
- 相关使用文档参考 Validators
- 详细 api 文档参考 Validators 。
2.2 Flask-RESTful 使用经验
强制以 utf-8 而不是 Unicode 字符输出
Flask-RESTful 在以 json 格式输出数据时,会把 Unicode 原样输出,而不会自动做编码转换。就可能导致以其他编程语言实现的客户端在数据解析时出现困难。
可参考 Unicode in Flask-Restful API and JSON issue 讨论中的答案解决这个问题。
用 api 签名保护 RESTful api
最常见的保护 RESTful api 的方法是启用 HTTP basic authentication,参考 Designing a RESTful API using Flask-RESTful 以及 RESTful Authentication with Flask。
数据签名可能可以利用 itsdangerous 这个库。
不过我比较喜欢用 Flask-HmacAuth 来实现 api 的非对称签名保护机制。(这个库的历史不是特别长,可能文档存在一些小 bug ,不过代码也很简短,遇到问题很容易解决。)
3. 其他组件备忘
- flask-security:提供最基本的登陆、认证界面、注册验证码、密码找回等功能的组件。权限控制可能还得另外弄。
- Web Service Made Easy (WSME):也是一个创建 Web Service 的支撑库,也提供多协议支持、加密保护等完整的特性。不过我老觉得易用性似乎不如 Flask-RESTful 。。
- 除官方文档外,可参考 Using WSME with Flask microframework 来快速上手。
- Dash:Flask 的交互式可视化呈现工具,可以用来做数据驾驶舱。
- Pyxley:似乎是一个类似的工具,评价似乎比 Dash 略弱。