# Migration optimizer does not optimize choices

**URL:** <https://forum.djangoproject.com/t/migration-optimizer-does-not-optimize-choices/4645>\
**Category:** ORM\
**Created:** [October 5, 2020, 9:31am UTC](https://forum.djangoproject.com/t/migration-optimizer-does-not-optimize-choices/4645 "2020-10-05T09:31:25Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![fmarani](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/fmarani/32/1229_2.png) [@fmarani](https://forum.djangoproject.com/u/fmarani)\
**Post date:** [October 5, 2020, 9:31am UTC](https://forum.djangoproject.com/t/migration-optimizer-does-not-optimize-choices/4645/1 "2020-10-05T09:31:25Z")

</div>

We have a lot of tables in our db which have a lot of category-type fields, and those charfields have a fixed set of choices that have been growing in the history of the project.

First migration:

```auto
        migrations.CreateModel(
            name="...",
            fields=[...
                (
                    "transaction_type",
                    models.CharField(
                        blank=True,
                        choices=[
                            ("DD_FIRST_COLLECTION", "Direct debit first collection"),
                            ("DD_REGULAR_COLLECTION", "Direct debit regular collection"),
                        ],
                        max_length=512,
                    ),
                ),

```

And lots of these:

```auto
        migrations.AlterField(
            model_name="accountpayment",
            name="transaction_type",
            field=models.CharField(
                blank=True,
                choices=[
                    ("DD_FIRST_COLLECTION", "Direct debit first collection"),
                    ("DD_REGULAR_COLLECTION", "Direct debit regular collection"),
                    ("CREDIT_CARD", "Credit card collection"),
....

```

I think the right behaviour, during squashmigrations, should be that the migrations optimizer merges these operations back into one. Especially if the second set is a superset of the first one.

Could this be a suggested improvement to the optimizer’s code? or is there a edge case which i am not seeing?

---

<div class="post-metadata">

**Author:** ![andrewgodwin](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/andrewgodwin/32/2_2.png) [@andrewgodwin](https://forum.djangoproject.com/u/andrewgodwin)\
**Post date:** [October 5, 2020, 4:59pm UTC](https://forum.djangoproject.com/t/migration-optimizer-does-not-optimize-choices/4645/2 "2020-10-05T16:59:58Z")

</div>

The problem - and the reason `choices` are there in the first place - is that the optimiser doesn’t know if `choices` is relevant or not (it’s the same with any field argument), and so it can’t do special handling just for it.

If we could trust that choices didn’t affect the database - which it doesn’t in _almost all_ situations - then it would be fine. In your case, it probably is - but I don’t think we could turn that setting turned on by default.

The right approach here might be pluggable optimiser strategies that allow people to opt-in to certain fields not affecting the DB or being last-value-wins, like this.

---

<div class="post-metadata">

**Author:** ![jarekwg](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/jarekwg/32/27643_2.png) [@jarekwg](https://forum.djangoproject.com/u/jarekwg)\
**Post date:** [March 21, 2025, 9:57am UTC](https://forum.djangoproject.com/t/migration-optimizer-does-not-optimize-choices/4645/3 "2025-03-21T09:57:47Z")

</div>

Hey Peeps, reviving this ancient topic in the context of Django5’s new callable choices.

Django5 allows us to now wrap our choices enums in callables and pass these to modelfields instead:

```auto
def get_taxcode_choices() -> list[tuple[str, str]]:
    return TaxCode.choices

```

With this, migrations no longer need to change every time we change our enums! 🥳 However, we’re still forced to create all these callables around what is let’s face it the majority of the time just a `TextChoices` class.

I put to you that we introduce a check that says

> When writing modelfield `choices` out to a migration, if the choices are of the form `<TextChoicesClass>.choices` (or a similar django-recognised enum), we pass this expression directly to the migration without evaluating it out to a list.

This would be a beautiful QoL change, building upon improvements to the `Choices` class and `choices` modelfield attrib that have come through in more recent django versions.

Thoughts (on this approach, or some equivalent)?  
It’s just super frustrating presently defining this beautiful `TextChoices` class only to not be able to pass it directly to our model fields..

---

<div class="post-metadata">

**Author:** ![leandrodesouzadev](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/leandrodesouzadev/32/8981_2.png) [@leandrodesouzadev](https://forum.djangoproject.com/u/leandrodesouzadev)\
**Post date:** [March 21, 2025, 12:43pm UTC](https://forum.djangoproject.com/t/migration-optimizer-does-not-optimize-choices/4645/4 "2025-03-21T12:43:02Z")

</div>

I believe that another option that is present for a long time, is to inherit from the field and add a [deconstruct](https://docs.djangoproject.com/en/5.1/topics/migrations/#adding-a-deconstruct-method) method to remove the choices from the kwargs.  
One also can monkey patch the django Field class deconstruct method to apply this to all of the fields on that project, I remember doing this for a project and it worked really well, but it’s real “magic” that you need to keep track of.

---

<div class="post-metadata">

**Author:** ![jarekwg](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/jarekwg/32/27643_2.png) [@jarekwg](https://forum.djangoproject.com/u/jarekwg)\
**Post date:** [March 23, 2025, 8:55pm UTC](https://forum.djangoproject.com/t/migration-optimizer-does-not-optimize-choices/4645/5 "2025-03-23T20:55:23Z")

</div>

Clever. I guess the tradeoff against “magic” improves with the number of times `choices` is used in the project. A hack like this could also be done as a bet that a future Django version might support what I’ve defined above, in which case there’d be close to no refactor needed to upgrade, beyond just removing the monkeypatch.

---

<div class="post-metadata">

**Author:** ![jarekwg](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/jarekwg/32/27643_2.png) [@jarekwg](https://forum.djangoproject.com/u/jarekwg)\
**Post date:** [June 12, 2025, 10:39pm UTC](https://forum.djangoproject.com/t/migration-optimizer-does-not-optimize-choices/4645/6 "2025-06-12T22:39:16Z")

</div>

What if we avoided magic and had an explicit callable offered by TextChoices?

```auto
class Choices(enum.Enum, metaclass=ChoicesType):

    @classmethod
    def model_choices(cls):
        return cls.choices

    ...

```

Then our models could just do

```auto
class Customer(models.Model):
    class TaxCode(models.TextChoices):
        TAX = 'TAX', 'Tax'
        EXEMPT = 'EXEMPT', 'Exempt'

    taxcode = models.CharField(max_length=6, choices=TaxCode.model_choices)

```

Raised a django FR here: [Improve Choices for migrations · Issue #51 · django/new-features · GitHub](https://github.com/django/new-features/issues/51)
