Properly Implementing 'allow_migrate' In Database Router

I just cannot get my database routers to work correctly. I have read, and read, and researched, and tested, and tested, and still nothing.

Here is my router:

class TenantBaseRouter(object):
    def db_for_read(self, model, **hints):
        return 'tenant_base'

    def db_for_write(self, model, **hints):
        return 'tenant_base'

    def allow_relation(self, obj1, obj2, **hints):
        return True

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'test_app':
            if db == 'tenant_base' and model_name in tenant_models:
                return True

        return False

Yes, I have read the latest documentation about database routers, as printed here: Multiple databases | Django documentation | Django

Regarding allow_migrate, the documentation states, " Return True if the operation should run, False if it shouldn’t run, or None if the router has no opinion."

However, scroll down the page a bit, down to the examples, and this is the code example:

def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        Make sure the auth and contenttypes apps only appear in the
        'auth_db' database.
        """
        if app_label in self.route_app_labels:
            return db == "auth_db"
        return None

So, one part of the documentation indicates return True, False, or None. Another part of the documentation indicates return db == "auth_db" (which actually doesn’t even make sense).

I am specifying the exact database when I run the migrations:
python manage.py migrate --database=tenant_base

When I run the migrations, as specified, NONE of the models in question migrate to the table in question. Instead, all of Django’s built in models migrate over, despite not matching the conditions specified in allow_migrate.

I am at a total loss.

Actually, it makes a lot of sense, and is considered quite “pythonic”
The == operator is the Boolean comparison operator in Python. It has the value True if the two sides of the expression are equal, False if not equal.
That value (True or False) is what the return statement will return, which is exactly what is required.

Have you verified that the router is running at all? (Is it defined correctly in your setting file?)

Also note that migrate will show the migration has run. You would need to visually inspect the database to determine whether the operations were performed. The output of the migrate command is not going to give you any visible indication that an operation was skipped. Quoting from the docs:

makemigrations always creates migrations for model changes, but if allow_migrate() returns False , any migration operations for the model_name will be silently skipped when running migrate on the db .

1 Like

You can also use the showmigrations command to check which migrations have been applied to a given database.

Actually, it makes a lot of sense, and is considered quite “pythonic”
The == operator is the Boolean comparison operator in Python. It has the value True if the two sides of the expression are equal, False if not equal.
That value (True or False) is what the return statement will return, which is exactly what is required.

Pardon the digression, but as an IT professional and Python teacher, I disagree with your assertion. Clarity and readability are important aspects of writing maintainable code. Using explicit comparisons like return db == 'my_db_alias' can sometimes be confusing, especially for those unfamiliar with the codebase, so it’s better to provide more descriptive logic whenever possible. This is a best practice that has been around since I first began learning to code back in 1991. Simply labeling a bad practice with a fanciful name, such as “pythonic,” doesn’t somehow make it more acceptable.

Certainly, I have verified that the router is running, by implementing print statements at different points in the method, including one that prints out the parameter values for the current call.

Yes, of course I have visually inspected the database, using pgAdmin, which is why, in my OP, I stated:

When I run the migrations, as specified, NONE of the models in question migrate to the table in question. Instead, all of Django’s built in models migrate over, despite not matching the conditions specified in allow_migrate.

Reviewing that statement now, however, I realize that I misspoke. I should have stated that none of the models migrate to the database in question, which is evident by a lack of relevant tables. Somehow, Django’s built-in models are migrating to the database, though, despite not matching any of the conditions specified in my allow_migrate method.

That being said, if I switch my final return statement to false (as below), that resolves the issue of the unwanted migrations. However, the desired migrations are still not being applied, and I have found that returning false can cause issues when multiple routers are being utilized.

def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == 'test_app':
            if db == 'tenant_base' and model_name in tenant_models:
                return True

        return False

Oy! I solved the problem myself and, as with most bugs, it was an oversight on my part in connection with tired eyes. The problem was rooted in the following condition:

I had created 2 additional models and forgot to add them to the tenant_models library.

Thank you to all who contributed in trying to help me resolve this issue! :beers:

You may disagree all you want, and you may hold whatever opinions you desire - but for the benefit of those coming behind us, you’re in the minority.

It’s not my assertion. It’s the demonstrated fact by the python core developers themselves. There are more than 380 individual instances in the python standard library alone, using this syntax.

Additionally, there are more than 85 instances of this in Django, 90 in the pip package, and more than 900 in total among all the other different packages I have in my currently-active virtual environment containing about 150 packages. (Of that 150 packages, about 100 of them use this syntax, or on average, 9 instances each.)

You may not like this particular utilization of returning an expression, but to try and label it as a “bad practice” is a bit of an overreach. It has become a standard practice.

1 Like

Thanks for the unnecessary aside, but just because “everybody’s doing it” doesn’t make it right.

The official Django documentation is the SUPREME authority on such matters. Until the relevant documentation is updated to properly reflect the aforementioned as standardized, it’s not. Period.

1 Like

Maybe it’s because I am in Django 3.2, but showmigrations will show the migrations as “applied” even if they have been skipped by allow_migrate. Only by checking the database itself can we see that the tables weren’t created.

It doesn’t seem to tell you whether they were “applied” but whether they were “processed”.

Ah yes, sorry for the noise. That would be because allow_migrate() is on a per-operation basis, not per-migration. So a migration can be applied to a database, even though all of its operations were skipped because allow_migrate() returned False for them.

1 Like