Hello, Flask!
首发于Hello, Flask!
Flask表单:表单数据的验证与处理

Flask表单:表单数据的验证与处理

这篇文章作为上一篇的续篇,所以结构上也和上一篇一样。

使用Flask-WTF

这是一个登录的视图函数:

@auth.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        email = form.email.data
        ...
    return render_template('login.html', form=form)

验证数据

当我们点击了表单上的提交按钮时,form.validate_on_submit()判断会做下面两件事情:

  1. 通过is_submitted()通过判断HTTP方法来确认是否提交了表单
  2. 通过WTForms提供的validate()来验证表单数据(使用我们在下面的表单类里给每个字段传入的验证函数)

这是我们的表单类:

class LoginForm(FlaskForm):
    email = StringField(u'邮箱', validators=[
                DataRequired(message= u'邮箱不能为空'), Length(1, 64),
                Email(message= u'请输入有效的邮箱地址,比如:username@domain.com')])
    password = PasswordField(u'密码', 
                  validators=[Required(message= u'密码不能为空')])
    submit = SubmitField(u'登录')
在Flask-WTF 0.13版本,引入的表单类为FlaskForm
在WTForms 3.0版本,验证函数Required变为DataRequired

当validate()验证未通过时,会在表单字段下面显示我们传进去的错误提示(例如message= u'邮箱不能为空')。

自定义验证

你可以在表单类创建自定义的验证函数,一个简单的例子:

def validate_username(self, field):
    if User.query.filter_by(username=field.data).first():
        raise ValidationError(u'用户名已被注册,换一个吧。')

这个例子验证用户名是否已经存在(这里使用SQLAlchemy),其中field.data是数据。

ValidationError从wtforms导入,用来向用户显示错误信息,验证函数的名称由validate_fieldname组成。你也可以在这里对用户数据进行预处理:

def validate_website(self, field):
    if field.data[:4] != "http":
        field.data = "http://" + field.data

这个函数对用户输入的网址进行处理(字段名为website)。

你也可以在表单类外面定义一个通用的验证函数,然后传入字段的验证函数列表里,具体见WTForms文档:wtforms.readthedocs.io/


获取数据

验证通过后,我们使用form.email.data来获得数据,WTForms提供的静态方法.data返回一个以字段名(field name)和字段值(field value)作为键值对的字典。

不使用Flask-WTF,只使用WTForms

我们既然已经知道了Flask-WTF的form.validate_on_submit()的工作原理,我们也可以自己实现:

@auth.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm(request.form)  # 需要手动传入包含表单数据的request.form属性
    if request.method == 'POST' and form.validate():
        email = form.email.data
        ...
    return render_template('login.html', form=form)

但是这样要复杂一点,而且在渲染时比较繁琐,如果你想了解更多,可以参考:flask.pocoo.org/docs/0.

不使用Flask-WTF,也不使用WTForms

在一些特殊场景下,比如一个特别简单的表单。这是上一个实践项目的表单:

<form method="POST" action="{{ url_for('custom') }}">
    <input type="text" name="time" class="time-input" placeholder="example: 12/30s/20m/2h">
    <input type="submit" class="startButton" value="START">
</form>

这时要给表单添加一个action属性,属性值填写要处理表单数据的视图函数。这是处理表单数据的函数:

@app.route('/custom', methods=['GET', 'POST'])
def custom():
    if request.method == 'POST':
        time = request.form.get('time', 180)
        ...

获取数据

使用request来获取数据(request从flask导入),使用字段的name值来区分,你可以用:

request.form['input-name']

或是:

request.form.get('input-name', default_value)

注意如果你使用第一种方式获取数据,要确保有数据才行,像勾选框这样可以留空的字段,如果用户没有填写数据,提交后会引起HTTP 400错误,这时应该使用第二种方式来设置一个默认值。

你也可以像使用Flask-WTF一样为表单填入已经存储在数据库里的内容,不过要自己填到表单里:

@app.route('/custom', methods=['GET', 'POST'])
def custom():
    if request.method == 'POST':
        time = request.form.get('time', 180)
        ...
    email = 'example@flask.com' 
    return render_template('demo.html', email=email)

在HTML里,将email作为value的值:

<input name="email" value="{{ email }}">

验证数据

因为没有使用WTForms,所以验证数据要自己实现,常见的验证通过你的Python知识大多都很容易实现,错误信息则可以使用flash()传递。

大多数新手对正则表达式比较头疼,在这里推荐一个在线正则表达式编辑网站,regex101.com,支持PHP、PCRE、Python、Golang和 JavaScript,可以写测试,查cheatsheet,生成代码,解释表达式,非常好用!


实践:多个表单字段的生成及数据的获取

其实这是后面豆瓣相册实践项目里的内容,就提前透露一点吧。在豆瓣相册里,有一个批量修改的功能,在这个页面,要为你的相册里的每一张图片生成一个编辑框。我们可以直接使用for循环在HTML里生成表单。

生成表单

用for循环生成input,注意我用每个图片的id作为相应输入框的name值:

<form action="{{ url_for('.edit_photos', id=album.id) }}" method="POST">
<ul>
    {% for photo in photos %}
    <li>
        <img class="img-responsive portrait" src="{{ photo.path }}" alt="Some description"/>
        <textarea name="{{ photo.id }}" placeholder="add some description" rows="3">{% if photo.description %}{{ photo.description }}{% endif %}</textarea>
    </li>
    {% endfor %}
</ul>
<hr>
<input class="btn btn-success" type="submit" name="submit" value="submit">

获取数据

然后我在视图函数同样用for循环迭代所有图片,然后使用request获取相应的数据(通过图片的id作为name值获取)然后保存到数据库:

@app.route('/edit-photos/<int:id>', methods=['GET', 'POST'])
@login_required
def edit_photos(id):
    album = Album.query.get_or_404(id)
    photos = album.photos.order_by(Photo.order.asc())
    if request.method == 'POST':
        for photo in photos:
            photo.about = request.form[str(photo.id)]
            db.session.add(photo)
        return redirect(url_for('.album', id=id))
    return render_template('edit_photo.html', album=album, photos=photos)

就先简单讲一下,具体内容等到后面再讲。

相关链接

  1. Flask-WTF文档:Flask-WTF - Flask-WTF 0.13
  2. WTForms文档:WTForms Documentation
  3. Flask文档WTForms章节:flask.pocoo.org/docs/0.
  4. Flask snippet Form分类:Flask (A Python Microframework)


- - - - -

更多关于Flask的优质内容,欢迎关注Hello, Flask! - 知乎专栏

编辑于 2018-06-14

文章被以下专栏收录

    大家好,我是李辉,一个 Python 程序员。在这个专栏,你会看到我学习和使用 Flask 的经验和总结,你还会看到我所有的发明创造,以及它们的实现方法。欢迎加入这场 Flask 之旅,拿好你的小键盘,上车吧!helloflask.com