django / django/new-features

Improve Choices for migrations

Open
#51 9 comments 22 reactions 0 assignees View on GitHub
Dominant language
No language data
Stars
188
Forks
7
PR merge metrics
No merged PRs in 30d

Description

### Code of Conduct

- [x] I agree to follow Django's Code of Conduct

### Feature Description

When using Django's `Choices` in model field choices, there should be a mechanism to avoid migrations updating each time the `Choices` enum changes.

### Problem

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

taxcode = models.CharField(max_length=6, choices=TaxCode.choices)
```
Each time i change my enum, django will generate a fresh migration:
```python
migrations.AlterField(
model_name='customer',
name='taxcode',
field=models.CharField(choices=[('TAX', 'Tax'), ('EXEMPT', 'Exempt'), ('BLAH', ...)], max_length=6),
),
```

There should be a way to indicate to django that migrations don't need updating.

### Request or proposal

proposal

### Additional Details

Original discussion [here](https://forum.djangoproject.com/t/migration-optimizer-does-not-optimize-choices/4645).

The current way around this issue, which is only possible since dj5, is to define a separate function for each Choices enum, and pass that to model choices..
```python
def get_taxcode_choices():
return Customer.TaxCode.choices
```
```python
class Customer(models.Model):
class TaxCode(models.TextChoices):
TAX = 'TAX', 'Tax'
EXEMPT = 'EXEMPT', 'Exempt'

taxcode = models.CharField(max_length=6, choices=get_taxcode_choices)
```

This gets repetitive and noisy _very_ quickly.

### Implementation Suggestions

There were some early thoughts to have migration generator handle this magically (ie not unfurl `.choices` prematurely if a django `Choices` is detected), however to avoid magic or cause backwards compatibility issues, the more sensible approach is probs to just introduce an explicit callable classmethod on the `Choices` model.

I propose we add a `model_choices` method:

```python
class Choices(enum.Enum, metaclass=ChoicesType):
@classmethod
def model_choices(cls):
return cls.choices

...
```

Users can then do this:
```python
class Customer(models.Model):
class TaxCode(models.TextChoices):
TAX = 'TAX', 'Tax'
EXEMPT = 'EXEMPT', 'Exempt'

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

which results in migrations generating out like this initially (and not generating new operations from thereon):
```python
migrations.CreateModel(
name='Customer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('taxcode', models.CharField(choices=Customer.TaxCode.model_choices, max_length=6)),
],
),
```

Credits to @bayangan1991 for initial idea.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.