Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #4620: you can now easily add custom labels to ModelChoiceField…

…s via subclassing. Thanks, PhiR.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@7326 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 649cdf907d5d23290caf3cc7e34426c39dfa4159 1 parent 9426965
@jacobian jacobian authored
View
43 django/newforms/models.py
@@ -277,19 +277,18 @@ class ModelForm(BaseModelForm):
# Fields #####################################################################
-class QuerySetIterator(object):
- def __init__(self, queryset, empty_label, cache_choices):
- self.queryset = queryset
- self.empty_label = empty_label
- self.cache_choices = cache_choices
+class ModelChoiceIterator(object):
+ def __init__(self, field):
+ self.field = field
+ self.queryset = field.queryset
def __iter__(self):
- if self.empty_label is not None:
- yield (u"", self.empty_label)
+ if self.field.empty_label is not None:
+ yield (u"", self.field.empty_label)
for obj in self.queryset:
- yield (obj.pk, smart_unicode(obj))
+ yield (obj.pk, self.field.label_from_instance(obj))
# Clear the QuerySet cache if required.
- if not self.cache_choices:
+ if not self.field.cache_choices:
self.queryset._result_cache = None
class ModelChoiceField(ChoiceField):
@@ -306,6 +305,7 @@ def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
help_text=None, *args, **kwargs):
self.empty_label = empty_label
self.cache_choices = cache_choices
+
# Call Field instead of ChoiceField __init__() because we don't need
# ChoiceField.__init__().
Field.__init__(self, required, widget, label, initial, help_text,
@@ -321,19 +321,30 @@ def _set_queryset(self, queryset):
queryset = property(_get_queryset, _set_queryset)
+ # this method will be used to create object labels by the QuerySetIterator.
+ # Override it to customize the label.
+ def label_from_instance(self, obj):
+ """
+ This method is used to convert objects into strings; it's used to
+ generate the labels for the choices presented by this object. Subclasses
+ can override this method to customize the display of the choices.
+ """
+ return smart_unicode(obj)
+
def _get_choices(self):
# If self._choices is set, then somebody must have manually set
# the property self.choices. In this case, just return self._choices.
if hasattr(self, '_choices'):
return self._choices
+
# Otherwise, execute the QuerySet in self.queryset to determine the
- # choices dynamically. Return a fresh QuerySetIterator that has not
- # been consumed. Note that we're instantiating a new QuerySetIterator
- # *each* time _get_choices() is called (and, thus, each time
- # self.choices is accessed) so that we can ensure the QuerySet has not
- # been consumed.
- return QuerySetIterator(self.queryset, self.empty_label,
- self.cache_choices)
+ # choices dynamically. Return a fresh QuerySetIterator that has not been
+ # consumed. Note that we're instantiating a new QuerySetIterator *each*
+ # time _get_choices() is called (and, thus, each time self.choices is
+ # accessed) so that we can ensure the QuerySet has not been consumed. This
+ # construct might look complicated but it allows for lazy evaluation of
+ # the queryset.
+ return ModelChoiceIterator(self)
def _set_choices(self, value):
# This method is copied from ChoiceField._set_choices(). It's necessary
View
18 docs/newforms.txt
@@ -1549,15 +1549,23 @@ additional required argument:
``ModelChoiceField``
~~~~~~~~~~~~~~~~~~~~
-Allows the selection of a single model object, suitable for
-representing a foreign key.
+Allows the selection of a single model object, suitable for representing a
+foreign key. The method receives an object as an argument and must return a
+string to represent it.
+
+The labels for the choice field call the ``__unicode__`` method of the model to
+generate string representations. To provide custom labels, subclass ``ModelChoiceField`` and override ``label_for_model``::
+
+ class MyModelChoiceField(ModelChoiceField):
+ def label_from_instance(self, obj):
+ return "My Object #%i" % obj.id
``ModelMultipleChoiceField``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Allows the selection of one or more model objects, suitable for
-representing a many-to-many relation.
-
+Allows the selection of one or more model objects, suitable for representing a
+many-to-many relation. As with ``ModelChoiceField``, you can use
+``label_from_instance`` to customize the object labels.
Creating custom fields
----------------------
View
17 tests/modeltests/model_forms/models.py
@@ -659,6 +659,19 @@ def __unicode__(self):
...
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
+# check that we can safely iterate choices repeatedly
+>>> gen_one = list(f.choices)
+>>> gen_two = f.choices
+>>> gen_one[2]
+(2L, u"It's a test")
+>>> list(gen_two)
+[(u'', u'---------'), (1L, u'Entertainment'), (2L, u"It's a test"), (3L, u'Third')]
+
+# check that we can override the label_from_instance method to print custom labels (#4620)
+>>> f.queryset = Category.objects.all()
+>>> f.label_from_instance = lambda obj: "category " + str(obj)
+>>> list(f.choices)
+[(u'', u'---------'), (1L, 'category Entertainment'), (2L, "category It's a test"), (3L, 'category Third'), (4L, 'category Fourth')]
# ModelMultipleChoiceField ####################################################
@@ -744,6 +757,10 @@ def __unicode__(self):
...
ValidationError: [u'Select a valid choice. 4 is not one of the available choices.']
+>>> f.queryset = Category.objects.all()
+>>> f.label_from_instance = lambda obj: "multicategory " + str(obj)
+>>> list(f.choices)
+[(1L, 'multicategory Entertainment'), (2L, "multicategory It's a test"), (3L, 'multicategory Third'), (4L, 'multicategory Fourth')]
# PhoneNumberField ############################################################
Please sign in to comment.
Something went wrong with that request. Please try again.