Flask-WTF
Flask-WTF 是简化了 WTForms 操作的一个第三方库
WTForms 表单的两个主要功能是验证用户提交数据的合法性以及渲染模板,当然也有其他的功能,如 CSRF 保护,文件上传等
安装 Flask-WTF 默认也会安装 WTForms,因此下列命令会安装二个库
pip install flask-wtf # 同时安装 Flask-WTF 和 WTForms
借用 WTForms 表单验证
由于下列文件执行过程出现错误,我们再次下载一个库,执行命令
pip install email_validator
Python 主文件(demo.py)
from flask import Flask, request, render_template
# 用于创建 Flask 实例, 请求 url 参数, 连接 html 文件
from forms import RegisterForm, LoginForm
# 用于表单验证, 从 form.py 文件中导入 registerForm, LoginForm 类
web = Flask(__name__) # 实例化 Flask 对象
@web.route('/') # 关联视图函数(url)
def index(): # 视图函数
return '首页' # 浏览器接收字符串
@web.route('/register/', methods=['GET', 'POST']) # 关联视图函数(url), 指定 url 获取方法为 post 和 get
def register(): # 视图函数
if request.method == 'GET': # 若获取方法为 get
return render_template('register.html') # 连接 html 文件, 要注意此文件的存放位置
else: # 若获取方法为 post
form = RegisterForm(request.form) # 实例化表单类, 并传递参数 request.form(post)
if form.validate(): # 表单执行无错误
return '成功'
else: # 表单执行出错
print(form.errors) # 打印错误
return '失败'
@web.route('/login/', methods=['GET', 'POST']) # 关联视图函数(url), 指定 url 获取方法为 post 和 get
def login(): # 视图函数
if request.method == 'GET': # 若获取方法为 get
return render_template('login.html') # # 连接 html 文件, 要注意此文件的存放位置
else: # 若获取方法为 post
form = LoginForm(request.form) # 实例化表单类, 并传递参数 request.form(post)
if form.validate(): # 表单执行无错误
return '成功'
else: # 表单执行出错
print(form.errors) # 打印错误
return '失败'
if __name__ == '__main__':
web.run(debug=True) # 运行 Flask 实例
Python 表单文件(forms.py)
from wtforms import Form, StringField # 用于创建表单和创建数据类型
from wtforms.validators import Length, EqualTo, Email # 用于创建数据关联
class RegisterForm(Form):
# StringField 是定义字符串类型的方法
username = StringField(validators=[Length(min=3, max=10, message='用户长度不正确')])
# Length 里面定义了该字段 username 长度的最小值和最大值, 以及出错返回信息
password = StringField(validators=[Length(min=3, max=10, message='密码长度不正确')])
# Length 里面定义了该字段 password 长度的最小值和最大值, 以及出错返回信息
password_verify = StringField(validators=[Length(min=3, max=10, message='密码长度不正确'), EqualTo('password', message='两次密码不一致')])
# Length 里面定义了该字段 password_verify 长度的最小值和最大值, 以及出错返回信息, 同时还用 EqualTo 方法和字段 password 进行比较, 如果二个字段值不一样, 则返回错误信息
class LoginForm(Form):
email = StringField(validators=[Email(message='邮箱不正确')])
# 此处使用 Email 方法和 StringField 方法
login.html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
<div>
<span>邮箱:</span><input type="text" name="email">
<!-- 此处 name='email' 主要用和 post 提交过程中数据的传递 -->
<input type="submit" value="提交">
</div>
</form>
</body>
</html>
register.html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
<div>
<span>用户名:</span><input type="text" name="username">
<!-- 此处 name='email' 主要用和 post 提交过程中数据的传递 -->
</div>
<div>
<span>密码:</span><input type="text" name="password">
<!-- 此处 name='password' 主要用和 post 提交过程中数据的传递 -->
</div>
<div>
<span>确认密码:</span><input type="text" name="password_verify">
<!-- 此处 name='password_verify' 主要用和 post 提交过程中数据的传递 -->
</div>
<div>
<input type="submit" value="注册">
</div>
</form>
</body>
</html>
不借用 WTForms 表单的验证过程
Python 主文件(二个 html 和上面的一样)
from flask import Flask, request, render_template
# 用于创建 Flask 实例, 请求 url 参数, 连接 html 文件
web = Flask(__name__) # 实例化 Flask 对象
@web.route('/') # 关联视图函数(url)
def index(): # 视图函数
return '首页' # 浏览器接收字符串
@web.route('/register/', methods=['GET', 'POST']) # 关联视图函数(url), 指定 url 获取方法为 post 和 get
def register(): # 视图函数
if request.method == 'GET': # 若获取方法为 get
return render_template('register.html') # 连接 html 文件, 要注意此文件的存放位置
else: # 若获取方法为 post
# 直接用 request.form.get 方法获取关键字参数
username = request.form.get('username')
password = request.form.get('password')
password_verify = request.form.get('password_verify')
# 验证表单数据的正确性并返回结果
if len(username) < 3 or len(username) > 10:
return '用户名长度不正确'
elif len(password) < 3 or len(password) > 10:
return '密码长度不正确'
elif password != password_verify:
return '二次输入不一致'
else:
return '注册成功'
@web.route('/login/', methods=['GET', 'POST']) # 关联视图函数(url), 指定 url 获取方法为 post 和 get
def login(): # 视图函数
if request.method == 'GET': # 若获取方法为 get
return render_template('login.html') # # 连接 html 文件, 要注意此文件的存放位置
else: # 若获取方法为 post
# 直接用 request.form.get 方法获取关键字参数
email = request.form.get('email')
# 验证表单数据的正确性并返回结果
if '@' in email:
return '登录成功'
else:
return '登录失败'
if __name__ == '__main__':
web.run(debug=True) # 运行 Flask 实例
细讲表单验证
Flask-WTF 第一个功能,就是用表单来做数据验证,现在有一个 forms.py 文件,然后在里面创建一个 RegisterForm 的注册验证表单和 LoginForm 邮箱登录验证表单
from wtforms import Form, StringField # 用于创建表单和创建数据类型
from wtforms.validators import Length, EqualTo, Email # 用于创建数据关联
class RegisterForm(Form):
# StringField 是定义字符串类型的方法
username = StringField(validators=[Length(min=3, max=10, message='用户长度不正确')])
# Length 里面定义了该字段 username 长度的最小值和最大值, 以及出错返回信息
password = StringField(validators=[Length(min=3, max=10, message='密码长度不正确')])
# Length 里面定义了该字段 password 长度的最小值和最大值, 以及出错返回信息
password_verify = StringField(validators=[Length(min=3, max=10, message='密码长度不正确'), EqualTo('password', message='两次密码不一致')])
# Length 里面定义了该字段 password_verify 长度的最小值和最大值, 以及出错返回信息, 同时还用 EqualTo 方法和字段 password 进行比较, 如果二个字段值不一样, 则返回错误信息
class LoginForm(Form):
email = StringField(validators=[Email(message='邮箱不正确')])
# 此处使用 Email 方法和 StringField 方法
其中指定了需要上传的参数,并且指定了验证器,比如 username 的长度应该在 3-10 之间,email 必须要满足邮箱的格式,password 长度必须在 3-10 之间,password_verify 长度必须在 3 ~ 10 之间,并且应该和 password 一样才能通过验证
表单完成之后,接下来写二个 html 文件,特别强调,注意表单中的参数需要和 html 中标签的 name 属性对应!
其一,register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
<div>
<span>用户名:</span><input type="text" name="username">
<!-- 此处 name='email' 主要用和 post 提交过程中数据的传递 -->
</div>
<div>
<span>密码:</span><input type="text" name="password">
<!-- 此处 name='password' 主要用和 post 提交过程中数据的传递 -->
</div>
<div>
<span>确认密码:</span><input type="text" name="password_verify">
<!-- 此处 name='password_verify' 主要用和 post 提交过程中数据的传递 -->
</div>
<div>
<input type="submit" value="注册">
</div>
</form>
</body>
</html>
其二,login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
<div>
<span>邮箱:</span><input type="text" name="email">
<!-- 此处 name='email' 主要用和 post 提交过程中数据的传递 -->
<input type="submit" value="提交">
</div>
</form>
</body>
</html>
最后,视图文件,RegisterForm 传递的是 request.form ,用于初始化,并且判断 form.validate 会返回用户提交的数据是否满足表单的验证
from flask import Flask, request, render_template
# 用于创建 Flask 实例, 请求 url 参数, 连接 html 文件
from forms import RegisterForm, LoginForm
# 用于表单验证, 从 form.py 文件中导入 registerForm, LoginForm 类
web = Flask(__name__) # 实例化 Flask 对象
@web.route('/') # 关联视图函数(url)
def index(): # 视图函数
return '首页' # 浏览器接收字符串
@web.route('/register/', methods=['GET', 'POST']) # 关联视图函数(url), 指定 url 获取方法为 post 和 get
def register(): # 视图函数
if request.method == 'GET': # 若获取方法为 get
return render_template('register.html') # 连接 html 文件, 要注意此文件的存放位置
else: # 若获取方法为 post
form = RegisterForm(request.form) # 实例化表单类, 并传递参数 request.form(post)
if form.validate(): # 表单执行无错误
return '成功'
else: # 表单执行出错
print(form.errors) # 打印错误
return '失败'
@web.route('/login/', methods=['GET', 'POST']) # 关联视图函数(url), 指定 url 获取方法为 post 和 get
def login(): # 视图函数
if request.method == 'GET': # 若获取方法为 get
return render_template('login.html') # # 连接 html 文件, 要注意此文件的存放位置
else: # 若获取方法为 post
form = LoginForm(request.form) # 实例化表单类, 并传递参数 request.form(post)
if form.validate(): # 表单执行无错误
return '成功'
else: # 表单执行出错
print(form.errors) # 打印错误
return '失败'
if __name__ == '__main__':
web.run(debug=True) # 运行 Flask 实例
文件上传
普通上传
Python 主文件
from werkzeug.datastructures import CombinedMultiDict
from froms import UploadForm
from flask import Flask, request, render_template # 用于 Flask 实例, 请求 url, 连接 html
from werkzeug.utils import secure_filename # 用于判断文件名的正确性和规范性
import os # 用于打开文件路径
web = Flask(__name__) # 创建 Flask 实例
@web.route('/') # 连接视图函数
def index(): # 视图函数
return '首页' # 渲染文本
@web.route('/upload/', methods=['GET', 'POST']) # 连接视图函数, 指定数据获取方法为 get/post
def upload(): # 视图函数
if request.method == 'GET': # 如果数据获取方法为 get
return render_template('upload.html') # 连接 html 文件, 注意文件目录
else: # 如果数据获取方法为 post
desc = request.form.get('desc') # 获取 html 文件表单中的 input 标签(name=desc)
# request.files.get 接受文件的形式
# 获取 html 文件表单中的 input 标签(name=image_file)
image_file = request.files.get('image_file') # image_file 接收用 request.files.get 打开的文件
filename = secure_filename(image_file.filename) # secure_filename 用来判断文件名的正确性和规范性
image_file.save(os.path.join('images', filename)) # 保存文件在方法 os.path.join 指定的目录 images 中
return '文件上传成功' # 若上述过程无错误, 返回此字符串
if __name__ == '__main__':
web.run(debug=True, port=9000)
upload.html 文件
enctype='multipart/form-data'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" enctype='multipart/form-data'>
<!-- enctype='multipart/form-data' 用于和 python 文件获取数据关联-->
<table>
<tr>
<td>头像</td>
<td><input type="file" name="image_file"></td>
</tr>
<tr>
<td>描述</td>
<td><input type="text" name="desc"></td>
</tr>
<tr>
<td><input type="submit" value="上传"></td>
</tr>
</table>
</form>
</body>
</html>
使用表单上传
表单文件
from wtforms import Form, FileField, StringField
from wtforms.validators import InputRequired
from flask_wtf.file import FileAllowed, FileRequired
class UploadForm(Form):
image_file = FileField(validators=[FileRequired(), FileAllowed(['jpg', 'gif', 'png'])])
desc = StringField(validators=[InputRequired()])
Python 主文件
from werkzeug.datastructures import CombinedMultiDict
from froms import UploadForm # 导入表单文件 html
from flask import Flask, request, render_template # 用于 Flask 实例, 请求 url, 连接 html
from werkzeug.utils import secure_filename # 用于判断文件名的正确性和规范性
import os # 用于打开文件路径
web = Flask(__name__) # 创建 Flask 实例
@web.route('/') # 连接视图函数
def index(): # 视图函数
return '首页' # 渲染文本
@web.route('/upload/', methods=['GET', 'POST']) # 连接视图函数, 指定数据获取方法为 get/post
def upload(): # 视图函数
if request.method == 'GET': # 如果数据获取方法为 get
return render_template('upload.html') # 连接 html 文件, 注意文件目录
else: # 如果数据获取方法为 post
form = UploadForm(CombinedMultiDict([request.form, request.files]))
if form.validate(): # 执行过程无错误
desc = request.form.get('desc')
# request.files.get 接受文件的形式
image_file = request.files.get('image_file')
filename = secure_filename(image_file.filename)
image_file.save(os.path.join('images', filename))
return '文件上传成功'
else:
print(form.errors)
return '文件上传失败'
if __name__ == '__main__':
web.run(debug=True, port=9000)
upload.html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" enctype='multipart/form-data'>
<!-- enctype='multipart/form-data' 用于和 python 文件获取数据关联-->
<table>
<tr>
<td>头像</td>
<td><input type="file" name="image_file"></td>
</tr>
<tr>
<td>描述</td>
<td><input type="text" name="desc"></td>
</tr>
<tr>
<td><input type="submit" value="上传"></td>
</tr>
</table>
</form>
</body>
</html>
cookie 和 session
cookie
在网站中,http 请求是无状态的,也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户,cookie 的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie 数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了
cookie 存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过 4KB,因此使用 cookie 只能存储一些小量的数据
session
session 和 cookie 的作用有点类似,都是为了存储用户相关的信息,不同的是,cookie 是存储在本地浏览器,session 是存储在服务器,虽然实现不一样,但是他们的目的都是服务器为了方便存储数据的,session 的出现,是为了解决 cookie 存储数据不安全的问题
cookie 和 session 结合使用
web 开发发展至今,cookie 和 session 的使用已经出现了一些非常成熟的方案,在如今的市场或者企业里,一般有两种存储方式
存储在服务端
通过 cookie 存储一个 session_id,然后具体的数据则是保存在 session 中,如果用户已经登录,则服务器会在 cookie 中保存一个session_id,下次再次请求的时候,会把该 session_id 携带上来,服务器根据 session_id 在 session 库中获取用户的 session 数据,就能知道该用户到底是谁,以及之前保存的一些状态信息,这种专业术语叫做 server side session,存储在服务器的数据会更加的安全,不容易被窃取,但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器应对一些 session 信息还是绰绰有余的
将 session 数据加密,然后存储在 cookie 中
这种专业术语叫做 client side session,flask 采用的就是这种方式,但是也可以替换成其他形式
cookie 实例创建过程
如下,我们利用 pycharm 来为 Flask 框架创建 cookie,具体实现过程在视图函数内,具体语法是,用变量接收 Response 实例化的对象,并且传入一个参数,然后使用该变量的 set_cookie 方法,并且接收三个参数,分别是 cookie 名称,cookie 内容及有效时间
from flask import Flask, Response # 用于创建 Flask 实例, 设置 cookie
web = Flask(__name__) # 创建 Flask 实例
@web.route('/') # 连接视图函数
def index(): # 视图函数
res = Response('百度') # 实例化对象, 用变量 res 接收
res.set_cookie('username', 'xiao su', max_age=60*60*8) # max_age 设置的是秒, 此处设置的是 8h
return res # 浏览器渲染字符串 '百度'
if __name__ == '__main__':
web.run(debug=True) # 运行 Flask 实例
session 实例创建过程
如下,我们用 session 来创建一个加密的 cookie,首先需要给 Flask 实例 web,设置密钥为一段随机的字符串,设置其 session 保存时间为 24 h,另外 session 的封装是在视图函数 login 中进行的,语法为 session['名称'] = '内容'
,session 退出语法为 session.clear()
from flask import Flask, session # 用于 Flask 实例和创建 session 实例
from datetime import timedelta # 用于设置 session 存活时间
import os # 用于随机生成一段字符串
web = Flask(__name__) # 用于 Flask 实例
print(os.urandom(5)) # 输出一段长度为 5 的随机字符串
web.config['SECRET_KEY'] = os.urandom(25) # 设置密匙为一段随机生成的字符串
web.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=24) # session 存活时间配置
@web.route('/') # 连接视图函数
def index(): # 视图函数
return '首页' # 渲染文本
@web.route('/login/') # 连接视图函数
def login(): # 视图函数
# 一个字典的形式进行存储的
session['username'] = 'xiao ke'
# session 持久化(时间为 30 days)
session.permanent = True
return '登录页面' # 渲染文本
@web.route('/logout/') # 连接视图函数
def logout(): # 视图函数
# session 清除方式 :clear()
session.clear() # 清除当前 session
return '退出登录' # 渲染文本
if __name__ == '__main__':
web.run(debug=True, port=8000) # 运行 Flask 实例
此处显示的正在使用的 Cookie 在 url 为 / 或 /login/ 时始终只有一个加密的 session 存在
而当我们 url 改为 /logout/ 后,Cookie 则会被清除