﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
34899	Model Field.choices callable support is not actually lazy	Adam Johnson	Nick Pope	"#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.

"	Bug	closed	Database layer (models, ORM)	5.0	Release blocker	fixed	choices, callable, lazy	Natalia Bidart Nick Pope	Ready for checkin	1	0	0	0	0	0
