Files
python-book/04.Flask/06.Flask-WTF插件.md
2025-08-27 14:39:37 +08:00

320 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Flask-WTF插件
## 1. Flask-WTF表单验证
`Flask-WTF`是简化了`WTForms`操作的一个第三方库。`WTForms`表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。当然还包括一些其他的功能:`CSRF保护`,文件上传等。安装`Flask-WTF`默认也会安装`WTForms`,因此使用以下命令来安装`Flask-WTF`:
```
pip install flask-wtf
```
### 1.1 表单验证:
安装完`Flask-WTF`后。来看下第一个功能,就是用表单来做数据验证,现在有一个`forms.py`文件,然后在里面创建一个`RegistForm`的注册验证表单:
```python
class RegistForm(Form):
name = StringField(validators=[length(min=4,max=25)])
email = StringField(validators=[email()])
password = StringField(validators=[DataRequired(),length(min=6,max=10),EqualTo('confirm')])
confirm = StringField()
```
在这个里面指定了需要上传的参数,并且指定了验证器,比如`name`的长度应该在`4-25`之间。`email`必须要满足邮箱的格式。`password`长度必须在`6-10`之间,并且应该和`confirm`相等才能通过验证。
写完表单后,接下来就是`regist.html`文件:
```python
<form action="/regist/" method="POST">
<table>
<tr>
<td>用户名</td>
<td><input type="text" name="name"></td>
</tr>
<tr>
<td>邮箱</td>
<td><input type="email" name="email"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>确认密码</td>
<td><input type="password" name="confirm"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form>
```
再来看视图函数`regist`
```python
@app.route('/regist/',methods=['POST','GET'])
def regist():
form = RegistForm(request.form)
if request.method == 'POST' and form.validate():
user = User(name=form.name.data,email=form.email.data,password=form.password.data)
db.session.add(user)
db.session.commit()
return u'注册成功!'
return render_template('regist.html')
```
`RegistForm`传递的是`request.form`进去进行初始化,并且判断`form.validate`会返回用户提交的数据是否满足表单的验证。
### 1.2 渲染模板:
`form`还可以渲染模板,让你少写了一丢丢的代码,比如重写以上例子,`RegistForm`表单代码如下:
```python
class RegistForm(Form):
name = StringField(u'用户名:',validators=[length(min=4,max=25)])
email = StringField(u'邮箱:'validators=[email()])
password = StringField(u'密码:',validators=[DataRequired(),length(min=6,max=10),EqualTo('confirm')])
confirm = StringField(u'确认密码:')
```
以上增加了第一个位置参数用来在html文件中做标签提示作用。
`app`中的视图函数中,修改为如下:
```python
@app.route('/regist/',methods=['POST','GET'])
def regist():
form = RegistForm(request.form)
if request.method == 'POST' and form.validate():
user = User(name=form.name.data,email=form.email.data,password=form.password.data)
db.session.add(user)
db.session.commit()
return u'注册成功!'
return render_template('regist.html',form=form)
```
以上唯一的不同是在渲染模板的时候传入了`form`表单参数进去,这样在模板中就可以使用表单`form`变量了。
接下来看下`regist.html`文件:
```python
<form action="/regist/" method="POST">
<table>
<tr>
<td>{{ form.name.label }}</td>
<td>{{ form.name() }}</td>
</tr>
<tr>
<td>{{ form.email.label }}</td>
<td>{{ form.email() }}</td>
</tr>
<tr>
<td>{{ form.password.label }}</td>
<td>{{ form.password() }}</td>
</tr>
<tr>
<td>{{ form.confirm.label }}</td>
<td>{{ form.confirm() }}</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form>
```
## 2. Flask-WTF常用字段和验证器
### 2.1 Field常用参数
在使用`Field`的时候,经常需要传递一些参数进去,以下将对一些常用的参数进行解释:
- label第一个参数`Field`的label的文本。
- validators验证器。
- id`Field`的id属性默认不写为该属性名。
- default默认值。
- widget指定的`html`控件。
### 2.2 常用Field
- BooleanField布尔类型的Field渲染出去是`checkbox`
- FileField文件上传Field。
```python
# forms.py
from flask_wtf.file import FileField,FileAllowed,FileRequired
class UploadForm(FlaskForm):
avatar = FileField(u'头像:',validators=[FileRequired(),FileAllowed([])])
# app.py
@app.route('/profile/',methods=('POST','GET'))
def profile():
form = ProfileForm()
if form.validate_on_submit():
filename = secure_filename(form.avatar.data.filename)
form.avatar.data.save(os.path.join(app.config['UPLOAD_FOLDER'],filename))
return u'上传成功'
return render_template('profile.html',form=form)
```
- FloatField浮点数类型的Field但是渲染出去的时候是`text`的input。
- IntegerField整形的Field。同FloatField。
- RadioField`radio`类型的`input`。表单例子如下:
```python
# form.py
class RegistrationForm(FlaskForm):
gender = wtforms.RadioField(u'性别:',validators=[DataRequired()])
```
模板文件代码如下:
```python
<tr>
<td>
{{ form.gender.label }}
</td>
<td>
{% for gender in form.gender %}
{{ gender.label }}
{{ gender }}
{% endfor %}
</td>
</tr>
```
`app.py`文件的代码如下,给`gender`添加了`choices`
```python
@app.route('/register/',methods=['POST','GET'])
def register():
form = RegistrationForm()
form.gender.choices = [('1',u'男'),('2',u'女')]
if form.validate_on_submit():
return u'success'
return render_template('register.html',form=form)
```
- SelectField类似于`RadioField`。看以下示例:
```python
# forms.py
class ProfileForm(FlaskForm):
language = wtforms.SelectField('Programming Language',choices=[('cpp','C++'),('py','python'),('text','Plain Text')],validators=[DataRequired()])
```
再来看`app.py`文件:
```python
@app.route('/profile/',methods=('POST','GET'))
def profile():
form = ProfileForm()
if form.validate_on_submit():
print form.language.data
return u'上传成功'
return render_template('profile.html',form=form)
```
模板文件为:
```html
<form action="/profile/" method="POST">
{{ form.csrf_token }}
{{ form.language.label }}
{{ form.language() }}
<input type="submit">
</form>
```
- StringField渲染到模板中的类型为`<input type='text'>`,并且是最基本的文本验证。
- PasswordField渲染出来的是一个`password`的`input`标签。
- TextAreaField渲染出来的是一个`textarea`。
### 2.3 常用的验证器:
数据发送过来,经过表单验证,因此需要验证器来进行验证,以下对一些常用的内置验证器进行讲解:
- Email验证上传的数据是否为邮箱。
- EqualTo验证上传的数据是否和另外一个字段相等常用的就是密码和确认密码两个字段是否相等。
- InputRequired原始数据的需要验证。如果不是特殊情况应该使用`InputRequired`。
- Length长度限制有min和max两个值进行限制。
- NumberRange数字的区间有min和max两个值限制如果处在这两个数字之间则满足。
- Regexp自定义正则表达式。
- URL必须要是`URL`的形式。
- UUID验证`UUID`。
### 2.4 自定义验证字段:
使用`validate_fieldname(self,field)`可以对某个字段进行更加详细的验证,如下:
```python
class ProfileForm(FlaskForm):
name = wtforms.StringField('name',[validators.InputRequired()])
def validate_name(self,field):
if len(field.data) > 5:
raise wtforms.ValidationError(u'超过5个字符')
```
## 3. CSRF保护
在flask的表单中默认是开启了`csrf`保护功能的,如果你想关闭表单的`csrf`保护,可以在初始化表单的时候传递`csrf_enabled=False`进去来关闭`csrf`保护。如果你想关闭这种默认的行为。如果你想在没有表单存在的请求视图函数中也添加`csrf`保护,可以开启全局的`csrf`保护功能:
```python
csrf = CsrfProtect()
csrf.init_app(app)
```
或者是针对某一个视图函数,使用`csrf.protect`装饰器来开启`csrf`保护功能。并且如果已经开启了全局的`csrf`保护,想要关闭某个视图函数的`csrf`保护功能,可以使用`csrf.exempt`装饰器来取消本视图函数的保护功能。
### 3.1 AJAX的CSRF保护
在`AJAX`中要使用`csrf`保护,则必须手动的添加`X-CSRFToken`到`Header`中。但是`CSRF`从哪里来,还是需要通过模板给渲染,而`Flask`比较推荐的方式是在`meta`标签中渲染`csrf`,如下:
```html
<meta name="csrf-token" content="{{ csrf_token() }}">
```
如果要发送`AJAX`请求,则在发送之前要添加`CSRF`,代码如下使用了jQuery
```javascript
var csrftoken = $('meta[name=csrf-token]').attr('content')
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
}
})
```