How to pre-populate checkboxes with Flask/WTForms
Asked Answered
B

5

10

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.

Barge answered 24/10, 2013 at 11:0 Comment(0)
H
7

I think jeverling's answer is very close and led my to a tested solution. I needed items to remain checked, but each time the url is serviced, the checkbox items are cleared unless you can specify the selections.

The important part is ChoiceObj (was MyObj above) inheriting from object so that setattr can be called on it. To make this work, the arguments to setattr(obj, attribute, value) where

  • obj is the ChoiceObj instance
  • attribute is the name of the form
  • value set in the list of choices.

color.py:

from flask.ext.wtf import Form
from flask import Flask, render_template, session, redirect, url_for
from wtforms import SelectMultipleField, SubmitField, widgets

SECRET_KEY = 'development'

app = Flask(__name__)
app.config.from_object(__name__)

class ChoiceObj(object):
    def __init__(self, name, choices):
        # this is needed so that BaseForm.process will accept the object for the named form,
        # and eventually it will end up in SelectMultipleField.process_data and get assigned
        # to .data
        setattr(self, name, choices)

class MultiCheckboxField(SelectMultipleField):
    widget = widgets.TableWidget()
    option_widget = widgets.CheckboxInput()

    # uncomment to see how the process call passes through this object
    # def process_data(self, value):
    #     return super(MultiCheckboxField, self).process_data(value)

class ColorLookupForm(Form):
    submit = SubmitField('Save')
    colors = MultiCheckboxField(None)

allColors = ( 'red', 'pink', 'blue', 'green', 'yellow', 'purple' )

@app.route('/', methods=['GET', 'POST'])
def color():
    selectedChoices = ChoiceObj('colors', session.get('selected') )
    form = ColorLookupForm(obj=selectedChoices)
    form.colors.choices =  [(c, c) for c in allColors]

    if form.validate_on_submit():
        session['selected'] = form.colors.data
        return redirect(url_for('.color'))
    else:
        print form.errors
    return render_template('color.html',
                           form=form,
                           selected=session.get('selected'))

if __name__ == '__main__':
    app.run()

And templates/color.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <form method="post">
        <table>
            <tr>
                <td>
                    {{ form.hidden_tag() }}
                    {{ form.colors }}
                </td>
                <td width="20"></td>
                <td>
                    <b>Selected</b><br>
                    {% for s in selected %}
                        {{ s }}<br>
                    {% endfor %}
                </td>
            </tr>

        </table>
        <input type="submit">
    </form>
</body>
</html>
Histogram answered 22/2, 2015 at 6:42 Comment(5)
I won't vote on this, as I'm not using Flask/WTForma anymore.Barge
This is such a fantastic piece of code. I've used a modified version of it (removed session references with database queries). However, I have absolutely no idea how it works. Could you give some explanation or provide some links?Unconcern
This was such great help in 2019!Truthfunction
@EricWilson May I ask, what is the reason contra Flask/WTF?Panier
@Panier I am no longer working on the project in which I was using this library.Barge
B
5

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

Blowtorch answered 1/6, 2014 at 14:22 Comment(2)
I am using this approach in my code but I can't extract data from HiddenField. My form.data looks like this: {'removing_items': [{'pub_id': '<input id="pub_id" name="pub_id" type="hidden" value="1">', 'remove': True}, {'pub_id': '<input id="pub_id" name="pub_id" type="hidden" value="9">', 'remove': True}, {'pub_id': '<input id="pub_id" name="pub_id" type="hidden" value="14">', 'remove': True}], 'submit': True}Chaddie
Yeah as someone pointed out in another answer BooleanField and SubmitField can't be used properly in a FieldList. They will render properly but will fail on submission.Blowtorch
C
2

I thinke the approch using a internal subclass should work for this problem:

http://wtforms.simplecodes.com/docs/1.0.1/specific_problems.html#dynamic-form-composition

You will have to set the choices before you init the form, which you can do with the internal subclass. Then you can pass an object to the Form, that will be used by wtforms to prepopulate your fields:

def foo():
    class F(FooForm):
        pass
    choices = [(foo.id, foo.label) for foo in foos]
    F.bar.choices = choices

    class MyObj(object):
        pass
    obj = MyObj()
    for choice in choices:
        setattr(obj, choice, True)

    form = F(request.POST or None, obj=obj)

Please not that this is untested, but I think it should work.
Good luck!

Chaim answered 24/10, 2013 at 13:18 Comment(0)
B
2

WTForms' SelectMultipleField determines whether or not a box is checked by comparing the value of each item (foo.id in your example) with the field's .data list. (Check the WTForms source code to verify this.)

I solved the problem like so. My goal was to precheck every box, so I included that option, commented out:

@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]
    if form.is_submitted():
        for x in form.bar.data:
            #ACT ON EACH USER-SELECTED ITEM HERE
    else: #precheck the boxes here
        form.bar.data = [1,2,5,8] #id's of items to be prechecked on form
        #form.bar.data = [x[0] for x in form.bar.choices] #this prechecks every box
    return render_template('foo.html', 
                           foos=foos,
                           form=form)
Bullyrag answered 25/2, 2021 at 21:55 Comment(2)
I hope your answer is helpful to someone. Flask is great, but I haven't used it in at least five years.Barge
Works beautifully. Thank you.Dionisio
B
0

The answer here is the idea that worked in my case: https://mcmap.net/q/617044/-pre-populating-a-booleanfield-as-checked-wtforms

The key is that unlike some html elements where the

form.field(value="value") 

construct is used to set the value dynamically, the key/value parameter "checked" must be used from within the template.html

{{form.booleanField(checked=True)}}
{{form.booleanField(checked=False)}}

or more generally if is_admin is a parameter passed to the template:

{{form.booleanField(checked=is_admin)}}
Bitters answered 1/7, 2024 at 3:20 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.