How to pre-populate checkboxes with Flask/WTForms

Eric Wilson picture Eric Wilson · Oct 24, 2013 · Viewed 10.5k times · Source

I'm trying to produce a dynamic checkbox list with certain boxes checked based on the state of the data.

Here's my Form:

class FooForm(Form):
    bar = SelectMultipleField(
        'Bar',
        option_widget=CheckboxInput(),
        widget=ListWidget(prefix_label=True))

Here's the controller:

@app.route('/fooform', methods = ['GET','POST'])
def foo():
    foos = foo_dao.find() 
    form = FooForm()
    form.bar.choices = [(foo.id, foo.label) for foo in foos]
    # SOMEHOW PRE-POPULATE CHECKBOXES HERE
    if form.is_submitted():
        # DO STUFF
    return render_template('foo.html', 
                           foos=foos,
                           form=form)

Here's the template:

  <form action="" method="post" name="foos">
      {{form.bar}}
    <p><input type="submit" value="Add"></p>
  </form>

This produces a checkbox list, and it works, but I can't figure out how to specify which checkboxes in the list are to be pre-populated.

Answer

nsfyn55 picture nsfyn55 · Jun 1, 2014

Use a combination of FormField and FieldList: For the expense of a little bit of extra boiler plate and some hand binding at persist time you can get a similar result by breaking your submission into multiple fields.

This benefits from being a DRYer approach to WTForms. If you adhere to it you will find that your forms work more smoothly. This is because you are working with the default behaviors built into the library. Although the library allows you to mix and match Widget classes its been my experience that there are a rather limited subset of combinations that work well together. If you stick to basic Field/Validator combinations and compose them with FormField/FieldList things work much more nicely.

See the example below:

Code

from collections import namedtuple
from wtforms import Form, FieldList, BooleanField, HiddenField, FormField
from webob.multidict import MultiDict

GroceryItem = namedtuple('GroceryItem', ['item_id', 'want', 'name'])

class GroceryItemForm(Form):
    item_id = HiddenField()
    want = BooleanField()

class GroceryListForm(Form):
    def __init__(self, *args, **kwargs):
        super(GroceryListForm, self).__init__(*args, **kwargs)

        # just a little trickery to get custom labels
        # on the list's checkboxes
        for item_form in self.items:
            for item in kwargs['data']['items']:
                if item.item_id == item_form.item_id.data:
                    item_form.want.label ='' 
                    item_form.label = item.name

    items = FieldList(FormField(GroceryItemForm))

item1 = GroceryItem(1, True, 'carrots')
item2 = GroceryItem(2, False, 'cornmeal')

data = {'items': [item1, item2]}

form = GroceryListForm(data=MultiDict(data))

print form.items()

Raw HTML

<ul id="items">
   <li>
      carrots 
      <table id="items-0">
         <tr>
            <th></th>
            <td><input id="items-0-item_id
               " name="items-0-item_id" type="hidden" value="1"><input checked id="items-0-want" name="it
               ems-0-want" type="checkbox" value="y"></td>
         </tr>
      </table>
   </li>
   <li>
      cornmeal 
      <table id="items
         -1">
      <tr>
         <th></th>
         <td><input id="items-1-item_id" name="items-1-item_id" type="hidden" valu
            e="2"><input id="items-1-want" name="items-1-want" type="checkbox" value="y"></td>
      </tr>
      </t
      able>
   </li>
</ul>

Rendered Result

enter image description here

Result of form.data after POST

{'items': [{'item_id': 1, 'want': True}, {'item_id': 2, 'want': False}]}