mirror of https://github.com/django/django.git
Fixed #4620: you can now easily add custom labels to ModelChoiceFields via subclassing. Thanks, PhiR.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7326 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
94269654d1
commit
649cdf907d
|
@ -277,19 +277,18 @@ class ModelForm(BaseModelForm):
|
||||||
|
|
||||||
# Fields #####################################################################
|
# Fields #####################################################################
|
||||||
|
|
||||||
class QuerySetIterator(object):
|
class ModelChoiceIterator(object):
|
||||||
def __init__(self, queryset, empty_label, cache_choices):
|
def __init__(self, field):
|
||||||
self.queryset = queryset
|
self.field = field
|
||||||
self.empty_label = empty_label
|
self.queryset = field.queryset
|
||||||
self.cache_choices = cache_choices
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
if self.empty_label is not None:
|
if self.field.empty_label is not None:
|
||||||
yield (u"", self.empty_label)
|
yield (u"", self.field.empty_label)
|
||||||
for obj in self.queryset:
|
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.
|
# Clear the QuerySet cache if required.
|
||||||
if not self.cache_choices:
|
if not self.field.cache_choices:
|
||||||
self.queryset._result_cache = None
|
self.queryset._result_cache = None
|
||||||
|
|
||||||
class ModelChoiceField(ChoiceField):
|
class ModelChoiceField(ChoiceField):
|
||||||
|
@ -306,6 +305,7 @@ class ModelChoiceField(ChoiceField):
|
||||||
help_text=None, *args, **kwargs):
|
help_text=None, *args, **kwargs):
|
||||||
self.empty_label = empty_label
|
self.empty_label = empty_label
|
||||||
self.cache_choices = cache_choices
|
self.cache_choices = cache_choices
|
||||||
|
|
||||||
# Call Field instead of ChoiceField __init__() because we don't need
|
# Call Field instead of ChoiceField __init__() because we don't need
|
||||||
# ChoiceField.__init__().
|
# ChoiceField.__init__().
|
||||||
Field.__init__(self, required, widget, label, initial, help_text,
|
Field.__init__(self, required, widget, label, initial, help_text,
|
||||||
|
@ -321,19 +321,30 @@ class ModelChoiceField(ChoiceField):
|
||||||
|
|
||||||
queryset = property(_get_queryset, _set_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):
|
def _get_choices(self):
|
||||||
# If self._choices is set, then somebody must have manually set
|
# If self._choices is set, then somebody must have manually set
|
||||||
# the property self.choices. In this case, just return self._choices.
|
# the property self.choices. In this case, just return self._choices.
|
||||||
if hasattr(self, '_choices'):
|
if hasattr(self, '_choices'):
|
||||||
return self._choices
|
return self._choices
|
||||||
|
|
||||||
# Otherwise, execute the QuerySet in self.queryset to determine the
|
# Otherwise, execute the QuerySet in self.queryset to determine the
|
||||||
# choices dynamically. Return a fresh QuerySetIterator that has not
|
# choices dynamically. Return a fresh QuerySetIterator that has not been
|
||||||
# been consumed. Note that we're instantiating a new QuerySetIterator
|
# consumed. Note that we're instantiating a new QuerySetIterator *each*
|
||||||
# *each* time _get_choices() is called (and, thus, each time
|
# time _get_choices() is called (and, thus, each time self.choices is
|
||||||
# self.choices is accessed) so that we can ensure the QuerySet has not
|
# accessed) so that we can ensure the QuerySet has not been consumed. This
|
||||||
# been consumed.
|
# construct might look complicated but it allows for lazy evaluation of
|
||||||
return QuerySetIterator(self.queryset, self.empty_label,
|
# the queryset.
|
||||||
self.cache_choices)
|
return ModelChoiceIterator(self)
|
||||||
|
|
||||||
def _set_choices(self, value):
|
def _set_choices(self, value):
|
||||||
# This method is copied from ChoiceField._set_choices(). It's necessary
|
# This method is copied from ChoiceField._set_choices(). It's necessary
|
||||||
|
|
|
@ -1549,15 +1549,23 @@ additional required argument:
|
||||||
``ModelChoiceField``
|
``ModelChoiceField``
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Allows the selection of a single model object, suitable for
|
Allows the selection of a single model object, suitable for representing a
|
||||||
representing a foreign key.
|
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``
|
``ModelMultipleChoiceField``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Allows the selection of one or more model objects, suitable for
|
Allows the selection of one or more model objects, suitable for representing a
|
||||||
representing a many-to-many relation.
|
many-to-many relation. As with ``ModelChoiceField``, you can use
|
||||||
|
``label_from_instance`` to customize the object labels.
|
||||||
|
|
||||||
Creating custom fields
|
Creating custom fields
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
@ -659,6 +659,19 @@ Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
|
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 ####################################################
|
# ModelMultipleChoiceField ####################################################
|
||||||
|
|
||||||
|
@ -744,6 +757,10 @@ Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. 4 is not one of the available choices.']
|
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 ############################################################
|
# PhoneNumberField ############################################################
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue