WTForms

form

WTForms is a framework-agnostic library handling web forms in Python: define a form as a class, validate incoming data, render fields in templates.

Note: WTForms is still maintained, but it is a legacy choice at this point. For new projects, prefer your framework’s own forms (Django) or Pydantic-based validation (FastAPI). Kept here as a reference.

Official documentation: WTForms documentation

Define a form

import wtforms

class UrlFilterForm(wtforms.Form):
    scheme = wtforms.StringField()
    netloc = wtforms.StringField()
    path = wtforms.StringField()
    query = wtforms.StringField()
    fragment = wtforms.StringField()

Handle a request

The same handler serves the empty form (GET) and the submission (POST):

def handle(self, request):
    form = MyForm(request.form)

    if request.method == 'POST' and form.validate():
        # validation succeeded, handle success here
        return self.redirect('...')

    return self.render_template('...', form=form)

Render a form in a Jinja template

Fields and labels are callable and return safe markup (markupsafe.Markup), so no | safe filter is needed. Keyword arguments become HTML attributes:

<form class="form-inline" method="post" action="">
    {% for field in form %}
        <div class="form-group">
            {{ field.label }}{% if field.flags.required %}*{% endif %}
            {{ field(class='form-control') }}
        </div>
    {% endfor %}
    <button type="submit" class="btn btn-outline-primary">Submit</button>
</form>

To render validation errors, each field exposes an errors list:

<div class="form-group {% if form.some_field.errors %}has-error{% endif %}">
    {{ form.some_field.label(class="col-sm-2 control-label") }}
    <div class="col-sm-10">
        {{ form.some_field(class='form-control') }}
        {% if form.some_field.errors %}
            <ul class="errors">{% for error in form.some_field.errors %}<li>{{ error }}</li>{% endfor %}</ul>
        {% endif %}
    </div>
</div>

Override a default once the form is instantiated

Setting form.my_field.default after instantiation has no effect: the default is copied into the field data at instantiation time. Set the data instead:

form.my_field.data = '...'

Add errors after form validation

field.errors is a plain list after validate(), so you can append to it; the error also shows up in form.errors:

form.my_field.errors.append('Caramba!')

Verified on WTForms 3.2.2.

Related