Fixed #27002 -- Prevented double query when rendering ModelChoiceField.

This commit is contained in:
Alex Hill 2016-08-03 14:18:48 +08:00 committed by Tim Graham
parent 29a3f8b4bb
commit 74105b2636
2 changed files with 28 additions and 12 deletions

View File

@ -8,6 +8,7 @@ from django.utils import six
from django.utils.encoding import (
force_text, python_2_unicode_compatible, smart_text,
)
from django.utils.functional import cached_property
from django.utils.html import conditional_escape, format_html, html_safe
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
@ -42,28 +43,32 @@ class BoundField(object):
return self.as_widget() + self.as_hidden(only_initial=True)
return self.as_widget()
def __iter__(self):
@cached_property
def subwidgets(self):
"""
Yields rendered strings that comprise all widgets in this BoundField.
Most widgets yield a single subwidget, but others like RadioSelect and
CheckboxSelectMultiple produce one subwidget for each choice.
This really is only useful for RadioSelect widgets, so that you can
iterate over individual radio buttons in a template.
This property is cached so that only one database query occurs when
rendering ModelChoiceFields.
"""
id_ = self.field.widget.attrs.get('id') or self.auto_id
attrs = {'id': id_} if id_ else {}
attrs = self.build_widget_attrs(attrs)
for subwidget in self.field.widget.subwidgets(self.html_name, self.value(), attrs):
yield subwidget
return list(self.field.widget.subwidgets(self.html_name, self.value(), attrs))
def __iter__(self):
return iter(self.subwidgets)
def __len__(self):
return len(list(self.__iter__()))
return len(self.subwidgets)
def __getitem__(self, idx):
# Prevent unnecessary reevaluation when accessing BoundField's attrs
# from templates.
if not isinstance(idx, six.integer_types + (slice,)):
raise TypeError
return list(self.__iter__())[idx]
return self.subwidgets[idx]
@property
def errors(self):

View File

@ -1576,11 +1576,22 @@ class ModelChoiceFieldTests(TestCase):
field = CustomModelChoiceField(Category.objects.all())
self.assertIsInstance(field.choices, CustomModelChoiceIterator)
def test_radioselect_num_queries(self):
class CategoriesForm(forms.Form):
categories = forms.ModelChoiceField(Category.objects.all(), widget=forms.RadioSelect)
def test_modelchoicefield_num_queries(self):
"""
Widgets that render multiple subwidgets shouldn't make more than one
database query.
"""
categories = Category.objects.all()
class CategoriesForm(forms.Form):
radio = forms.ModelChoiceField(queryset=categories, widget=forms.RadioSelect)
checkbox = forms.ModelMultipleChoiceField(queryset=categories, widget=forms.CheckboxSelectMultiple)
template = Template("""
{% for widget in form.checkbox %}{{ widget }}{% endfor %}
{% for widget in form.radio %}{{ widget }}{% endfor %}
""")
template = Template('{% for widget in form.categories %}{{ widget }}{% endfor %}')
with self.assertNumQueries(2):
template.render(Context({'form': CategoriesForm()}))