Search code examples
pythondjangopostgresqldjango-modelsdjango-migrations

Django not migrating CharField with choices properly


I have a model Car with a CharField color, that has the choices of white, black and blue.

class Car(models.Model):

    WHITE = "White"
    BLACK = "Black"
    BLUE = "Blue"

    COLOR_CHOICES = (
        (WHITE, "White"),
        (BLACK, "Black"),
        (BLUE, "Blue"),
    )

    ...

    color = models.CharField(max_length=20, choices=COLOR_CHOICES, default=BLUE)

I already have created Car objects with a color. Now when I introduce a change to the choices (e.g. change BLUE to RED in all occurences, as well as in the default) and run the migrations, the Car objects of color BLUE that already exist do not get migrated to RED. And that's where the weirdness begins:

  1. When I use the django shell to check the objects, they appear as Car.BLUE (the choice that no longer exists).
  2. When I inspect the objects in the Django Admin, they appear as Car.WHITE.
  3. When I create a new object, it works - it becomes Car.RED automatically (picks up the default).

Questions:

  • Are there any specific steps that I missed when migrating choices for the CharField?
  • Why could this weird behavior be happening and how can I safely fix?
  • Do I have to manually (or through a script) fix the data?

I expect upon migration all existing Car.BLUE objects to migrate to Car.RED when I run the migrations, because this is the new default value. I tried to manually create a new object and it picks up the new default RED color. I also tried to manually change an existing object from BLUE to RED, which is also fine. There were no errors when applying the migrations.

Django version - 4.1.5 DB - PostgreSQL


Solution

  • Django will make migrations if you change the choices=… parameter [Django-doc], in fact Django will make a migration if you change any parameter. But the default migration handler… will not do anything when you change the choices. One can implement a different handler, but likely it is not worth the effort.

    This thus means that eventually no database changes will be done, so we will have to do this ourselves. If you want to modify data, you can make a data migration [Django-doc]. A data migration is just like a normal migration, except that we write how the database should change.

    We make a data migration with:

    python manage.py makemigrations --empty app_name

    In the migration file, we can then construct an update query:

    from django.db import migrations
    
    
    def red_to_blue(apps, schema_editor):
        Car = apps.get_model('app_name', 'Car')
        Car.objects.filter(color='Red').update(color='Blue')
    
    
    class Migration(migrations.Migration):
        dependencies = [
            ("app_name", "1234_some_migration"),
        ]
    
        operations = [
            migrations.RunPython(red_to_blue),
        ]

    If we then migrate, it will run the red_to_blue function, and migrate the data in the database.