Is there a way to specify the database alias that is concerned by a RunPython operation in a Django migration?

816 views Asked by At

I have a Django project that uses two databases. I defined a Database Router and everything works fine when running migrations, except for RunPython migration operations : in this case I have to "manually" check in the RunPython code function on which database alias the code is run to decide whether or not to apply the given operations.

So far I have implemented a decorator that I use on every RunPython code function that checks whether or not to run the operation based on the current database alias. It works fine, but I was wondering if Django already provided a way to specify the database alias(es) concerned by a RunPython migration without having custom code. Is there such a way ?

For information, here is the decorator :

def run_for_db_aliases(database_aliases):
    def decorator(migration_function):
        def decorated(apps, schema_editor):
            if schema_editor.connection.alias not in database_aliases:
                return
            return migration_function(apps, schema_editor)
        return decorated
    return decorator

This allows me to define code for RunPython migrations like this :

@run_for_db_aliases(['default'])
def forwards_func(apps, schema_editor):
    # Perform data operations on models that are stored in the 'default' database
   ...

Is there a cleaner way to do this, like an option when instantiating a RunPython operation ?

EDIT:

Here is the models and database router I use.

my_project/my_app/models.py

class A(models.Model):
    # The table for this model is in the 'default' database
    name = models.CharField(max_length=128)

class B(models.Model):
    # The table for this model is in the 'other' database
    description = models.CharField(max_length=128)

my_project/db_routers.py

class MyDBRouter(object):

    def _is_in_other(self, model):
        return model._meta.app_label == 'my_app' and model._meta.model_name == 'b'

    def db_for_read(self, model, **hints):
        return 'other' if self._is_in_other(model) else None

    def db_for_write(self, model, **hints):
        return 'other' if self._is_in_other(model) else None

    def allow_relation(self, obj1, obj2, **hints):
        # Pointless in this example
        return None

    def allow_migrate(self, db, model):
        if db == 'other':
            return self._is_in_other(model)
        if self._is_in_other(model):
            return False
        return None
1

There are 1 answers

0
rparent On BEST ANSWER

Problem solved since Django 1.8 : https://docs.djangoproject.com/en/1.10/releases/1.8/#migrations

The migration operations RunPython and RunSQL now call the allow_migrate() method of database routers. The router can use the newly introduced app_label and hints arguments to make a routing decision. To take advantage of this feature you need to update the router to the new allow_migrate signature