在说WTForms之前,我们先搞清楚Flask-WTF与WTForms的区别
Flask-WTF
基本了解
Flask-WTF是集成WTForms,并带有 csrf 令牌的安全表单和全局的 csrf 保护的功能。
每次我们在建立表单所创建的类都是继承与flask_wtf中的FlaskForm,而FlaskForm是继承WTForms中forms。
用法:
1.创建基础表单
例如,form.py:
class LoginForm(FlaskForm):
username = StringField()
password = PasswordField()
remember_me = BooleanField(label='Keep me logged in')
2.CSRF保护
任何使用FlaskForm创建的表单发送请求,都会有CSRF的全部保护,在对应的template中HTML渲染表单时,可以加入form.csrf_token:
<form method="post">
{{ form.csrf_token }}
</form>
但是如果模板中没有表单,则可以使用一个隐藏的input标签加入csrf_token。
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>
3.验证表单
在视图处理程序中验证请求:
view.py
def login():
form = LoginForm()
if form.validate_on_submit():
return redirect('/success')
return render_template('login.html', form=form)
使用validate_on_submit
来检查是否是一个 POST 请求并且请求是否有效。
4.文件上传
Flask-WTF 提供 FileField
来处理文件上传,它在表单提交后,自动从 flask.request.files
中抽取数据。FileField
的 data
属性是一个 Werkzeug FileStorage 实例。
from werkzeug import secure_filename
from flask_wtf.file import FileField
class PhotoForm(Form):
photo = FileField('Your photo')
@app.route('/upload/', methods=('GET', 'POST'))
def upload():
form = PhotoForm()
if form.validate_on_submit():
filename = secure_filename(form.photo.data.filename)
form.photo.data.save('uploads/' + filename)
else:
filename = None
return render_template('upload.html', form=form, filename=filename)
注意:在 HTML 表单的 enctype 设置成 multipart/form-data,如下:
<form action="/upload/" method="POST" enctype="multipart/form-data">
....
</form>
5.验证码
Flask-WTF 通过 RecaptchaField 也提供对验证码的支持:
from flask_wtf import Form, RecaptchaField
from wtforms import TextField
class SignupForm(Form):
username = TextField('Username')
recaptcha = RecaptchaField()
还需要配置一下信息:
字段 | 配置 |
---|---|
RECAPTCHA_PUBLIC_KEY | 必须 公钥 |
RECAPTCHA_PRIVATE_KEY | 必须 私钥 |
RECAPTCHA_API_SERVER | 可选 验证码 API 服务器 |
RECAPTCHA_PARAMETERS | 可选 一个 JavaScript(api.js)参数的字典 |
RECAPTCHA_DATA_ATTRS | 可选 一个数据属性项列表 https://developers.google.com/recaptcha/docs/display |
WTForms
WTForms是一个Flask集成的框架,或者是说库。用于处理浏览器表单提交的数据。它在Flask-WTF 的基础上扩展并添加了一些随手即得的精巧的帮助函数,这些函数将会使在 Flask 里使用表单更加有趣。
安装
pip install wtforms
WTForms支持的HTML标准字段
字段类型 | 说明 |
---|---|
StringField | 文本字段, 相当于type类型为text的input标签 |
TextAreaField | 多行文本字段 |
PasswordField | 密码文本字段 |
HiddenField | 隐藏文本字段 |
DateField | 文本字段, 值为datetime.date格式 |
DateTimeField | 文本字段, 值为datetime.datetime格式 |
IntegerField | 文本字段, 值为整数 |
DecimalField | 文本字段, 值为decimal.Decimal |
FloatField | 文本字段, 值为浮点数 |
BooleanField | 复选框, 值为True 和 False |
RadioField | 一组单选框 |
SelectField | 下拉列表 |
SelectMultipleField | 下拉列表, 可选择多个值 |
FileField | 文件上传字段 |
SubmitField | 表单提交按钮 |
FormFiled | 把表单作为字段嵌入另一个表单 |
FieldList | 子组指定类型的字段 |
Validators验证器
WTForms可以支持很多表单的验证函数:
验证函数 | 说明 |
---|---|
验证是电子邮件地址 | |
EqualTo | 比较两个字段的值; 常用于要求输入两次密钥进行确认的情况 |
IPAddress | 验证IPv4网络地址 |
Length | 验证输入字符串的长度 |
NumberRange | 验证输入的值在数字范围内 |
Optional | 无输入值时跳过其它验证函数 |
DataRequired | 确保字段中有数据 |
Regexp | 使用正则表达式验证输入值 |
URL | 验证url |
AnyOf | 确保输入值在可选值列表中 |
NoneOf | 确保输入值不在可选列表中 |
自定义Validators验证器
第一种: in-line validator(内联验证器)
也就是自定义一个验证函数,在定义表单类的时候,在对应的字段中加入该函数进行认证。下面的my_length_check
函数就是用于判name
字段长度不能超过50.
def my_length_check(form, field):
if len(field.data) > 50:
raise ValidationError('Field must be less than 50 characters')
class MyForm(Form):
name = StringField('Name', [InputRequired(), my_length_check])
第二种:通用且可重用的验证函数
一般是以validate
开头,加上下划线再加上对应的field字段(validate_filed),浏览器在提交表单数据时,会自动识别对应字段所有的验证器,然后执行验证器进行判断。
class RegistrationForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Length(1, 60), Email()])
username = StringField('Username', validators=[DataRequired(), Length(1, 60),
Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0, 'username must have only letters, numbers dots or underscores')])
password = PasswordField('Password', validators=[DataRequired(), EqualTo('password2', message='password must match')])
password2 = PasswordField('Confirm password', validators=[DataRequired()])
def validate_email(self, field):
if User.objects.filter(email=field.data).count() > 0:
raise ValidationError('Email already registered')
def validate_username(self, field):
if User.objects.filter(username=field.data).count() > 0:
raise ValidationError('Username has exist')
第三种:比较高级的validators
class Length(object):
def __init__(self, min=-1, max=-1, message=None):
self.min = min
self.max = max
if not message:
message = u'Field must be between %i and %i characters long.' % (min, max)
self.message = message
def __call__(self, form, field):
l = field.data and len(field.data) or 0
if l < self.min or self.max != -1 and l > self.max:
raise ValidationError(self.message)
length = Length
Widget组件
下面可以以登录界面为实例:
# login.py
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms import validators
from wtforms import widgets
class LoginForm(Form):
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired(message='用户名不能为空.'),
],
widget=widgets.TextInput(),
render_kw={'class': 'form-control'}
)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空.'),
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
使用
案例一:登录
from flask import Flask, render_template, request, redirect
from wtforms.fields import simple
from wtforms import validators, widgets, Form
app = Flask(__name__, template_folder='templates')
app.debug = True
class LoginForm(Form):
# 字段(内部包含正则表达式)
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired(message='用户名不能为空.'),
validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
],
widget=widgets.TextInput(), # 页面上显示的插件
render_kw={'class': 'form-control'}
)
# 字段(内部包含正则表达式)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空.'),
validators.Length(min=8, message='用户名长度必须大于%(min)d'),
validators.Regexp(
regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[$@$!%*?&])[A-Za-zd$@$!%*?&]{8,}",
message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
form = LoginForm()
return render_template('login.html', form=form)
else:
form = LoginForm(formdata=request.form)
if form.validate():
print('用户提交数据通过格式验证,提交的值为:', form.data)
else:
print(form.errors)
return render_template('login.html', form=form)
if __name__ == '__main__':
app.run()
<!--login.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<form method="post">
<p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p>
<p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p>
<input type="submit" value="提交">
</form>
</body>
</html>
案例二:注册
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
app = Flask(__name__, template_folder='templates')
app.debug = True
class RegisterForm(Form):
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired()
],
widget=widgets.TextInput(),
render_kw={'class': 'form-control'},
default='cxw'
)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空.')
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
pwd_confirm = simple.PasswordField(
label='重复密码',
validators=[
validators.DataRequired(message='重复密码不能为空.'),
validators.EqualTo('pwd', message="两次密码输入不一致")
],
widget=widgets.PasswordInput(),
render_kw={'class': 'form-control'}
)
email = html5.EmailField(
label='邮箱',
validators=[
validators.DataRequired(message='邮箱不能为空.'),
validators.Email(message='邮箱格式错误')
],
widget=widgets.TextInput(input_type='email'),
render_kw={'class': 'form-control'}
)
gender = core.RadioField(
label='性别',
choices=(
(1, '男'),
(2, '女'),
),
#这句话的意思是上面的choices元组的第一个值是int类型
#如果上上面为(‘1’, '男'),(‘2’, '女'),则下面的coerce则不用写
coerce=int # “1” “2”
)
#这里是单选框
city = core.SelectField(
label='城市',
choices=(
('bj', '北京'),
('sh', '上海'),
)
)
#这里是多选框
hobby = core.SelectMultipleField(
label='爱好',
choices=(
(1, '篮球'),
(2, '足球'),
),
coerce=int
)
#这里是多选的checkbox
favor = core.SelectMultipleField(
label='喜好',
choices=(
(1, '篮球'),
(2, '足球'),
),
widget=widgets.ListWidget(prefix_label=False),
option_widget=widgets.CheckboxInput(),
coerce=int,
default=[1, 2]
)
#这里可以改值
def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球'))
def validate_pwd_confirm(self, field):
"""
自定义pwd_confirm字段规则,例:与pwd字段是否一致
:param field:
:return:
"""
# 最开始初始化时,self.data中已经有所有的值
if field.data != self.data['pwd']:
# raise validators.ValidationError("密码不一致") # 继续后续验证
raise validators.StopValidation("密码不一致") # 不再继续后续验证
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'GET':
#这里可以传默认值
form = RegisterForm(data={'gender': 2,'hobby':[1,]}) # initial
return render_template('register.html', form=form)
else:
form = RegisterForm(formdata=request.form)
if form.validate():
print('用户提交数据通过格式验证,提交的值为:', form.data)
else:
print(form.errors)
return render_template('register.html', form=form)
if __name__ == '__main__':
app.run()
<!--register.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户注册</h1>
<form method="post" novalidate style="padding:0 50px">
{% for field in form %}
<p>{{field.label}}: {{field}} {{field.errors[0] }}</p>
{% endfor %}
<input type="submit" value="提交">
</form>
</body>
</html>