WTForms is a forms validation and rendering library for python web development. It is available under the BSD license.
The latest version is 0.5, released February 13th, 2010 (changelog, download).
Introduction
So you’ve cracked your knuckles and started working on that awesome python webapp you want to write. You get through writing a few pages and finally you need to tackle that loathsome task: form input handling and validation. Enter WTForms.
But why do I need yet another framework? Well, some webapp frameworks take the approach of associating database models with form handling. While this can be handy for very basic create/update views, chances are not every form you need can map directly to a database model. Or maybe you already use a generic form handling framework but you want to customize the HTML generation of those form fields, and define your own validation.
With WTForms, your form field HTML can be generated for you, but we let you customize it in your templates. This allows you to maintain separation of code and presentation, and keep those messy parameters out of your python code. Because we strive for loose coupling, you should be able to do that in any templating engine you like, as well.
Download / Installation
The easiest way to install WTForms is by using easy_install or pip:
easy_install WTForms pip install WTForms
You can also download WTForms manually
from PyPI and then run python setup.py install.
If you're the sort that likes to risk it all and run the latest version from Mercurial, you can grab a packaged up version of the tip, or head over to Bitbucket and clone the repository.
Crash Course
Let's get right down to business and define our first form:
from wtforms import Form, BooleanField, TextField, validators class RegistrationForm(Form): username = TextField('Username', [validators.Length(min=4, max=25)]) email = TextField('Email Address', [validators.Length(min=6, max=35)]) accept_rules = BooleanField('I accept the site rules', [validators.Required()])
When you create a form, you define the fields in a way that is similar to the way many ORM’s have you define their columns; By defining class variables which are instantiations of the fields.
Because forms are regular Python classes, you can easily extend them as you would expect:
class ProfileForm(Form): birthday = DateTimeField('Your Birthday', format='%m/%d/%y') signature = TextAreaField('Forum Signature') class AdminProfileForm(ProfileForm): username = TextField('Username', [validators.Length(max=40)]) level = IntegerField('User Level', [validators.NumberRange(min=0, max=10)])
Via subclassing, AdminProfileForm, gains all the fields already defined in
ProfileForm. This allows you to easily share common subsets of fields between
forms, such as the example above, where we are adding admin-only fields to
ProfileForm.
Using Forms
Using a form is as simple as instantiating it. Consider the following
django-like view, using the RegistrationForm we defined earlier:
def register(request): form = RegistrationForm(request.POST) if request.method == 'POST' and form.validate(): user = User() user.username = form.username.data user.email = form.email.data user.save() redirect('register') return render_response('register.html', form=form)
First, we instantiate the form, providing it with any data available in
request.POST. We then check if the request is made using POST, and if it is,
we validate the form, and check that the user accepted the rules. If successful,
we create a new User and assign the data from the validated form to it, and save
it.
That takes care of forms for new entries, but what if we want to edit an existing object? Easy:
def edit_profile(request): user = request.current_user form = ProfileForm(request.POST, user) if request.method == 'POST' and form.validate(): form.populate_obj(user) user.save() redirect('edit_profile') return render_response('edit_profile.html', form=form)
Here, we instantiate the form by providing both request.POST and the user object
to the form. By doing this, the form will get any data that isn't present in the
post data from the user object.
We're also using the form's populate_obj method to re-populate the user
object with the contents of the validated form. This method is provided for
convenience, for use when the field names match the names on the object you're
providing with data. Typically, you will want to assign the values manually, but
for this simple case it's perfect. It can also be useful for CRUD and admin
forms.
Form Data Population
In addition to providing data using the first two arguments (formdata and
obj), you can pass keyword arguments to populate the form. Note though that a
few names are reserved: formdata, obj, prefix and idprefix.
formdata takes precendence over obj, which itself takes precedence over
keyword arguments. For example:
def change_username(request): user = request.current_user form = ChangeUsernameForm(request.POST, user, username='silly') if request.method == 'POST' and form.validate(): user.username = form.username.data user.save() redirect('change_username') return render_response('change_username.html', form=form)
While you almost never use all three methods together in practice, it
illustrates how WTForms looks up the username field:
- Check if
request.POSThas ausernamekey. - Check if
userhas an attribute namedusername. - Check if a keyword argument named
usernamewas provided. - Finally, if everything else fails, use the default value provided by the field, if any.
Validators
Validation in WTForms is done by providing a field with a set of validators to
run when the containing form is validated. You provide these via the field
constructor's second argument, validators:
class ChangeEmailForm(Form): email = TextField('Email', [validators.Length(min=6, max=120), validators.Email()])
You can provide any number of validators to a field. Typically, you will want to provide a custom error message:
class ChangeEmailForm(Form): email = TextField('Email', [ validators.Length(min=6, message=_(u'Little short for an email address?')), validators.Email(message=_(u'That\'s not a valid email address.')) ])
It is generally preferable to provide your own messages, as the default messages by necessity are generic. This is also the way to provide localised error messages.
For a list of all the built-in validators, check the documentation.
Rendering Fields
Rendering a field is as simple as coercing it to a string:
>>> from wtforms import Form, TextField >>> class SimpleForm(Form): ... content = TextField('content') ... >>> form = SimpleForm(content='foobar') >>> str(form.content) '<input id="content" name="content" type="text" value="foobar" />' >>> unicode(form.content) u'<input id="content" name="content" type="text" value="foobar" />'
However, the real power comes from rendering the field with its __call__
method. By calling the field, you can provide keyword arguments, which will be
injected as html parameters in the output:
>>> form.content(style="width: 200px;", class_="bar") u'<input class="bar" id="content" name="content" style="width: 200px;" type="text" value="foobar" />'
Now let's apply this power to rendering a form in a Jinja template. First, our form:
class LoginForm(Form): username = TextField('Username') password = PasswordField('Password') form = LoginForm()
And the template:
<form method="POST" action="/login"> <div>{{ form.username.label }}: {{ form.username(class="css_class") }}</div> <div>{{ form.password.label }}: {{ form.password() }}</div> </form>
Alternately, if you're using Django, you can use the form_field templatetag we
provide in our Django extension, when you want to pass keyword arguments:
{% load wtforms %} <form method="POST" action="/login"> <div> {{ form.username.label }}: {% form_field form.username class="css_class" %} </div> <div> {{ form.password.label }}: {{ form.password }} </div> </form>
Both of these will output:
<form method="POST" action="/login"> <div> <label for="username">Username</label>: <input class="css_class" id="username" name="username" type="text" value="" /> </div> <div> <label for="password">Password</label>: <input id="password" name="password" type="password" value="" /> </div> </form>
WTForms is template engine agnostic, and will work with anything that allows
attribute access, string coercion, and/or function calls. The form_field
templatetag is provided as a convenience as you can't pass arguments in Django
templates.
Displaying Errors
Now that we have a template for our form, let's add error messages:
<form method="POST" action="/login"> <div>{{ form.username.label }}: {{ form.username(class="css_class") }}</div> {% if form.username.errors %} <ul class="errors">{% for error in form.username.errors %}<li>{{ error }}</li>{% endfor %}</ul> {% endif %} <div>{{ form.password.label }}: {{ form.password() }}</div> {% if form.password.errors %} <ul class="errors">{% for error in form.password.errors %}<li>{{ error }}</li>{% endfor %}</ul> {% endif %} </form>
If you prefer one big list of errors at the top, this is also easy:
{% if form.errors %} <ul class="errors"> {% for field_name, field_errors in form.errors if field_errors %} {% for error in field_errors %} <li>{{ form[field_name].label }}: {{ error }}</li> {% endfor %} {% endfor %} </ul> {% endif %}
As error handling can become a rather verbose affair, it is preferable to use Jinja macros (or equivalent) to reduce boilerplate in your templates. (example)
Custom Validators
There are two ways to provide custom validators. By defining a custom validator and using it on a field:
from wtforms.validators import ValidationError def is_42(form, field): if field.data != 42: raise ValidationError('Must be 42') class FourtyTwoForm(Form): num = IntegerField('Number', [is_42])
Or by providing an in-form field-specific validator:
class FourtyTwoForm(Form): num = IntegerField('Number') def validate_num(form, field): if field.data != 42: raise ValidationError(u'Must be 42')
For more complex validators that take parameters, check the documentation.
Next Steps
The crash course has just skimmed the surface on how you can begin using WTForms to handle form input and validation in your application. For more information, you'll want to check the following:
- The WTForms documentation has API documentation for the entire library.
- Solving specific problems can help you tackle specific integration issues with WTForms and other frameworks.
- The mailing list is where you can get help, discuss bugs in WTForms, and propose new features.
