From a2e97abd8149e78071806a52282a24c27fe8236b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Fri, 23 Feb 2018 21:12:09 -0800 Subject: [PATCH] Fixed #29159 -- Made ModelChoiceIterator reuse QuerySet result cache. When __len__() is called (e.g. when casting to list or tuple), the QuerySet is evaluated and the result cache populated. iterator() shouldn't be called on the QuerySet after that, as it would reset the result cache and trigger a second query. --- django/forms/models.py | 2 +- tests/model_forms/test_modelchoicefield.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/django/forms/models.py b/django/forms/models.py index 8134cf3de3..414a9eafe4 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -1132,7 +1132,7 @@ class ModelChoiceIterator: yield ("", self.field.empty_label) queryset = self.queryset # Can't use iterator() when queryset uses prefetch_related() - if not queryset._prefetch_related_lookups: + if not queryset._prefetch_related_lookups and queryset._result_cache is None: queryset = queryset.iterator() for obj in queryset: yield self.choice(obj) diff --git a/tests/model_forms/test_modelchoicefield.py b/tests/model_forms/test_modelchoicefield.py index 6b0f00cf7f..733175823f 100644 --- a/tests/model_forms/test_modelchoicefield.py +++ b/tests/model_forms/test_modelchoicefield.py @@ -257,6 +257,17 @@ class ModelChoiceFieldTests(TestCase): (self.c3.pk, 'Third'), ]) + def test_queryset_result_cache_is_reused(self): + f = forms.ModelChoiceField(Category.objects.all()) + with self.assertNumQueries(1): + # list() calls __len__() and __iter__(); no duplicate queries. + self.assertEqual(list(f.choices), [ + ('', '---------'), + (self.c1.pk, 'Entertainment'), + (self.c2.pk, 'A test'), + (self.c3.pk, 'Third'), + ]) + def test_num_queries(self): """ Widgets that render multiple subwidgets shouldn't make more than one