#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.

Reply via email to