pythonformswtformsflask-wtforms

WTForms: populate form with data if data exists


I have the following Flask-WTF form:

class PersonForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()])
    age = IntegerField('Age', validators=[NumberRange(min=0)], render_kw={'type': 'number'})
    educated = BooleanField('Educated', render_kw={'type': 'checkbox'})

I know I can pre-populate the form by passing values into the form like so:

form = PersonForm(name='Johnny', age=25, educated=True)

I've noticed there's a better way to do this by injecting an object into the form (references here and here). I've tried the following, however, it doesn't work. Where am I going wrong (is the object supposed to be something other than a dictionary)?

person = {'name': 'Johnny', 'age': 25, 'educated'=True}
form = PersonForm(obj=person)

Note these pre-populated values come from a database. Some values are defined while some aren't. For example, another "person" may look like {'name': 'Jessica', 'educated': True} (the age field will be empty in this case).


Solution

  • I think you want to use the data parameter instead, as mentioned in this documentation:

    The Form class

    class wtforms.form.Form

    Declarative Form base class. Construction

    __init__(formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs)
        Parameters:   
    

    ...

    data – Accept a dictionary of data. This is only used if formdata and obj are not present. ...

    Demonstration:

    >>> from wtforms import Form, StringField, validators
    >>>
    >>> class UsernameForm(Form):
        username = StringField('Username', [validators.Length(min=5)], default=u'test')
        email = StringField('Email', [validators.email(message='Check your email')], default=u'test@domain.com')
    >>>
    >>> person = {'username': 'Johnny', 'email': 'Johny@domain.net'}
    >>>
    >>> form = UsernameForm(data=person)
    >>> 
    >>> form.username.data
    'Johnny'
    >>> 
    >>> form.email.data
    'Johny@domain.net'
    

    It does work with the formdata parameter as well, but you will have to pass a MultiDict object:

    >>> from werkzeug.datastructures import MultiDict
    >>> 
    >>> b = MultiDict(person)
    >>> 
    >>> b
    MultiDict([('email', 'Johny@domain.net'), ('username', 'Johnny')])
    >>> 
    >>> 
    >>> form2 = UsernameForm(formdata=b)
    >>> form2.username.data
    'Johnny'
    >>> form2.email.data
    'Johny@domain.net'
    >>> 
    

    Also with **kwargs passed as a regular dictionary:

    >> form3 = UsernameForm(**person)
    >>> 
    >>> form3.username.data
    'Johnny'
    >>> 
    >>> form3.email.data
    'Johny@domain.net'
    

    EDIT: in reply to OP's comment concerning the use of obj parameters and quoting from docs:

     __init__(formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs)
    

    Parameters:
    ...

    obj – If formdata is empty or not provided, this object is checked for attributes matching form field names, which will be used for field values.

    ...

    That means you need to pass in an object with attribute names same as your forms' attribute name, as follows:

    >>> class Person:
            username = 'Johny'
            email = 'Johny@domain.net'
    
    >>> 
    >>> form = UsernameForm(obj=Person)
    >>> 
    >>> form.data
    {'email': 'Johny@domain.net', 'username': 'Johny'}