#34899: Model Field.choices callable support is not actually lazy
-------------------------------------+-------------------------------------
               Reporter:  Adam       |          Owner:  nobody
  Johnson                            |
                   Type:  Bug        |         Status:  new
              Component:  Database   |        Version:  5.0
  layer (models, ORM)                |
               Severity:  Release    |       Keywords:
  blocker                            |
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 #24561 added support for callables to model Field.choices, with the docs
 declaring that using a callable “can be particularly handy” when you need
 I/O like a database query. Whilst the model Field supports this by only
 lazily calling choices, the formfield() method ends up iterating over
 choices, leading to the callable being run at import time with a
 `ModelField` definition.

 Here’s a minimal example ([https://github.com/adamchainz/django-5.0
 -choices-laziness source]):

 {{{
 from django import forms
 from django.db import models

 ready = False


 def animals():
     if not ready:
         raise RuntimeError("Not ready to load animals")

     return [
         (1, "Aardvark"),
         (2, "Banana"),
     ]


 class User(models.Model):
     spirit_animal = models.IntegerField(choices=animals)


 class UserForm(forms.ModelForm):
     class Meta:
         model = User
         fields = ["spirit_animal"]


 ready = True
 }}}

 On Django 5.0a1 it fails with:

 {{{
 $ ./manage.py shell
 Traceback (most recent call last):
   File "/..././manage.py", line 21, in <module>
     main()
   ...
   File "/.../example/models.py", line 21, in <module>
     class UserForm(forms.ModelForm):
   File "/.../.venv/lib/python3.11/site-packages/django/forms/models.py",
 line 309, in __new__
     fields = fields_for_model(
              ^^^^^^^^^^^^^^^^^
   File "/.../.venv/lib/python3.11/site-packages/django/forms/models.py",
 line 234, in fields_for_model
     formfield = f.formfield(**kwargs)
                 ^^^^^^^^^^^^^^^^^^^^^
   File "/.../.venv/lib/python3.11/site-
 packages/django/db/models/fields/__init__.py", line 2142, in formfield
     return super().formfield(
            ^^^^^^^^^^^^^^^^^^
   File "/.../.venv/lib/python3.11/site-
 packages/django/db/models/fields/__init__.py", line 1118, in formfield
     defaults["choices"] = self.get_choices(include_blank=include_blank)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/.../.venv/lib/python3.11/site-
 packages/django/db/models/fields/__init__.py", line 1054, in get_choices
     choices = list(self.choices)
               ^^^^^^^^^^^^^^^^^^
   File "/.../.venv/lib/python3.11/site-packages/django/utils/choices.py",
 line 17, in __iter__
     yield from normalize_choices(self.func())
                                  ^^^^^^^^^^^
   File "/.../example/models.py", line 9, in animals
     raise RuntimeError("Not ready to load animals")
 RuntimeError: Not ready to load animals
 }}}

 I encountered this bug whilst trying to update django-countries to support
 Django 5.0 (https://github.com/SmileyChris/django-countries/pull/438). Its
 sorted, translated list of countries is exactly what the callable choices
 feature is intended for.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/34899>
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/0107018b2cfa9d8b-f6a869eb-d6dc-47bb-9b77-094f0d998d34-000000%40eu-central-1.amazonses.com.

Reply via email to