#31295: required ModelChoiceField makes duplicate (cursor) queries to the
database
---------------------------------+--------------------------------------
Reporter: Aurélien Pardon | Owner: nobody
Type: Bug | Status: new
Component: Forms | Version: 2.2
Severity: Normal | Resolution:
Keywords: Model | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+--------------------------------------
Changes (by Aurélien Pardon):
* status: closed => new
* type: Cleanup/optimization => Bug
* resolution: wontfix =>
Comment:
Hi Carlton and thanks for your answer but, all due respect, I think you
overlooked this bug.
First, this simple example on an empty project/database that I just tested
(Python 3.8.1, Django 2.2.10):
{{{#!python
class MyModel(models.Model):
my_field = models.CharField(max_length=1)
class MyForm(forms.Form):
my_form_field = forms.ModelChoiceField(MyModel.objects.all(), ,
empty_label=None)
my_form = MyForm()
type(my_form.widget.choices)
# >>> return <class 'django.forms.models.ModelChoiceIterator'>
my_form.as_ul()
len(connecton.queries)
# >>> return 2 (and it's two times "SELECT * from my_app.my_model")
}}}
Here is what I understand:
1) The code you sent, where {{{ChoiceWidget.choices}}} is evaluated and
saved into a {{{list}}} is executed at the initialization of the widget
(which is executed at the initialization of the field at the declaration
of the form).
2) But! a {{{ModelChoiceField}}} override {{{self.choices}}}
[https://github.com/django/django/blob/271fdab8b78af558238df51c64b4d1c8dd0792bb/django/forms/models.py#L1225-L1227
here] (it makes perfect sense: the choices have to be evaluated everytime
the form is instanciated).
3) As the choices are bundled in a
{{{ModelChoiceIterator}}}/{{{.iterator()}}}, when rendering the form,
there is a test about the {{{required}}} attribute that try to
[https://github.com/django/django/blob/ffcf1a8ebfbdc8e3afac84aed88d6ed29a57c72b/django/forms/widgets.py#L699
fetch the first value of the queryset]:
{{{#!python
class Select(ChoiceWidget):
"""[...]"""
def use_required_attribute(self, initial):
"""[...]"""
first_choice = next(iter(self.choices), None)
}}}
4) {{{ModelChoiceIterator.__iter__}}}
[https://github.com/django/django/blob/ffcf1a8ebfbdc8e3afac84aed88d6ed29a57c72b/django/forms/models.py#L1148-L1156
is called]. If {{{empty_label}}} is {{{not None}}}, everything is fine, as
the {{{next()}}} grabs the empty label, without additional request to the
database. But if {{{empty_label}}} is {{{None}}} (and if there is no
prefetch to the queryset), an additional request is made (code is changed
lightly to illustrate the bug):
{{{#!python
def __iter__(self):
if self.field.empty_label is not None:
yield ("", self.field.empty_label)
for obj in self.queryset.iterator():
yield self.choice(obj)
}}}
Now that I have made some tests on an empty project/database, I'm
confident enough to state that it's a **bug** because there is a useless
and additional request to the DB.
''Thanks for all the work you put into the projects in and around Django.
I hope I tested correctly, I do not want to waste your time.''
--
Ticket URL: <https://code.djangoproject.com/ticket/31295#comment:3>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/065.40916b6b3e6158e3034b4cd5f350eca5%40djangoproject.com.