#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 django-updates+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/django-updates/065.40916b6b3e6158e3034b4cd5f350eca5%40djangoproject.com.