File: crash_course.rst

package info (click to toggle)
wtforms 3.2.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,064 kB
  • sloc: python: 5,264; makefile: 27; sh: 17
file content (391 lines) | stat: -rw-r--r-- 14,446 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
Crash Course
============

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
-----------------------

WTForms is available through `PyPI`_. Install it using pip::

    pip install WTForms

.. _PyPI: https://pypi.org/project/WTForms/


Key Concepts
------------

 - :class:`Forms <wtforms.form.Form>` are the core container of WTForms. Forms
   represent a collection of fields, which can be accessed on the form
   dictionary-style or attribute style.
 - :mod:`Fields <wtforms.fields>` do most of the heavy lifting. Each field represents a *data type*
   and the field handles coercing form input to that datatype. For example,
   `IntegerField` and `StringField` represent two different data types. Fields
   contain a number of useful properties, such as a label, description, and a
   list of validation errors, in addition to the data the field contains.
 - Every field has a :mod:`Widget <wtforms.widgets>` instance. The widget's job
   is rendering an HTML representation of that field. Widget instances can be
   specified for each field but every field has one by default which makes
   sense. Some fields are simply conveniences, for example
   :class:`~wtforms.fields.TextAreaField` is simply a
   :class:`~wtforms.fields.StringField` with the default widget being a
   :class:`~wtforms.widgets.TextArea`.
 - In order to specify validation rules, fields contain a list of
   :mod:`Validators <wtforms.validators>`.

Getting Started
---------------

Let's get right down to business and define our first form::

    from wtforms import Form, BooleanField, StringField, validators

    class RegistrationForm(Form):
        username     = StringField('Username', [validators.Length(min=4, max=25)])
        email        = StringField('Email Address', [validators.Length(min=6, max=35)])
        accept_rules = BooleanField('I accept the site rules', [validators.InputRequired()])

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 = StringField('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.


Editing existing objects
^^^^^^^^^^^^^^^^^^^^^^^^

Our earlier registration example showed how to accept input and validate it 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.


Exploring in the console
^^^^^^^^^^^^^^^^^^^^^^^^

WTForms forms are very simple container objects, and perhaps the easiest way to
find out what's available to you in a form is to play around with a form in the
console:

.. code-block:: pycon

    >>> from wtforms import Form, StringField, validators
    >>> class UsernameForm(Form):
    ...     username = StringField('Username', [validators.Length(min=5)], default='test')
    ...
    >>> form = UsernameForm()
    >>> form['username']
    <wtforms.fields.core.StringField object at ...>
    >>> form.username.data
    'test'
    >>> form.validate()
    False
    >>> form.errors
    {'username': ['Field must be at least 5 characters long.']}

What we've found here is that when you instantiate a form, it contains
instances of all the fields, which can be accessed via either dictionary-style
or attribute-style. These fields have their own properties, as does the enclosing form.

When we validate the form, it returns False, meaning at least one validator was
not satisfied. :attr:`form.errors <wtforms.form.Form.errors>` will give you a
summary of all the errors.

.. code-block:: pycon

    >>> form2 = UsernameForm(username='Robert')
    >>> form2.data
    {'username': 'Robert'}
    >>> form2.validate()
    True

This time, we passed a new value for username when instantiating UserForm, and
it was sufficient to validate the form.


How Forms get data
~~~~~~~~~~~~~~~~~~

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`, and `prefix`.

`formdata` takes precedence 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()
            return 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:

1. If a form was submitted (request.POST is not empty), process the form
   input. Even if there was no form input for this field in particular, if
   there exists form input of any sort, then we will process the form input.

2. If there was no form input, then try the following in order:

   1. Check if `user` has an attribute named `username`.
   2. Check if a keyword argument named `username` was provided.
   3. 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 = StringField('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 = StringField('Email', [
            validators.Length(min=6, message=_('Little short for an email address?')),
            validators.Email(message=_('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 :mod:`Validators Reference <wtforms.validators>`


Rendering Fields
~~~~~~~~~~~~~~~~

Rendering a field is as simple as coercing it to a string::

    >>> from wtforms import Form, StringField
    >>> class SimpleForm(Form):
    ...   content = StringField('content')
    ...
    >>> form = SimpleForm(content='foobar')
    >>> str(form.content)
    Markup('<input id="content" name="content" type="text" value="foobar">')

However, the real power comes from rendering the field with its :meth:`~wtforms.fields.Field.__call__`
method. By calling the field, you can provide keyword arguments, which will be
injected as html attributes in the output::

    >>> form.content(style="width: 200px;", class_="bar")
    Markup('<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 = StringField('Username')
        password = PasswordField('Password')

    form = LoginForm()

.. _Jinja: https://jinja.palletsprojects.com/

And the template:

.. code-block:: html+jinja

    <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 templates, you can use the `form_field`
templatetag we provide in our Django extension, when you want to pass keyword
arguments:

.. code-block:: html+django

    {% 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:

.. code-block:: html

    <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:

.. code-block:: html+jinja

    <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:

.. code-block:: html+jinja

    {% if form.errors %}
        <ul class="errors">
            {% for field_name, field_errors in form.errors|dictsort 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.
(:ref:`example <jinja-macros-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('Must be 42')

For more complex validators that take parameters, check the :ref:`custom-validators` section.

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 :doc:`WTForms documentation <index>` has API documentation for the entire library.
 - :doc:`specific_problems` can help you tackle specific
   integration issues with WTForms and other frameworks.