Johnny Metz
Johnny Metz

Reputation: 5965

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

Upvotes: 1

Views: 6265

Answers (3)

Rob Surf
Rob Surf

Reputation: 31

I used a session variable to pass data from my form on POST back to my template on GET to have form user filled values rendered in case of error on Post or user page refresh - to not clear the form and annoy user.

I had to directly pass the full form.request back to the template and index back out values from that dict for prefilling data in my html template as I have no class defining the fields of my form in my python Flask App. Other cleaner answers make use of a form class and pass that to the html - I'm not so well setup ;)

Relevant Code below - leaving out the unrelated bits in app.py:

if ( request.method == 'POST'):
    session['last_request_data']= request.form
if (form_error):
    return render_template('home.html', prefill_data=session['last_request_data'])

if ( request.method == 'GET'):

if ('last_request_data' in session): 
    print("Prefill form with last data from the session")      
    return render_template('home.html', prefill_data=session['last_request_data'])
else:
    print("Frist time page load - no data to prefill - render empty form")   
    return render_template('home.html')

And then in the html template - I have to check if there was any prefill data was passed each time I try to prefill values using the keys from the form ...

            <input type="text" name="player1name" {% if prefill_data %} value="{{ prefill_data.get("player1name") }}"{% endif %}/> 

Upvotes: 1

Iron Fist
Iron Fist

Reputation: 10951

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'[email protected]')
>>>
>>> person = {'username': 'Johnny', 'email': '[email protected]'}
>>>
>>> form = UsernameForm(data=person)
>>> 
>>> form.username.data
'Johnny'
>>> 
>>> form.email.data
'[email protected]'

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', '[email protected]'), ('username', 'Johnny')])
>>> 
>>> 
>>> form2 = UsernameForm(formdata=b)
>>> form2.username.data
'Johnny'
>>> form2.email.data
'[email protected]'
>>> 

Also with **kwargs passed as a regular dictionary:

>> form3 = UsernameForm(**person)
>>> 
>>> form3.username.data
'Johnny'
>>> 
>>> form3.email.data
'[email protected]'

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 = '[email protected]'

>>> 
>>> form = UsernameForm(obj=Person)
>>> 
>>> form.data
{'email': '[email protected]', 'username': 'Johny'}

Upvotes: 11

Artagel
Artagel

Reputation: 396

You need a MultiDict

>>> a
{'hello': 'data', 'more': 'data'}
>>> from werkzeug.datastructures import MultiDict
>>> b=MultiDict(a)
>>> b
MultiDict([('hello', 'data'), ('more', 'data')])
>>> 

Upvotes: 0

Related Questions