[Django] #36892: Lazy Tuples in field Choices generate repeated migrations with no changes

2 views
Skip to first unread message

Django

unread,
Jan 29, 2026, 6:31:00 PM (7 days ago) Jan 29
to django-...@googlegroups.com
#36892: Lazy Tuples in field Choices generate repeated migrations with no changes
-------------------------------------+-------------------------------------
Reporter: Matt Armand | Type: Bug
Status: new | Component:
| Migrations
Version: 5.0 | Severity: Normal
Keywords: migrations tuple | Triage Stage:
choices lazy functional | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
## Background

TLDR, when a model field specifies `choices` backed by a lazily evaluated
tuple, Django>=5.0 serializes them incorrectly into migrations files and
repeatedly generates identical migrations on repeated `makemigrations`
runs. Django 4.2 is able to handle these fields correctly.

This bug appears to me to have been introduced with the 5.0 release. At
time of writing, this bug exists in the latest release on the 5.2 channel
(5.2.10) as well as the 6.0 channel (6.0.1).

This issue was originally found in a Django application utilizing the a
list of US States in the `django-localflavor` library as the `choices` for
a model field. That library uses `django.utils.functional::lazy` for
creating a lazily evaluated tuple, and the bug is reproducible with pure
Django code and no external dependencies.

This is a minimalistic example of the lazy tuple used in `django-
localflavor` and that can be used to reproduce the bug:

{{{
import operator
from django.db import models
from django.utils.functional import lazy

TUPLE_1 = (("A", "A value"),)
TUPLE_2 = (("B", "B value"),)
LAZY_TUPLE = lazy(
lambda: tuple(sorted(TUPLE_1 + TUPLE_2, key =
operator.itemgetter(1))), tuple
)()

class TestModel(models.model):
test_field = models.CharField(choices=LAZY_TUPLE)
}}}

## Expected Behavior

Prior to Django 5.0 (in 4.2.27 for example), running `makemigrations` on
an app containing this field and model yields migration code containing
the following serialization: `choices=[('A', 'A value'), ('B', 'B
value')],` The choices attribute is an array as expected, and repeated
`makemigrations` calls successfully detect no changes to the model.

## Actual Behavior

Beginning in Django 5.0, running `makemigrations` on an app containing
this field and model yields migration code containing the following
serialization: `choices="(('A', 'A value'), ('B', 'B value'))",` The
choices attribute is now a string representation of the tuple, and
repeated `makemigrations` calls will re-generate a new and identical
`AlterField` migration for this field ad infinitum.

I've pushed a [https://github.com/matthewarmand/django-lazy-migration-bug
sample reproduction Django app]. You can see in the
`django_lazy_migration_bug/test_app/migrations/` files generated by Django
versions 5.x and 6.x, the erroneous behavior is exhibited, and new
migration files are repeatedly generated every time `makemigrations` is
run. Under Django 4.2.27, the field is serialized correctly and repeated
migrations don't occur.

## Investigation

I'm still not sure quite what the root cause of this is. Comparing 5.0 to
4.2.27, there doesn't seem to be significant change in
`django.db.migrations.serializer.py::serializer_factory` that would change
the `MigrationWriter`'s serialization of this field, nor were there any
significant changes to the `MigrationWriter` itself. The first conditional
in `serializer_factory` (concerning the `Promise` `isinstance` check)
would evaluate to true in both versions.There were some changes to
`django.utils.functional.py::lazy`, specifically to the handling of
`resultclasses` `__wrapper__` functions, so maybe that caused some change
in the migration serialization. But I don't see an obvious cause for this
yet.


I have attached to this ticket a patch to the Django unit tests adding a
case for this, which I've confirmed fails currently. As I have time I can
debug further, but I wanted to get the issue reported in case someone else
had a quicker fix than I.
--
Ticket URL: <https://code.djangoproject.com/ticket/36892>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Jan 29, 2026, 6:32:00 PM (7 days ago) Jan 29
to django-...@googlegroups.com
#36892: Lazy Tuples in field Choices generate repeated migrations with no changes
-------------------------------------+-------------------------------------
Reporter: Matt Armand | Owner: (none)
Type: Bug | Status: new
Component: Migrations | Version: 5.0
Severity: Normal | Resolution:
Keywords: migrations tuple | Triage Stage:
choices lazy functional | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Matt Armand):

* Attachment "django-lazy-migrations-bug-unit-test.diff" added.

Django

unread,
Jan 29, 2026, 9:51:09 PM (7 days ago) Jan 29
to django-...@googlegroups.com
#36892: Lazy Tuples in field Choices generate repeated migrations with no changes
-------------------------------------+-------------------------------------
Reporter: Matt Armand | Owner: Ahmed
| Shammas J
Type: Bug | Status: assigned
Component: Migrations | Version: 5.0
Severity: Normal | Resolution:
Keywords: migrations tuple | Triage Stage:
choices lazy functional | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Ahmed Shammas J):

* owner: (none) => Ahmed Shammas J
* status: new => assigned

Comment:

I am Ahmed Shammas, a 2nd-year BCA student and GSoC 2026 aspirant. I am
claiming this ticket to investigate the serialization issue with lazy
tuples in migrations. I'll post updates as I make progress.
--
Ticket URL: <https://code.djangoproject.com/ticket/36892#comment:1>

Django

unread,
Jan 29, 2026, 11:34:30 PM (7 days ago) Jan 29
to django-...@googlegroups.com
#36892: Lazy Tuples in field Choices generate repeated migrations with no changes
-------------------------------------+-------------------------------------
Reporter: Matt Armand | Owner: Ahmed
| Shammas J
Type: Bug | Status: assigned
Component: Migrations | Version: 5.0
Severity: Normal | Resolution:
Keywords: migrations tuple | Triage Stage:
choices lazy functional | Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Ahmed Shammas J):

* has_patch: 0 => 1

Comment:

"I have submitted a PR at https://github.com/django/django/pull/20613
which includes a fix in serializer_factory and a regression test.
--
Ticket URL: <https://code.djangoproject.com/ticket/36892#comment:2>

Django

unread,
Jan 30, 2026, 9:16:28 AM (7 days ago) Jan 30
to django-...@googlegroups.com
#36892: Lazy Tuples in field Choices generate repeated migrations with no changes
-------------------------------------+-------------------------------------
Reporter: Matt Armand | Owner: Ahmed
| Shammas J
Type: Bug | Status: assigned
Component: Migrations | Version: 5.0
Severity: Normal | Resolution:
Keywords: migrations tuple | Triage Stage: Accepted
choices lazy functional |
deconstruction |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* has_patch: 1 => 0
* keywords: migrations tuple choices lazy functional => migrations tuple
choices lazy functional deconstruction
* stage: Unreviewed => Accepted

Comment:

Thanks for the report. The specific location you're seeing this problem
inside `choices` is new since 500e01073adda32d5149624ee9a5cb7aa3d3583f,
but the problem applies to all field arguments, as discussed in
ticket:29852#comment:6 where a proposed solution is identified:


> Promise serialization currently assumes it's only used for string values
and it should be adjusted to deal with other types as well. What I suggest
doing is inspecting wrapped types of the promise object and only perform a
str on the value if the types are (str,). Else the value should be
deconstructed as a django.utils.functional.lazy import and a lazy(...)()
reconstruction.
--
Ticket URL: <https://code.djangoproject.com/ticket/36892#comment:3>

Django

unread,
Jan 30, 2026, 10:08:34 AM (7 days ago) Jan 30
to django-...@googlegroups.com
#36892: Lazy Tuples in field Choices generate repeated migrations with no changes
-------------------------------------+-------------------------------------
Reporter: Matt Armand | Owner: Ahmed
| Shammas J
Type: Bug | Status: assigned
Component: Migrations | Version: 5.0
Severity: Normal | Resolution:
Keywords: migrations tuple | Triage Stage: Accepted
choices lazy functional |
deconstruction |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Matt Armand):

Replying to [comment:3 Jacob Walls]:

Thanks Jacob, that makes sense. I notice that ticket conversation is
pretty old, is the proposed `Promise` serialization enhancement already
planned or in progress, or would that just be the best approach to fix
this issue should someone pick it up?
--
Ticket URL: <https://code.djangoproject.com/ticket/36892#comment:4>

Django

unread,
Jan 30, 2026, 10:50:10 AM (7 days ago) Jan 30
to django-...@googlegroups.com
#36892: Lazy Tuples in field Choices generate repeated migrations with no changes
-------------------------------------+-------------------------------------
Reporter: Matt Armand | Owner: Ahmed
| Shammas J
Type: Bug | Status: assigned
Component: Migrations | Version: 5.0
Severity: Normal | Resolution:
Keywords: migrations tuple | Triage Stage: Accepted
choices lazy functional |
deconstruction |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Jacob Walls):

I'd be glad to review a contribution implementing that suggestion. I think
you can take the liberty to assign the ticket to yourself in that case, as
the prior attempt preceded ticket triage.
--
Ticket URL: <https://code.djangoproject.com/ticket/36892#comment:5>

Django

unread,
Jan 30, 2026, 10:49:58 PM (6 days ago) Jan 30
to django-...@googlegroups.com
#36892: Lazy Tuples in field Choices generate repeated migrations with no changes
-------------------------------------+-------------------------------------
Reporter: Matt Armand | Owner: Ahmed
| Shammas J
Type: Bug | Status: assigned
Component: Migrations | Version: 5.0
Severity: Normal | Resolution:
Keywords: migrations tuple | Triage Stage: Accepted
choices lazy functional |
deconstruction |
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Ahmed Shammas J):

* has_patch: 0 => 1

Comment:

I have resubmitted a clean, atomic PR with the requested regression test
here: https://github.com/django/django/pull/20620.
--
Ticket URL: <https://code.djangoproject.com/ticket/36892#comment:6>
Reply all
Reply to author
Forward
0 new messages