Django: Dynamic checkbox/select options in form

Usually when you create a form in Django, you have to know what items you want to allow as the initial values, otherwise it'll give you an invalid_choice error:

Select a valid choice. 'X' is not one of the available choices.

Normally it's fine, it's just some dope changing values on their browser and messing with your form by sending dodgy data.

But things get a little more complicated when you NEED dynamic choices in the form. For example, I want a form that will allow the user to list a bunch of cars with a given set of features. The "manufacturers" list we can generate using ModelChoiceField, but when you select one, it uses Ajax to dynamically change the "features" options.

note: This example is more verbose than it needs to be. That's just to help you understand what's going on. You can trim off the excess fat once you've got a hang of things.

Firstly, subclass the django.forms.MultipleChoiceField class.

class DynamicMultipleChoiceField(forms.MultipleChoiceField):
# The only thing we need to override here is the validate function. 
    def validate(self, value):
if self.required and not value:
raise ValidationError(self.error_messages['required'])

# Twig: This is part of the original code that we don't want.
# Validate that each value in the value list is in self.choices.
#for val in value:
# if not self.valid_value(val):
# raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})

Now in your form, change the existing field of choice to use the new DynamicMultipleChoiceField:

class CarForm(forms.Form):
manufacturer = forms.ModelChoiceField(queryset = Car.objects.order_by('name'))
features = DynamicMultipleChoiceField(widget = forms.CheckboxSelectMultiple(), choices = [])

def filter_features(self, data):
items = Features.objects.order_by('name')
query = data.get('filter_manufacturer_id', 0)

if query:
manufacturer = Manufacturer.objects.get(pk = query)
items = items.filter(manufacturer = manufacturer)

self.fields['features'].choices = [ (, for item in items ]

What we've done here is set the field to be our DynamicMultipleChoiceField, but use the CheckboxSelectMultiple widget to render it.

When the manufacturer list changes, use Ajax to post the selected manufacturer information (your choice of either ID or name). Assuming you've set it up correctly, you can replace the whole choice field with the output from this view:

def ajax_features_field(request):
form = CarForm()

return HttpResponse("%s" % form['features'])

All the view does is return the rendered HTML for just the features field. If you've customised the form, add a little wrapper  class/ID so you can use jQuery to easily replace the whole field.

Feature: I want a car that can park like Japan!


Copyright © Twig's Tech Tips
Theme by BloggerThemes & TopWPThemes Sponsored by iBlogtoBlog