Code

Ticket #4620: 4620_all.diff

File 4620_all.diff, 4.8 KB (added by PhiR, 6 years ago)

patch with fix + doc + tests

Line 
1Index: django/newforms/models.py
2===================================================================
3--- django/newforms/models.py   (revision 7319)
4+++ django/newforms/models.py   (working copy)
5@@ -278,18 +278,17 @@
6 # Fields #####################################################################
7 
8 class QuerySetIterator(object):
9-    def __init__(self, queryset, empty_label, cache_choices):
10-        self.queryset = queryset
11-        self.empty_label = empty_label
12-        self.cache_choices = cache_choices
13+    def __init__(self, field):
14+        self.field = field
15+        self.queryset = field.queryset
16 
17     def __iter__(self):
18-        if self.empty_label is not None:
19-            yield (u"", self.empty_label)
20+        if self.field.empty_label is not None:
21+            yield (u"", self.field.empty_label)
22         for obj in self.queryset:
23-            yield (obj.pk, smart_unicode(obj))
24+            yield (obj.pk, self.field.label_from_instance(obj))
25         # Clear the QuerySet cache if required.
26-        if not self.cache_choices:
27+        if not self.field.cache_choices:
28             self.queryset._result_cache = None
29 
30 class ModelChoiceField(ChoiceField):
31@@ -321,6 +320,11 @@
32 
33     queryset = property(_get_queryset, _set_queryset)
34 
35+    # this method will be used to create object labels by the QuerySetIterator.
36+    # Override it to customize the label.
37+    def label_from_instance(self, obj):
38+        return smart_unicode(obj)
39+   
40     def _get_choices(self):
41         # If self._choices is set, then somebody must have manually set
42         # the property self.choices. In this case, just return self._choices.
43@@ -331,9 +335,9 @@
44         # been consumed. Note that we're instantiating a new QuerySetIterator
45         # *each* time _get_choices() is called (and, thus, each time
46         # self.choices is accessed) so that we can ensure the QuerySet has not
47-        # been consumed.
48-        return QuerySetIterator(self.queryset, self.empty_label,
49-                                self.cache_choices)
50+        # been consumed. This construct might look complicated but it allows
51+        # for lazy evaluation of the queryset.
52+        return QuerySetIterator(self)
53 
54     def _set_choices(self, value):
55         # This method is copied from ChoiceField._set_choices(). It's necessary
56Index: tests/modeltests/model_forms/models.py
57===================================================================
58--- tests/modeltests/model_forms/models.py      (revision 7319)
59+++ tests/modeltests/model_forms/models.py      (working copy)
60@@ -645,7 +645,20 @@
61 ...
62 ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
63 
64+# check that we can safely iterate choices repeatedly
65+>>> gen_one = list(f.choices)
66+>>> gen_two = f.choices
67+>>> gen_one[2]
68+(2L, u"It's a test")
69+>>> list(gen_two)
70+[(u'', u'---------'), (1L, u'Entertainment'), (2L, u"It's a test"), (3L, u'Third')]
71 
72+# check that we can override the label_from_instance method to print custom labels (#4620)
73+>>> f.queryset = Category.objects.all()
74+>>> f.label_from_instance = lambda obj: "category " + str(obj)
75+>>> list(f.choices)
76+[(u'', u'---------'), (1L, 'category Entertainment'), (2L, "category It's a test"), (3L, 'category Third'), (4L, 'category Fourth')]
77+
78 # ModelMultipleChoiceField ####################################################
79 
80 >>> f = ModelMultipleChoiceField(Category.objects.all())
81@@ -730,6 +743,10 @@
82 ...
83 ValidationError: [u'Select a valid choice. 4 is not one of the available choices.']
84 
85+>>> f.queryset = Category.objects.all()
86+>>> f.label_from_instance = lambda obj: "multicategory " + str(obj)
87+>>> list(f.choices)
88+[(1L, 'multicategory Entertainment'), (2L, "multicategory It's a test"), (3L, 'multicategory Third'), (4L, 'multicategory Fourth')]
89 
90 # PhoneNumberField ############################################################
91 
92Index: docs/newforms.txt
93===================================================================
94--- docs/newforms.txt   (revision 7319)
95+++ docs/newforms.txt   (working copy)
96@@ -1517,13 +1517,18 @@
97 ~~~~~~~~~~~~~~~~~~~~
98 
99 Allows the selection of a single model object, suitable for
100-representing a foreign key.
101+representing a foreign key. A ``ModelChoiceField`` will use the
102+``__unicode__()`` method of the model to represent them, but this
103+can be customized by overriding the ``label_from_instance`` method
104+of the field itself (ie subclassing it). The method receives an object
105+as an argument and must return a string to represent it.
106 
107 ``ModelMultipleChoiceField``
108 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
109 
110 Allows the selection of one or more model objects, suitable for
111-representing a many-to-many relation.
112+representing a many-to-many relation. As with ``ModelChoiceField``,
113+you can use ``label_from_instance`` to customize the object labels.
114 
115 
116 Creating custom fields