使用WTForms进行表单验证

当你需要处理由浏览器视图提交的表单数据时,代码往往会变得难以阅读。市面上有一些库专门用于让这个过程更容易管理,其中之一就是我们将在这里介绍的 WTForms。如果你的应用中有许多表单,不妨试试这个库。

When you are working with WTForms you have to define your forms as classes first. I recommend breaking up the application into multiple modules (将大型应用作为包组织) for that and adding a separate module for the forms.

Getting the most out of WTForms with an Extension

The Flask-WTF extension expands on this pattern and adds a few little helpers that make working with forms and Flask more fun. You can get it from PyPI.

The Forms

This is an example form for a typical registration page:

from wtforms import Form, BooleanField, StringField, PasswordField, validators

class RegistrationForm(Form):
    username = StringField('Username', [validators.Length(min=4, max=25)])
    email = StringField('Email Address', [validators.Length(min=6, max=35)])
    password = PasswordField('New Password', [
        validators.DataRequired(),
        validators.EqualTo('confirm', message='Passwords must match')
    ])
    confirm = PasswordField('Repeat Password')
    accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])

In the View

In the view function, the usage of this form looks like this:

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm(request.form)
    if request.method == 'POST' and form.validate():
        user = User(form.username.data, form.email.data,
                    form.password.data)
        db_session.add(user)
        flash('Thanks for registering')
        return redirect(url_for('login'))
    return render_template('register.html', form=form)

Notice we're implying that the view is using SQLAlchemy here (在Flask中使用SQLAlchemy数据库), but that's not a requirement, of course. Adapt the code as necessary.

Things to remember:

  1. create the form from the request form value if the data is submitted via the HTTP POST method and args if the data is submitted as GET.

  2. to validate the data, call the validate() method, which will return True if the data validates, False otherwise.

  3. to access individual values from the form, access form.<NAME>.data.

Forms in Templates

Now to the template side. When you pass the form to the templates, you can easily render them there. Look at the following example template to see how easy this is. WTForms does half the form generation for us already. To make it even nicer, we can write a macro that renders a field with label and a list of errors if there are any.

Here's an example _formhelpers.html template with such a macro:

{% macro render_field(field) %}
  <dt>{{ field.label }}
  <dd>{{ field(**kwargs)|safe }}
  {% if field.errors %}
    <ul class=errors>
    {% for error in field.errors %}
      <li>{{ error }}</li>
    {% endfor %}
    </ul>
  {% endif %}
  </dd>
{% endmacro %}

This macro accepts a couple of keyword arguments that are forwarded to WTForm's field function, which renders the field for us. The keyword arguments will be inserted as HTML attributes. So, for example, you can call render_field(form.username, class='username') to add a class to the input element. Note that WTForms returns standard Python strings, so we have to tell Jinja2 that this data is already HTML-escaped with the |safe filter.

Here is the register.html template for the function we used above, which takes advantage of the _formhelpers.html template:

{% from "_formhelpers.html" import render_field %}
<form method=post>
  <dl>
    {{ render_field(form.username) }}
    {{ render_field(form.email) }}
    {{ render_field(form.password) }}
    {{ render_field(form.confirm) }}
    {{ render_field(form.accept_tos) }}
  </dl>
  <p><input type=submit value=Register>
</form>

For more information about WTForms, head over to the WTForms website.