Flask-WTF是简化了WTForms操作的一个第三方库。WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。同时还包含一些其他的功能。例如CSRF保护,文件上传等功能,安装flask-wtf也会默认安装WTForms,通过pip方式安装:
pip install flask-wtf
表单验证
1. 自定义一个表单类,继承自wtform.Form类。
2. 定义好需要验证的字段,字段的名字必须和模板中input标签的name字段名字相同
3. 在需要验证的字段上,需要指定好具体的数据类型
4. 在相关的字段上,指定好验证器
5. 在视图中就只需要表单类的对象,进行表单验证。
from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo
# 表单验证类
class RegisterForm(Form):
# 导入验证器
# message为验证错误时抛出的错误信息
username = StringField(validators=[Length(min=3, max=10,
message="用户名长度必须3到10位")])
password = StringField(validators=[Length(min=6, max=10,
message="密码长度必须6到10位")])
repeat_password = StringField(validators=[Length(min=6, max=10, message="密码错误"),
EqualTo("password")])
视图函数的验证:
@app.route('/register/', methods=["GET", "POST"])
def register():
"""
表单验证
:return:
"""
if request.method == "GET":
return render_template("html/register.html")
else:
form = RegisterForm(request.form)
if form.validate():
return "success"
else:
print(form.errors) # 获取具体的错误信息
return "Fail"
WTForm常用的验证器:
Email:验证上传的数据是否为邮箱
EqualTo:验证上传的数据是否和另一个字段相等
InputRequired:原始数据的需要验证,如果不是特殊情况,应该使用InputRequired,即指定这个字段必须是要传的,否则会报错
Length:长度限制
NumberRange:数字的区间
Regexp:自定义正则表达式
URL:必须要是url的形式
UUID:验证uuid
# -*- coding: utf-8 -*-
from wtforms import Form, StringField, IntegerField
from wtforms.validators import Length, EqualTo, InputRequired, Email, NumberRange, URL, UUID, Regexp
# 表单验证类
class RegisterForm(Form):
# 导入验证器
# message为验证错误时抛出的错误信息
username = StringField(validators=[Length(min=3, max=10,
message="用户名长度必须3到10位")])
password = StringField(validators=[Length(min=6, max=10,
message="密码长度必须6到10位")])
repeat_password = StringField(validators=[Length(min=6, max=10, message="密码错误"),
EqualTo("password")])
# 表单验证类
class LoginForm(Form):
# email = StringField(validators=[Email(message="邮箱格式错误")])
username = StringField(validators=[InputRequired(message="用户名不能为空")])
age = IntegerField(validators=[NumberRange(20, 55)])
phone = StringField(validators=[Regexp(r"1[38745]\d{9}")])
自定义验证器:
当WTForm中的验证器不能满足业务需求的时候,用户可以自定义自己的验证器:
例如自定义验证码的验证器:
1. 定义一个方法,方法的命名规则是 validate_字段名(self,field)
2. 在方法中使用field.data可以获取到字段的具体值
3. 如果验证成功,那么可以什么都不做
4. 如果验证失败,需要抛出一个异常
# 表单验证类
class LoginForm(Form):
# email = StringField(validators=[Email(message="邮箱格式错误")])
# username = StringField(validators=[InputRequired(message="用户名不能为空")])
# age = IntegerField(validators=[NumberRange(20, 55)])
# phone = StringField(validators=[Regexp(r"1[38745]\d{9}")])
captcha = StringField(validators=[Length(4, 4)]) # 首先验证长度
# 假设验证码为1234, 自定义captcha验证器,函数名必须以validate开头
# 所以在验证captcha的时候,除了验证Length,还会自动调用下面自定义的函数进行验证
def validate_captcha(self, field): # 针对具体的字段做验证
# 通过field.data获取数据
if field.data == "1234":
return "Success"
else:
raise ValidationError("验证码错误")
WTForm渲染模板(不推荐使用)
WTForm可以结合jinja2模板渲染表单元素,例如可以渲染各种input, radio等标签
1. 首先,在form.py文件中定义相关的表单类,定义相关的字段
class SettingForm(Form):
username = StringField("用户名", validators=[InputRequired()])
age = IntegerField("年龄", validators=[NumberRange(20, 60)])
remember = BooleanField("记住我: ")
tags = SelectField("标签", choices=[("1", "Java"), ("2", "C++"), ("3", "Android")]
2. 在视图函数中,将实例化的form对象作为参数,传递到模板文件中:
@app.route('/settings/', methods=["GET", "POST"])
def setting():
if request.method == "GET":
form = SettingForm() # 利用form渲染模板
return render_template("html/settings.html", form=form) # 传递参数
else:
form = SettingForm(request.form)
pass
3. 在模板文件中,可以通过变量绑定来使用,同时,可以对相应的字段传入样式参数,id, class
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>设置</title>
<style>
.username-input
{
background: blue;
}
#age-input
{
background: pink;
}
</style>
</head>
<body>
<form action="" method="post">
<table>
<tbody>
<tr>
<!--利用form渲染页面-->
<td>{{ form.username.label }}</td>
{# <td>用户名</td>#}
{# <td><input type="text" name="username"></td>#}
<td>{{ form.username(class="username-input") }}</td>
</tr>
<tr>
<!--传入参数-->
<td>{{ form.age.label }}</td>
<td>{{ form.age(id="age-input") }}</td>
</tr>
<tr>
<td>{{ form.remember.label }}</td>
<td>{{ form.remember() }}</td>
</tr>
<tr>
<td>{{ form.tags.label }}</td>
<td>{{ form.tags() }}</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
渲染的效果:
文件上传
1. 在模板中,需要指定input标签的enctype才能上传文件。upload.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传</title>
</head>
<body>
<form action="" method="post" enctype="multipart/form-data"> <!--上传文件要写enctype-->
<table>
<tbody>
<tr>
<td>头像: </td>
<td><input type="file" name="avatar"></td>
</tr>
<tr>
<td>描述: </td>
<td><input type="text" name="desc"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
完成上传界面:
2. 在后台如果想要获取上传的文件,那么应该使用request.files.get(name)获取上传的文件,这里的name值表示的是对应的input标签的name属性的值。
@app.route('/upload/', methods=["GET", "POST"])
def upload():
if request.method == "GET":
return render_template("html/upload.html")
else:
# 接受用户上传的数据
desc = request.form.get("desc")
avatar = request.files.get("avatar") # 获取文件
# 保存文件
file_name = secure_filename(avatar.filename) # 对文件名进行安全检测
avatar.save("uploaded/" + file_name)
print(desc)
return "文件上传成功"
3. 获取到上传的文件后,可以使用save方法保存文件
【注】: 在保存文件之前,需要使用secure_filename对文件名进行过滤,消除安全隐患:
from werkzeug.utils import secure_filename # 对文件名进行包装
4. 在服务器上读取文件,应该定义一个url来获取指定文件,在视图函数中使用send_from_directory(文件目录,文件名)来获取服务器上的文件:
@app.route('/show/<file_name>/')
def show_image(file_name):
return send_from_directory("uploaded/", filename=file_name)
完整的app.py文件如下:
from flask import Flask, url_for, request, render_template
import config
from exts import db
from forms import RegisterForm, LoginForm, SettingForm
import os
from werkzeug.utils import secure_filename # 对文件名进行包装
from flask import send_from_directory
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app) # db获取app中数据库的连接方式
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/profile/')
def profile():
return "profile page"
@app.route('/register/', methods=["GET", "POST"])
def register():
"""
表单验证
:return:
"""
if request.method == "GET":
return render_template("html/register.html")
else:
form = RegisterForm(request.form)
if form.validate():
return "success"
else:
print(form.errors) # 获取具体的错误信息
return "Fail"
@app.route('/login/', methods=["GET", "POST"])
def login():
if request.method == "GET":
return render_template("html/login.html")
else:
form = LoginForm(request.form)
if form.validate():
return "success"
else:
print(form.errors)
return "Failed"
@app.route('/settings/', methods=["GET", "POST"])
def setting():
if request.method == "GET":
form = SettingForm() # 利用form渲染模板
return render_template("html/settings.html", form=form)
else:
form = SettingForm(request.form)
pass
@app.route('/upload/', methods=["GET", "POST"])
def upload():
if request.method == "GET":
return render_template("html/upload.html")
else:
# 接受用户上传的数据
desc = request.form.get("desc")
avatar = request.files.get("avatar") # 获取文件
# 保存文件
file_name = secure_filename(avatar.filename) # 对文件名进行安全检测
avatar.save("uploaded/" + file_name)
print(desc)
return "文件上传成功"
@app.route('/show/<file_name>/')
def show_image(file_name):
return send_from_directory("uploaded/", filename=file_name)
if __name__ == '__main__':
app.run()
利用Flask_wtf验证上传的文件:
1. 对上传的问价使用表单验证,在定义表单的时候,对文件的字段需要采用FileField进行验证
2. 验证器应该从flask_wtf.file中导入,需要使用的是flask_wtf.file.FileRequired验证文件是否为空,flask_wtf.file.FileAllowed
验证文件的格式是否满足要求。
3. 在进行验证的时候,因为上传的字符串需要使用request.form进行获取,上传的文件需要使用request.file进行获取,所以在进行验证的时候,需要对这两个数据进行组合,这两个数据都是immutableDict,不可变字典的形式,所以在视图函数中使用CombineMultiDict将字符串和文件组合在一起,作为表单验证的输入参数。
示例代码:
form.py中定义的验证表单
from flask_wtf.file import FileRequired, FileAllowed # 验证文件必须上传,以及上传的类型
# 定义上传表单验证
class UploadForm(Form):
avatar = FileField(validators=[FileRequired(), FileAllowed(["jpg", "png", "gif"])])
desc = StringField(validators=[InputRequired()])
视图函数:
from werkzeug.datastructures import CombinedMultiDict # 将两个不可变的字典组合到一起
@app.route('/upload/', methods=["GET", "POST"])
def upload():
if request.method == "GET":
return render_template("html/upload.html")
else:
# 接受用户上传的数据
# form = UploadForm(request.form) # 因为这里字符串是根据request.form获取的 文件时根据request.form获取的,需要组合
form = UploadForm(CombinedMultiDict([request.form, request.files]))
if form.validate():
desc = request.form.get("desc")
avatar = request.files.get("avatar") # 获取文件
# 另一种数据获取方法
# desc_ = form.desc.data
# avatar_ = form.avatar.data
# 保存文件
file_name = secure_filename(avatar.filename) # 对文件名进行安全检测
avatar.save("uploaded/" + file_name)
print(desc)
return "文件上传成功"
else:
print(form.errors)
return "文件上传失败"