RominYue’s Blog

A lifeway of coders

Flask-wtform Study

| Comments

注意:文中的所有’{‘之间的空格在实际代码中是不存在的(不知道如何不转义),这个是模班渲染问题

1. 安装

1
$ pip install flask-wtf

flask-wtf中集成了wtforms相关的功能
下面我们将写一个小型的flask的应用,路由处理’/‘页面和’/register’页面,利用flask-wtf生成register的表单.

2. 文件结构

我们将建立一个flask_wtf_practice文件夹和下述文件结构来组织代码

1
2
3
4
5
6
7
8
/flask_wtf_practice
  /static
  /templates
    register.html
    index.html
  app.py
  views.py
  forms.py

3. 初步代码

app.py
1
2
3
4
5
6
7
8
from flask import Flask

app = Flask(__name__)

from views import *

if __name__ == '__main__':
  app.run(debug=True)
views.py
1
2
3
4
5
6
7
8
9
10
from flask import render_template
from app import app

@app.route('/')
def index():
  return render_template('index.html')

@app.route('/register')
def register():
  return render_template('register.html')
index.html
1
2
3
4
5
6
7
8
9
<html>
    <head>
        <title>home page</title>
    </head>
    <body>
        <p>This is home page</p>
        <a href="/register"> Register </a>
    </body>
</html>
register.html
1
2
3
4
5
6
7
8
<html>
    <head>
        <title>home page</title>
    </head>
    <body>
        <p>Now you can register!</p>
    </body>
</html>

这时候在命令行cd进入flask_wtf_practice文件夹,运行python app.py命令,在浏览器窗口输入127.0.0.1:5000就可以看到初步的效果.

3. flask-wtf 配置

要想使用flask-wtf插件,需要进行csrf保护机制的flask配置.在app.py中加入关于此app的配置.

app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask

app = Flask(__name__)

from views import *

#configuration
CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'
app.config.from_object(__name__)

if __name__ == '__main__':
  app.run(debug=True)

4. 设计注册表单

作为最简单的应用,一个register页面应该包括用户名ID,昵称,密码,确认密码等信息.flask-wtf中用Form这个类表示表单,所有我们设计的表单均继承至这个类.

forms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from flask_wtf import Form
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, EqualTo, Length, InputRequired

class RegisterForm(Form):

    userID = StringField('userID',validators=[
        DataRequired('Plsease enter IDs'),
        Length(min = 4, max = 25,message='length must between 4 and 25')
    ])
    nickname = StringField('Nick Nickame',validators=[
        DataRequired('Please enter nickname'),
        Length(min = 4, max = 25,message='length must between 4 and 25')
    ])
    password = PasswordField('PassWord', validators=[
        InputRequired('Please enter password'),
        Length(min = 6, max = 16,message='length must between 4 and 25')
    ])
    rptpassword = PasswordField('Repeat Password', validators=[
        InputRequired('Please enter password to confirm'),
        Length(min = 4, max = 16,message='length must between 4 and 25'),
        EqualTo('password','Password must be match')

    ])
    submit = SubmitField('Submit')

第1行:从falsk_wtf导入Form类
第2行:从wtforms导入各个域(field)
第3行:从wtforms.validators导入各个验证器
第4行~最后:定义自己的registerform类

对于每一个特定的field,都可以实例化它.以stringfield为例子:

1
userID = StringField('label',validators=none)

第一个参数是该field的签名,这个就是最终生成表单的填空框前面的签名
validators是验证器的list,验证输入的内容是否满足list中的验证器.

对于每一个特定的验证器,其参数大致相同,具体请参阅官方文档,以Length为例:

1
validators = [Length(min = 4,max = 16, message = 'length must between 4 and 16')]

其中message是出错时候将要显示的信息,length验证器就是验证一个字符串长度是否符合特定的长度范围. 那么此时,将RegisterFrom类传入到register.html模版渲染就行了

views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import render_template,request, redirect
from app import app
from forms import RegisterForm

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/register', methods=['GET','POST'])
def register():
    form = RegisterForm()
    if request.method == 'POST' and form.validate():
        return redirect('/')
    return render_template('register.html',form=form)
register.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
    <head>
        <title>Register Page</title>
    </head>
    <body>
        <p>Register Now</p>
        <div>
            <form action = "/register" method="POST">
            { { form.csrf_token } }
            <center><h2> Register Information </h2></center>
            <div>{ { form.userID.label } }: { { form.userID } }</div>
            <div>{ { form.nickname.label } }: { { form.nickname } }</div>
            <div>{ { form.password.label } }: { { form.password } }</div>
            <div>{ { form.rptpassword.label } }: { { form.rptpassword } }</div>
            <div>{ { form.submit } }</div>
            </form>
        </div>
    </body>
</html>

5. 如何显示错误信息

上述验证不通过的时候,仅仅返回/register页面,并没有对我们做出提示,那应该如何做出提示呢?
我们修改register.html如下:

register.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<html>
    <head>
        <title>Register Page</title>
    </head>
    <body>
        <p>Register Now</p>
        <div>
            <form action = "/register" method="POST">
            { { form.csrf_token } }
            <center><h2> Register Information </h2></center>
            <div>{ { form.userID.label } }: { { form.userID } }</div>
            <div>{ { form.nickname.label } }: { { form.nickname } }</div>
            { % if form.nickname.errors % }
                <ul class="errors">
                    { % for error in form.nickname.errors % }
                        <li>{ { error } }</li>
                    { % endfor % }
                </ul>
            { % endif % }
            <div>{ { form.password.label } }: { { form.password } }</div>
            <div>{ { form.rptpassword.label } }: { { form.rptpassword } }</div>
            <div>{ { form.submit } }</div>
            </form>
        </div>
    </body>
</html>

6. 自定义validators,验证器

现在我们想限制一下nickname的取值限制在[a-zA-Z0-9]中,否则出错显示”wrong format”.
wtforms提供了几种自定义的方式,这里只显示一种,修改forms.py如下:

forms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from flask_wtf import Form
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, EqualTo, Length, InputRequired
from wtforms.validators import ValidationError
import re

class RegisterForm(Form):

    userID = StringField('userID',validators=[
        DataRequired('Plsease enter IDs'),
        Length(min = 4, max = 25,message='length must between 4 and 25')
    ])
    nickname = StringField('Nick Nickame',validators=[
        DataRequired('Please enter nickname'),
        Length(min = 4, max = 25,message='length must between 4 and 25')
    ])
    password = PasswordField('PassWord', validators=[
        InputRequired('Please enter password'),
        Length(min = 6, max = 16,message='length must between 4 and 25')
    ])
    rptpassword = PasswordField('Repeat Password', validators=[
        InputRequired('Please enter password to confirm'),
        Length(min = 4, max = 16,message='length must between 4 and 25'),
        EqualTo('password','Password must be match')

    ])
    submit = SubmitField('Submit')

    def validate_nickname(form,field):
        if not re.match(r'^[a-zA-Z0-9]{3,22}$',field.data):
            raise ValidationError(u'wrong format')

7. 渲染域(rendering field)

现在问题来了,我们想对nickname这个填空框做一些美化,或者css美化,应该怎么办呢?
其实对于register.html中的{ { form.nickname.label } }: { { form.nickname } },其实最终是把它渲染成了
nickname:
如何美化这样一个填空框呢? 我们可以在语句中修改: ,这样在css文件中美化css_class 就可以了.

8. 如何从Form类中获取数据

我们用post方法提交表单的时候,数据跟随表单提交上来,我们如何获取其中的数据呢?
其实随着post方法的提交,数据已经存储在form.field.data中,我们修改视图函数/register,观察输出.

views.py
1
2
3
4
5
6
7
8
9
10
11
@app.route('/register', methods=['GET','POST'])
def register():
    form = RegisterForm()
    if request.method == 'POST' and form.validate():
        user = {}
        user['userID'] = form.userID.data
        user['nickname'] = form.nickname.data
        user['password'] = form.password.data
        return str(user)

    return render_template('register.html',form=form)

可以观察注册后输出:
{‘nickname’: u’yomin’, ‘password’: u’123456’, ‘userID’: u’yomin’} 这种提取的数据可以用来存储在数据库中.

Comments