I keep forgetting the details of how one customizes the choices in a `forms.ChoiceField` per instance. The [`forms.ChoiceField` documentation][choices] says the required argument has to be an iterable but then moves straight to the next section.
Fortunately this was covered long ago by [James Bennett][ubernostrum] when Django’s newforms module was introduced. See [__Getting dynamic model choices in newforms__ on Django Snippets][snippets].
The following form example has a field for picking a letter of the alphabet (works for [Django 1.0][django]). The choices are limited to 3 letters only, picked at random and different for each form instance:
from django import forms
def letter_choices(max_choices=3):
“””Return a random list of max_choices letters of the alphabet.”””
import string, random
for l in random.sample(string.ascii_uppercase, max_choices):
yield (l, l)
class LetterForm(forms.Form):
“””Pick a letter from a small, random set.”””
letter = forms.ChoiceField(choices=letter_choices())
def __init__(self, *args, **kwargs):
super(LetterForm, self).__init__(*args, **kwargs)
self.fields[‘letter’].choices = letter_choices()
So that works. The `LetterForm class` uses the helper function `letter_choices` to provide the random choices, which actually returns a generator object rather than a list or tuple of choice pairs. I am relying on Django’s base `ChoiceField` class [calling `list()` on the choices][fields_py] when each form is instantiated, so having `letter_choices` return a generator is hunky dory.
>>> f1 = LetterForm()
>>> f1[‘letter’]
>>> print f1[‘letter’]
>>> f2 = LetterForm()
>>> print f2[‘letter’]
>>>
Now the only thing is… this example is not practical. Using genuinely random choices means that the valid choices on the form submitted by the user will be different to the valid choices on the form used to validate the user input on the next request, and this will likely raise a ValidationError.
*__Django feature suggestion__: allow choices to be any iterable or callable, calling it as appropriate when instantiating the field. If it is callable you could pass a function which returns an iterable at that point, which would save one having to write an `__init__` method for the form sub-class.*
[choices]: http://docs.djangoproject.com/en/dev/ref/forms/fields/#choicefield
[django]: http://www.djangoproject.com/
[fields_py]: http://code.djangoproject.com/browser/django/tags/releases/1.0.2/django/forms/fields.py#L634
[snippets]: http://www.djangosnippets.org/snippets/26/
[ubernostrum]: http://www.b-list.org/
Pingback: Reliably Broken » The gap between Filemaker and Django
Pingback: Reliably Broken » Using an object for Django’s ChoiceField choices