Simple wrapper to add "dynamic" (sets of) fields to an already instantiated WTForms form.
WTForms Dynamic Fields
Simple wrapper to add “dynamic” (sets of) fields to an already instantiated WTForms form.
Simply use pip to install:
pip install wtforms-dynamic-fields --pre
The ‘–pre’ flag is necessary until this module has an official release.
Then include it in your project:
A few notes before using this module
If you simply want to add one field to an already existing form, it may be less overhead to simply use setattr:
setattr(Form, field_name, TextField(field_label, validators=[InputRequired()]))
Doing so will attach a text field, with one validator to the “Form” object. This module is intended for slightly more complex scenarios and to offer an easier way of configuration.
Also, this module, in its current state, is developed to scratch a personal itch - simple server side validation of dynamic fields (through WTForms itself). It is most likely missing some needed flexibility and/or features, so do not hesitate to pinch in or drop me a line!
Adding a field
The method add_field() is used to add a field to the modules configuration.
Usage: add_field(‘machine name’, ‘label name’, WTFormField, *args, **kwargs)
Adding a validator
The method add_validator() is used to add a validator to an added field configuration.
Usage: add_validator(‘field_machine_name’, WTFormValidator, *args, **kwargs)
- Decorate field machine name arguments with %’s (%some_field_machine_name%) to have them automatically suffixed with a set number if applicable. More on this below.
Apply the configuration to a form
Once you have setup your configuration using the above methods, you can apply it to any valid WTForm instance.
Usage: process(ValidFormClass, POST)
Note that POST has to be a MultiDict, which is already the case with most frameworks like Flask, Django, …
The idea behind this module is that you can add “dynamic” fields to a form that has already been created. “Dynamic” here means fields that are not rendered (nor present in the original form object) initially, but get injected into the DOM afterwards.
The module uses the POST variables together with a user defined configuration to determine which fields are new and are allowed to be processed.
The first thing you need, obviously, is a valid WTForms instance to put the new fields on. Say, for example, we have a form that contains a first name and a last name field, this would be declared as follows:
from wtforms import Form, TextField from wtforms.validators import InputRequired class PersonalFile(Form): """ A personal file form. """ first_name = TextField('First name, validators=[InputRequired()]) last_name = TextField('Last name, validators=[InputRequired()])
When we present this form to our user, we wish to have the ability to optionally add an email address and make it required once added.
However, we also want this new email address field to be correctly validated on the server and, in case when validation fails, be rendered back to the user for inspection. We do not want to write our own validation code for this field, but leverage the power of the already present, full-blown WTForms form library to do the heavy lifting.
This is where this module steps in.
First you will need an instance of the module:
dynamic = WTFormsDynamicFields()
Next you will need to build the configuration which will hold the allowed, dynamic fields (and their validators). To do this, you use the “add_field” method: define the fields machine name, the label and finally a WTForms field type:
dynamic.add_field('email', 'Email address', TextField)
Optionally, you can pass *args and **kwargs to the field as well.
If needed, you can also apply optional validators by using the “add_validator” method. You define on which field you wish to apply the validator and you pass in a WTForms validator:
dynamic.add_validator('email', InputRequired, message='This field is required')
Here too you have the ability to pass in optional *args and **kwargs to the validator. Again, no parenthesis after InputRequired, its arguments will be bound by the module later on.
Now that you have added this email field and pushed a validator on it, you are ready to process your form. For the form to be processed, you will need your original form (PersonalFile in our case) and the POST that comes back from the server.
Normally, you would bind your form variable directly to the WTForm instance:
form = PersonalFile()
To enable this module to process you form, however, you simply need to wrap its “process” method around it and add the incoming POST:
form = dynamic.process(PersonalFile, request.post)
Now the form will pick up the optional email field when injected and make the validation fail server side if the field is left empty. Removing the field from the DOM will make your form pass validation again (given that you filled in the first_name and last_name fields, that is).
Usage with sets
Now imagine the use case where you which to capture not one, but an undefined amount of email address in that same form and have them all validated correctly.
With WTForms Dynamic Fields, this is trivial as the module supports sets - multiple fields of the same kind. To support these sets in your forms, you only need to uphold a simple naming convention: “_X” where X is a number.
If we would add, say, four email fields, these HTML inputs would look like this:
<input type="text" name="email_1" /> <input type="text" name="email_2" /> <input type="text" name="email_3" /> <input type="text" name="email_4" />
The fun fact is, you would not have to change anything to the code we used in the previous example. The module will derive the canonical name of each field (“email” in this case) and apply the user defined configuration for the email field to each individually.
Advanced usage with sets
A more complex scenario could occur when you would have a set comprised out of two or more fields that are dependent on one another.
For example, to elaborate on our email scenario from above, imagine we wish to also capture a telephone number with each email. But, to up the stakes, we only allow one of the two fields to be filled in.
This would require a dependency between the two fields - a validator which checks if its field is filled in and the other one is not. Such a validator would take the other field’s name as an argument:
The above (fictional) validator would be put on the “telephone” field to check if the email field was left empty.
Now if you have multiple sets of these fields, each field name will be suffixed with a number, like we have seen before:
<div><input type="text" name="email_1" /><input type="text" name="telephone_1" /></div> <div><input type="text" name="email_2" /><input type="text" name="telephone_2" /></div> <div><input type="text" name="email_3" /><input type="text" name="telephone_3" /></div> <div><input type="text" name="email_4" /><input type="text" name="telephone_4" /></div>
So which field machine name would you have to pass to the validator in such a use case?
For this, the WTForms Dynamic Fields module provides the ability to wrap a field name argument with % signs.
dynamic.add_field('telephone, 'Telephone number, TextField) dynamic.add_validator('telephone, RequiredIfEmpty, '%email%')
The module detects when it is processing a set of fields (derived from the “X” naming convention) and as such, when wrapping your field name with % signs, will append the correct suffix to the field when binding the arguments to the validator. So if we would be looking at email4, once expanded, the above code will translate to:
telephone_4 = TextField('Telephone number', validators=[RequiredIfEmpty('email_4')])