Search code examples
djangodjango-modelsdjango-south

How do I support an old and new database scheme at the same time in Django?


Current situation, caused by legacy:

class Foo(models.Model)
    field = models.BooleanField()

    @property
    def renamed_field(self):
        return self.field

    @renamed_field.setter
    def renamed_field(self, value):
        self.field = value

    obsolete_field = models.BooleanField()

Desired situation:

class Foo(models.Model)
    renamed_field = models.BooleanField()

No problem yet. South can handle the migration using db.rename_column and db.deletecolumn.

Problem: our Django app runs on multiple instances with a shared MySQL instance. When we deploy our code to production, we replace old instances with new instances one by one as new instances are booted. If we want to avoid downtime, the app models needs to support the new database scheme, and here's the catch: and while instances are replaced also the old database scheme.

We would like to avoid downtime.

A naive solution would be a two-step approach where we deploy, wait until all instances have been replaced, migrate, and enable the my_feature_switch immediately after migrating:

class OldFoo(models.Model)
    field = models.BooleanField()

    @property
    def renamed_field(self):
       return self.field

    @renamed_field.setter
    def renamed_field(self, value):
        self.field = value

    obsolete_field = models.BooleanField()

    class Meta:
        abstract = True

class NewFoo(models.Model)
    renamed_field = models.BooleanField()

    class Meta:
        abstract = True

if waffle.switch_is_active('my_feature_switch'):
    foo_model = NewFoo
else:
    foo_model = OldFoo

class Foo(widget_model_model):
    pass

I hope this shows the direction of a possible solution. It needs another deploy at some point to clean this up (basically renaming NewFoo to Foo and removing everything else).

The problem with the solution above is that we would need to restart all instances to respect the new waffle switch value. Restarting all servers is problematic. Preferably it respects a condition (waffle switch) during runtime.

  • Is there another way to make the model conditional?
  • Or maybe a completely different way to solve this problem? Maybe a conditional db_column for each field, for example?

We're on Django==1.6, South==1.0.


Solution

  • Completely different solution, rename object field, but specify old database column name using db_column parameter.

    class Foo(models.Model)
        renamed_field = models.BooleanField(db_column='obsolete_field')
    

    No migrations required.