James Gardner: Home > Blog > 2007 > Multiple Checkboxes With FormEncode

Multiple Checkboxes With FormEncode

Posted:2007-09-19 21:04
Tags:Pylons, Python, Web

Following on from my previous post about representing multiple subtopics as a comma separated list in a table field using a custom aggregate function in PostgreSQL, this post is about how to use FormEncode to validate a UI for the structure.

Our form consists of two fields: the first is the topic name and the second are the subtopics which are implemented as a load of checkboxes which the user can tick depending on which subtopics are relevant.

For the sake of this discussion the topic field is just a hidden field with the name of the topic although in real life it might be a dropdown select box which when changed, triggers an AJAX callback to load the appropriate sub-topic checkboxes.

The FormEncode schema looks like this:

class StudyTopic(formencode.Schema):
    allow_extra_fields = True
    filter_extra_fields = True

    topic = formencode.validators.String()
    subtopics = formencode.ForEach(formencode.validators.Int())

The important thing here is to notice the use of the ForEach validator which will apply the Int validator to each of the values submitted for the subtopics field.

If this was our HTML with Dementia and Parkinson's checked:

<input type="hidden" name="topic" value="disease" />

<input type="checkbox" name="subtopics" value="1" checked="checked" />Dementia
<input type="checkbox" name="subtopics" value="2" />Huntington's Disease
<input type="checkbox" name="subtopics" value="3" />Motor neurone disease
<input type="checkbox" name="subtopics" value="4" checked="checked" />Parkinson's Disease

Then the values 1 and 4 would be submitted for the subtopics and the value disease would be submitted for the topic.

If you ran the values through the schema you would get this:

{'topic': 'disease', subtopics': [1,4]}

Say you later want to repopulate the same HTML with these values:

defaults = {'topic': 'disease', 'subtopics': [1,3]}
errors = {}

You can do so like this:

htmlfill.render(html, defaults=defaults, errors=errors)

This works fine because HTMLFill knows how to handle the multiple subtopics with lots of checkboxes of different values.

Now imagine a variation on this theme where rather than using ForEach to handle validation of any number of integers, you instead want to use it to have a repeating set of fields. Consider the Study schema below which uses ForEach to check any number of Person sets of fields:

class Person(Schema):
    firstname = String(not_empty=True)
    surname = String(not_empty=True)

class Study(Schema):
    allow_extra_fields = True
    filter_extra_fields = True
    pre_validators = [NestedVariables()]
    start_date = Date()
    people = ForEach(Person())

This time we are not just iterating over a single field, each set of Person fields contains both a firstname and a surname field. To handle this situation we need to name the fields which make up each Person according to the FormEncode nested variables specification.

Now when the form is submitted the NestedVariables pre-validator will decode the Person field names to produce a data structure that ForEach can validate. After validation you might get a data structure which looks like this:

{
    'start_date': date(2007,9,17),
    'people': [
        {'firstname': 'james', 'surname': 'gardner'},
        {'firstname': 'ian', 'surname': 'gardner'}
    ]
}

This is all very well but remember your form fields had to be named according to a certain specification in order for this to work so you have to do some extra work to get the values back into a format which HTMLFill can use. You do so like this:

defaults = variable_encode(defaults, add_repetitions=False)

You'll also need to so the same to any errors:

errors = variable_encode(errors, add_repetitions=False)

Now you can use HTMLFill as normal:

return htmlfill.render(html, defaults=defaults, errors=errors)

This is a very handy trick if you want to use repeating sets of fields with FormEncode.

One question this begs though is whether you use the NestedVariables and variable_encode technique we've just used to validate the checkboxes used in the first example. The answer is "not easily".

The thing about the first example using checkboxes is that values are only submitted when the checkbox is ticked. This means only values which have been ticked will eventually get passed to HTMLFill. If you are using variable_encode HTMLFill will expect a value for each checkbox to be passed to it in the order the values should be set. Since there are missing values HTMLFill will not be able to match the ticked values to the names of the checkboxes except in the special case where you tick all the boxes up to a certain point and don't tick any after that point in which case the submitted values happen to map correctly to the checkbox names.

You should therefore decide what sort of behaviour you want from ForEach, whether it is to validate a repeating set of fields, or to handle a checkbox group and then plan whether or not to use NestedVariables and variable_encode accordingly. Is this a limitation in FormEncode`` which needs fixing?

(view source)

James Gardner: Home > Blog > 2007 > Multiple Checkboxes With FormEncode