From de570d4da9447ebef253519c2473d3b4f6fb2bea Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 5 Oct 2015 19:25:21 -0400 Subject: [PATCH] [1.8.x] Fixed #25496 -- Made ModelChoiceField respect prefetch_related(). Backport of 6afa6818fcf25665bbf61f0921c8c8c6fa8f223e from master --- django/forms/models.py | 6 ++++-- docs/releases/1.8.6.txt | 3 ++- tests/model_forms/tests.py | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/django/forms/models.py b/django/forms/models.py index 2137e9f4a6..1036e9b288 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -1092,15 +1092,17 @@ class ModelChoiceIterator(object): def __iter__(self): if self.field.empty_label is not None: yield ("", self.field.empty_label) + method = 'all' if self.queryset._prefetch_related_lookups else 'iterator' + queryset = getattr(self.queryset, method) if self.field.cache_choices: if self.field.choice_cache is None: self.field.choice_cache = [ - self.choice(obj) for obj in self.queryset.iterator() + self.choice(obj) for obj in queryset() ] for choice in self.field.choice_cache: yield choice else: - for obj in self.queryset.iterator(): + for obj in queryset(): yield self.choice(obj) def __len__(self): diff --git a/docs/releases/1.8.6.txt b/docs/releases/1.8.6.txt index 6268bb3bbf..9292d7835b 100644 --- a/docs/releases/1.8.6.txt +++ b/docs/releases/1.8.6.txt @@ -9,4 +9,5 @@ Django 1.8.6 fixes several bugs in 1.8.5. Bugfixes ======== -* ... +* Fixed a regression causing ``ModelChoiceField`` to ignore + ``prefetch_related()`` on its queryset (:ticket:`25496`). diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index f1cf77a6a6..2672fa5116 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -2203,6 +2203,29 @@ class OtherModelFormTests(TestCase): with self.assertRaises(ValidationError): f.fields['status'].clean('z') + def test_prefetch_related_queryset(self): + """ + ModelChoiceField should respect a prefetch_related() on its queryset. + """ + blue = Colour.objects.create(name='blue') + red = Colour.objects.create(name='red') + multicolor_item = ColourfulItem.objects.create() + multicolor_item.colours.add(blue, red) + red_item = ColourfulItem.objects.create() + red_item.colours.add(red) + + class ColorModelChoiceField(forms.ModelChoiceField): + def label_from_instance(self, obj): + return ', '.join(c.name for c in obj.colours.all()) + + field = ColorModelChoiceField(ColourfulItem.objects.prefetch_related('colours')) + with self.assertNumQueries(4): # would be 5 if prefetch is ignored + self.assertEqual(tuple(field.choices), ( + ('', '---------'), + (multicolor_item.pk, 'blue, red'), + (red_item.pk, 'red'), + )) + def test_foreignkeys_which_use_to_field(self): apple = Inventory.objects.create(barcode=86, name='Apple') Inventory.objects.create(barcode=22, name='Pear')