Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
Jacob Kaplan-Moss authored March 19, 2008
43  django/newforms/models.py
@@ -277,19 +277,18 @@ class ModelForm(BaseModelForm):
277 277
 
278 278
 # Fields #####################################################################
279 279
 
280  
-class QuerySetIterator(object):
281  
-    def __init__(self, queryset, empty_label, cache_choices):
282  
-        self.queryset = queryset
283  
-        self.empty_label = empty_label
284  
-        self.cache_choices = cache_choices
  280
+class ModelChoiceIterator(object):
  281
+    def __init__(self, field):
  282
+        self.field = field
  283
+        self.queryset = field.queryset
285 284
 
286 285
     def __iter__(self):
287  
-        if self.empty_label is not None:
288  
-            yield (u"", self.empty_label)
  286
+        if self.field.empty_label is not None:
  287
+            yield (u"", self.field.empty_label)
289 288
         for obj in self.queryset:
290  
-            yield (obj.pk, smart_unicode(obj))
  289
+            yield (obj.pk, self.field.label_from_instance(obj))
291 290
         # Clear the QuerySet cache if required.
292  
-        if not self.cache_choices:
  291
+        if not self.field.cache_choices:
293 292
             self.queryset._result_cache = None
294 293
 
295 294
 class ModelChoiceField(ChoiceField):
@@ -306,6 +305,7 @@ def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
306 305
                  help_text=None, *args, **kwargs):
307 306
         self.empty_label = empty_label
308 307
         self.cache_choices = cache_choices
  308
+        
309 309
         # Call Field instead of ChoiceField __init__() because we don't need
310 310
         # ChoiceField.__init__().
311 311
         Field.__init__(self, required, widget, label, initial, help_text,
@@ -321,19 +321,30 @@ def _set_queryset(self, queryset):
321 321
 
322 322
     queryset = property(_get_queryset, _set_queryset)
323 323
 
  324
+    # this method will be used to create object labels by the QuerySetIterator. 
  325
+    # Override it to customize the label. 
  326
+    def label_from_instance(self, obj):
  327
+        """
  328
+        This method is used to convert objects into strings; it's used to
  329
+        generate the labels for the choices presented by this object. Subclasses
  330
+        can override this method to customize the display of the choices.
  331
+        """
  332
+        return smart_unicode(obj)
  333
+    
324 334
     def _get_choices(self):
325 335
         # If self._choices is set, then somebody must have manually set
326 336
         # the property self.choices. In this case, just return self._choices.
327 337
         if hasattr(self, '_choices'):
328 338
             return self._choices
  339
+
329 340
         # Otherwise, execute the QuerySet in self.queryset to determine the
330  
-        # choices dynamically. Return a fresh QuerySetIterator that has not
331  
-        # been consumed. Note that we're instantiating a new QuerySetIterator
332  
-        # *each* time _get_choices() is called (and, thus, each time
333  
-        # self.choices is accessed) so that we can ensure the QuerySet has not
334  
-        # been consumed.
335  
-        return QuerySetIterator(self.queryset, self.empty_label,
336  
-                                self.cache_choices)
  341
+        # choices dynamically. Return a fresh QuerySetIterator that has not been
  342
+        # consumed. Note that we're instantiating a new QuerySetIterator *each*
  343
+        # time _get_choices() is called (and, thus, each time self.choices is
  344
+        # accessed) so that we can ensure the QuerySet has not been consumed. This
  345
+        # construct might look complicated but it allows for lazy evaluation of
  346
+        # the queryset.
  347
+        return ModelChoiceIterator(self)
337 348
 
338 349
     def _set_choices(self, value):
339 350
         # This method is copied from ChoiceField._set_choices(). It's necessary
18  docs/newforms.txt
@@ -1549,15 +1549,23 @@ additional required argument:
1549 1549
 ``ModelChoiceField``
1550 1550
 ~~~~~~~~~~~~~~~~~~~~
1551 1551
 
1552  
-Allows the selection of a single model object, suitable for
1553  
-representing a foreign key.
  1552
+Allows the selection of a single model object, suitable for representing a
  1553
+foreign key. The method receives an object as an argument and must return a
  1554
+string to represent it.
  1555
+
  1556
+The labels for the choice field call the ``__unicode__`` method of the model to
  1557
+generate string representations. To provide custom labels, subclass ``ModelChoiceField`` and override ``label_for_model``::
  1558
+
  1559
+    class MyModelChoiceField(ModelChoiceField):
  1560
+        def label_from_instance(self, obj):
  1561
+            return "My Object #%i" % obj.id
1554 1562
 
1555 1563
 ``ModelMultipleChoiceField``
1556 1564
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1557 1565
 
1558  
-Allows the selection of one or more model objects, suitable for
1559  
-representing a many-to-many relation.
1560  
-
  1566
+Allows the selection of one or more model objects, suitable for representing a
  1567
+many-to-many relation. As with ``ModelChoiceField``, you can use
  1568
+``label_from_instance`` to customize the object labels.
1561 1569
 
1562 1570
 Creating custom fields
1563 1571
 ----------------------
17  tests/modeltests/model_forms/models.py
@@ -659,6 +659,19 @@ def __unicode__(self):
659 659
 ...
660 660
 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
661 661
 
  662
+# check that we can safely iterate choices repeatedly
  663
+>>> gen_one = list(f.choices)
  664
+>>> gen_two = f.choices
  665
+>>> gen_one[2]
  666
+(2L, u"It's a test")
  667
+>>> list(gen_two)
  668
+[(u'', u'---------'), (1L, u'Entertainment'), (2L, u"It's a test"), (3L, u'Third')]
  669
+
  670
+# check that we can override the label_from_instance method to print custom labels (#4620)
  671
+>>> f.queryset = Category.objects.all()
  672
+>>> f.label_from_instance = lambda obj: "category " + str(obj)
  673
+>>> list(f.choices)
  674
+[(u'', u'---------'), (1L, 'category Entertainment'), (2L, "category It's a test"), (3L, 'category Third'), (4L, 'category Fourth')]
662 675
 
663 676
 # ModelMultipleChoiceField ####################################################
664 677
 
@@ -744,6 +757,10 @@ def __unicode__(self):
744 757
 ...
745 758
 ValidationError: [u'Select a valid choice. 4 is not one of the available choices.']
746 759
 
  760
+>>> f.queryset = Category.objects.all()
  761
+>>> f.label_from_instance = lambda obj: "multicategory " + str(obj)
  762
+>>> list(f.choices)
  763
+[(1L, 'multicategory Entertainment'), (2L, "multicategory It's a test"), (3L, 'multicategory Third'), (4L, 'multicategory Fourth')]
747 764
 
748 765
 # PhoneNumberField ############################################################
749 766
 

0 notes on commit 649cdf9

Please sign in to comment.
Something went wrong with that request. Please try again.