Form Validation with WTForms
When you have to work with form data submitted by a browser view, codequickly becomes very hard to read. There are libraries out there designedto make this process easier to manage. One of them is WTForms which wewill handle here. If you find yourself in the situation of having manyforms, you might want to give it a try.
When you are working with WTForms you have to define your forms as classesfirst. I recommend breaking up the application into multiple modules(Larger Applications) for that and adding a separate module for theforms.
Getting the most out of WTForms with an Extension
The Flask-WTF extension expands on this pattern and adds afew little helpers that make working with forms and Flask morefun. 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(SQLAlchemy in Flask), but that’s not a requirement, of course. Adaptthe code as necessary.
Things to remember:
create the form from the request
form
value ifthe data is submitted via the HTTPPOST
method andargs
if the data is submitted asGET
.to validate the data, call the
validate()
method, which will returnTrue
if the data validates,False
otherwise.to access individual values from the form, access form.
.data .
Forms in Templates
Now to the template side. When you pass the form to the templates, you caneasily render them there. Look at the following example template to seehow 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 withlabel 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 toWTForm’s field function, which renders the field for us. The keywordarguments will be inserted as HTML attributes. So, for example, you cancall render_field(form.username, class='username')
to add a class tothe input element. Note that WTForms returns standard Python unicodestrings, so we have to tell Jinja2 that this data is already HTML-escapedwith the |safe
filter.
Here is the register.html
template for the function we used above, whichtakes 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 WTFormswebsite.