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.
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
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>
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:
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()
<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>
form.data
after POST{'items': [{'item_id': 1, 'want': True}, {'item_id': 2, 'want': False}]}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With