# 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)
        }
    }
})
```





















