Flask进阶篇

Flask基础进阶

1. Flask的ORM模型

访问关系数据库的传统方式是,拼接 SQL 语句,但存在一些问题如:随着项目越来越大,通过拼接 SQL 语句访问数据库有繁琐易错,SQL 语句重复利用率低,直接使用 SQL 语句存在有 Web 安全漏洞的问题。Flask的ORM 模型定义了关系数据库和对象的映射关系,使得访问数据库的代码简单清晰、易于维护。


1.1 对象关系映射(ORM)

ORM 通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中

图片描述

  • 关系数据库中的表对应于面向对象中的类;
  • 关系数据库中的数据行(记录)对应于面向对象中的对象;
  • 关系数据库中的字段对应于面向对象中的属性。

1.2 SQLAlchemy

SQLAlchemy 是 Python 中一个通过 ORM 操作数据库的框架。SQLAlchemy 对象关系映射器提供了一种方法,用于将用户定义的 Python 类与数据库表相关联,并将这些类实例与其对应表中的行相关联。

图片描述

SQLAlchemy 的核心架构中,Schema / Types 定义了类到表之间的映射规则。DBAPI 是访问关系数据库的底层接口,底层接口仍然通过 SQL 语句访问关系数据库。SQLAlchemy 支持多种关系数据库 (Oracle, Postgresql, Mysql),Dialect 根据用户的配置,调用不同的数据库底层访问 API,并执行对应的 SQL 语句。

1.3 使用SQLAlchemy

安装相关库

1
2
3
pip install flask
pip install pymysql
pip install flask-sqlalchemy

创建 SQLAlchemy 对象

model.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

user = 'root'
password = '123456'
database = 'flask_db' # 数据库及表需要自己创建,后续使用模块自动生成
uri = 'mysql+pymysql://%s:%s@localhost:3306/%s' % (user, password, database)
app.config['SQLALCHEMY_DATABASE_URI'] = uri
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

class Student(db.Model):
__tablename__ = 'students'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
age = db.Column(db.Integer)

常用的字段类型如下表所示:

字段类 说明
db.Integer 整型
db.String (size) 字符串,size 为最大长度,比如 db.String(20)
db.Text 长文本
db.DateTime 时间日期,Python datetime 对象
db.Float 浮点数
db.Boolean 布尔值

1.4 面向对象的方式操作数据库

创建数据库表

1
2
db.drop_all()   # 删除数据库 school 中的所有表格
db.create_all() # 创建已经建立映射关系的表。

增加

1
2
3
4
5
6
7
8
zhangsan = Student(id = 1, name = 'zhangsan', age = 12)
db.session.add(zhangsan) # 将单个实例加入到数据库连接会话中
db.session.commit() # 提交保存到数据库。

lisi = Student(id = 2, name = 'lisi', age = 11)
wangwu = Student(id = 3, name = 'wangwu', age = 11)
db.session.add_all([lisi, wangwu]) # 将多个实例批量加入到数据库连接会话中
db.session.commit()

查询

<模型类>.query.<过滤方法(可选)>.<查询方法>

1
2
3
4
5
6
7
from model import Student  # 导入模型类
Student.query.all() # 获取 Student 模型的所有记录,返回包含多个模型类实例的列表
Student.query.filter_by(age=18) # 获取 age 字段值为 18 的记录
Student.query.filter_by(age=18).first() # 获取 Student 模型,查询第一个符合条件的数据
Student.query.count() # 获取 Student 模型所有记录的数量
Student.query.get(1) # 获取主键值为 1 的记录
Student.query.filter(Student.age==18).first() # 等同于上面的查询,但使用不同的过滤方法

常用的过滤方法:

过滤方法 说明
filter() 使用指定的规则过滤记录,返回新产生的查询对象
filter_by() 使用指定规则过滤记录(以关键字表达式的形式),返回新产生的查询对象
order_by() 根据指定条件对记录进行排序,返回新产生的查询对象
group_by() 根据指定条件对记录进行分组,返回新产生的查询对象

常用的查询方法:

查询方法 说明
all() 返回包含所有查询记录的列表
first() 返回查询的第一条记录,如果未找到,则返回 None
get(id) 传入主键值作为参数,返回指定主键值的记录,如果未找到,则返回 None
count() 返回查询结果的数量
first_or_404() 返回查询的第一条记录,如果未找到,则返回 404 错误响应
get_or_404(id) 传入主键值作为参数,返回指定主键值的记录,如果未找到,则返回 404 错误响应
paginate() 返回一个 Pagination 对象,可以对记录进行分页处理

更新

1
2
3
4
5
6
7
8
student = Student.query.get(1)  # 获取主键值为 1 的记录
student.name = 'zhan' # 直接对实例属性赋予新的值即可
student.age = 20
db.session.commit() # 注意仍然需要调用这一行来提交改动
# or
students = Student.query.filter_by(name = 'lisi') # 查找所有名为LISI的对象
students.update({'name':'LISI'})
db.session.commit()

删除

1
2
3
4
5
6
7
student = Student.query.get(1)
db.session.delete(student) # 使用 db.session.delete() 方法删除记录,传入模型实例
db.session.commit() # 提交改动
# or
students = Student.query.filter_by(name = 'LISI') # 查找所有名为LISI的对象
students.delete()
db.session.commit()

1.5 Flask操作Redis数据库

Redis 是是一个高性能的 key-value 数据库,可以有效应对高并发、大数据量访问的难题。

pip install redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/python3
from flask import Flask, render_template, request
import redis

app = Flask(__name__)
db = redis.Redis(host='localhost', decode_responses=True) # decode_responses=True 使 Redis 返回字符串

# 增加与更新
db.set(key, value) # 将单个键值对插入到 Redis 数据库中
db.mset({keyA:valueA, keyB:valueB}) # 将多个键值对插入到 Redis 数据库中

# 查询
keys = db.keys() # db.keys()查询所有键
dict = {}
for key in keys:
value = db.get(key) # db.get(key)获取指定键
dict[key] = value

# 删除
db.delete(key)

# 清空数据库中全部的键值对
db.flushall()

2. Flask表单

2.1 WTForms 和 Flask-WTF

在 Python 的 Web 开发中,WTForms 是一个灵活的表单验证和表单渲染的库,它的主要功能:

  • 验证表单中的字段的取值是否符合要求;
  • 渲染输出表单的 HTML 代码。

WTForms 可以与任意的 Web 框架和模板引擎一起使用。 在 Flask 框架或者 Django 框架中,都可以使用 WTForms。

Flask-WTF 在 WTForms 的基础上提供了一些扩展,可以方便的在 Flask 框架中生成表单。

2.2 表单字段

WTForms 支持如下类型的表单字段:

字段类型 说明
StringField 文本字段
TextAreaField 多行文本字段
PasswordField 密码文本字段
HiddenField 隐藏文本字段
DateField 文本字段,值为datetime.date格式
IntegerField 文本字段,值为整数
DecimalField 文本字段, 值为decimal.Decimal
FloatField 文本字段,值为浮点数
BooleanField 复选框, 值为True 和 False
RadioField 一组单选框
SelectField 下拉列表
FileField 文件上传字段
SubmitField 表单提交按钮

2.3 验证器

WTForms 支持如下类型的表单验证:

验证类型 说明
Email 验证电子邮件地址
EqualTo 比较两个字段的值;常用于要求输入两次秘钥进行确认的情况
Length 验证输入字符串的长度
NumberRange 验证输入的值在数字范围内
DateRequired 确保字段中有数据

2.4 Flask-WTF 使用

1
2
pip install flask-wtf
pip install email-validator
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
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import DataRequired, Length, Email, ValidationError

app = Flask(__name__)
app.config['SECRET_KEY'] = '复杂难猜的字符串~' # ‘SECRET_KEY’ 用于防范 CSRF 攻击

class LoginForm(FlaskForm):
email = StringField(
label = '邮箱',
validators = [
DataRequired(message = '邮箱不能为空'),
Email(message = '请输入正确的邮箱')
]
)

password = PasswordField(
label = '密码',
validators =[
DataRequired(message = '密码不能为空'),
Length(min = 6, message = '密码至少包括 6 个字符')
]
)

submit = SubmitField('登录')

定义类 LoginForm,它继承于 FlaskForm,用于描述登录界面,登录界面是一个表单,包含有 3 个字段:

  • email,显示 label 为 ‘邮箱’,包括 2 个验证器:DataRequired 和 Email,message 参数为验证失败的提示信息;
  • password,显示 label 为 ‘密码’,包括 2 个验证器:DataRequired 和 Length,message 参数为验证失败的提示信息,min = 6 表示密码的最小长度;
  • submit,提交按钮,提交表单给服务端。

form 对象提供了如下方法和属性:

属性 说明
form.validate_on_submit() 表单验证函数,返回值表示验证是否正确
form.email() 显示 email 字段对应的 HTML 代码
form.email.label email 字段的 label
form.email.errors 验证 email 字段的失败提示信息

2.5 自定义验证

在 Flask 中,可以自定义验证器实现特定的验证需求

行内验证器

验证函数的形式为 validate_字段名,在验证字段数据时会调用这个方法来验证对应的字段

1
2
3
4
class LoginForm(FlaskForm):
def validate_password(self, field):
# 验证逻辑
raise ValidationError('异常错误')

全局验证器

自定义验证逻辑函数并将添加到validators列表

1
2
3
4
5
6
7
8
9
10
11
12
13
def password_check(form, field):
# 验证逻辑
raise ValidationError('密码不能全部是数字')

class LoginForm(FlaskForm):
password = PasswordField(
label = '密码',
validators =[
DataRequired(message = '密码不能为空'),
Length(min = 6, message = '密码至少包括 6 个字符'),
password_check
]
)

3. Flask实现文件上传与下载

request对象

request.files['file'],属性files记录上传文件相关信息,使用表单中文件名作为索引

上传文件

1
2
3
f = request.files['file']
path = os.path.join('./upload', f.filename)
f.save(path) # f.save 将文件保存到指定路径 path

上传文件时,表单中需要指定属性 enctype=“multipart/form-data” 表示表单中包含有上传文件的数据需要处理。

下载文件

1
2
return send_from_directory('./upload', filename, as_attachment=True)
# as_attachment=True 将文件作为附件下载

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask, render_template, request, send_from_directory
import os

app = Flask(__name__)

@app.route('/')
def index():
entries = os.listdir('./upload')
return render_template('index.html', entries = entries)

@app.route('/upload', methods=['POST'])
def upload():
f = request.files['file'] # 属性files记录上传文件相关信息,使用表单中文件名作为索引
path = os.path.join('./upload', f.filename)
f.save(path) # f.save 将文件保存到指定路径 path
return render_template('upload.html')

@app.route('/files/<filename>')
def files(filename):
return send_from_directory('./upload', filename, as_attachment=True)

app.run(debug = True)

4. Flask的Cookie和Session机制

4.1 Cookie和Session

Cookie:储存在用户本地终端的数据,用于辨别用户身份、进行会话跟踪。(例如:登录状态、用户名称) ,这些数据被称为 Cookie。

Session:Session 是在服务端保存的一个数据结构,用来存储用户的信息 (例如登录状态、用户名称),Session 数据可以保存在内存、文件或者数据库中。Session 有一个唯一标识 SID (Session ID),对应一个用户,在服务端使用 SID 可以查找到对应用户的数据。

4.2 Flask操作Cookie

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
from flask import Flask, request, Response, render_template
app = Flask(__name__)

# 设置cookie
@app.route('/set_cookie')
def set_cookie():
html = render_template('cookie.html')
response = Response(html)
response.set_cookie('user', 'admin')
return response

# 获取cookie
@app.route('/get_cookie')
def get_cookie():
cookie = request.cookies.get('user')
return render_template('get_cookie.html', cookie = cookie)

# 删除cookie
@app.route('/del_cookie')
def del_cookie():
html = render_template('cookie.html')
response = Response(html)
response.delete_cookie('user')
return response

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

4.3 Flask操作Session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python3
from flask import Flask, session
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
# 使用 Session 存储数据时,Flask 在内部需要进行加密处理

# 设置session
session['user'] # 如果变量 user 不存在,会抛出异常
session.get('user') # 如果变量 user 不存在,返回 None,不会抛出异常

# 获取session
session.get('user')

# 删除session
session.pop('user')

# 清空session
session.clear()

5. Flask钩子函数

在服务端处理请求时,有时需要:在处理请求前执行一些准备工作,在处理请求后执行一些扫尾工作

Flask 提供了钩子函数机制:Flask 给应用程序提供挂载点,在挂载点调用应用程序注册的函数,该函数被称为钩子函数

常用的钩子

Flask 框架中常用的钩子总结如下:

1. before_first_request
在应用程序实例的第一个请求之前要运行的函数,只会运行一次。

2. before_request
在每个请求之前要运行的函数,对每一次请求都会执行一次。

3. after_request
在每个请求之后要运行的函数,对每一次请求都会执行一次。

在 after_request 注册的钩子函数的第一个参数是 response 对象。

4. teardown_request
在每个请求之后要运行的函数,对每一次请求都会执行一次。

在 teardown_request 注册的钩子函数的第一个参数是 exception 对象,指向执行请求时发生的错误。

5. errorhandler
发生一些异常时,比如 HTTP 404、HTTP 500 错误,或者抛出异常,就会自动调用该钩子函数。

都看到这里了,不赏点银子吗^v^