Search code examples
pythondjangoorm

Django Migrations Not Using Configured Database Connection


I am using supabase postgresql along with django. By default it uses the public schema, but now I want to link the user_id field in model to auth.users.id where auth is the schema, users is the table name and id is the UUID field

Models definition here

# auth/models.py
class SupabaseUser(models.Model):
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        verbose_name="User ID",
        help_text="Supabase managed user id",
        editable=False,
    )

    class Meta:
        managed = False
        db_table = "users"
# myapp/models.py
class MyModel(models.Model):
   user = models.ForeignKey(
        SupabaseUser,
        on_delete=models.CASCADE,
        verbose_name="Supabase User",
        help_text="Supabase user associated with the account",
        null=False,
    )

In the settings I have two database connections

DATABASES = {
    "default": dj_database_url.config(),
    "supabase_auth": dj_database_url.config(),
}
DATABASES["supabase_auth"]["OPTIONS"] = {
    "options": "-c search_path=auth",
}

And of course model router is configured as

from django.db.models import Model
from django.db.models.options import Options


class ModelRouter:
    @staticmethod
    def db_for_read(model: Model, **kwargs):
        return ModelRouter._get_db_schema(model._meta)

    @staticmethod
    def db_for_write(model: Model, **kwargs):
        return ModelRouter._get_db_schema(model._meta)

    @staticmethod
    def allow_migrate(db, app_label, model: Model, model_name=None, **kwargs):
        return True

    @staticmethod
    def _get_db_schema(options: Options) -> str:
        if options.app_label == "auth":
            return "supabase_auth"
        return "default"

When I use the ./manage.py shell and run the following script, it works

from auth.models import SupabaseUser
assert SupabaseUser.objects.count() == 1

But when I apply migration for myapp, I am getting the following error

$ ./manage.py makemigration myapp && ./manage.py migrate myapp
Operations to perform:
  Apply all migrations: myapp
Running migrations:
  Applying myapp.0013_mymodel_user...Traceback (most recent call last):
  File "/mnt/Projects/myapp/backend/.venv/lib/python3.11/site-packages/django/db/backends/utils.py", line 103, in _execute
    return self.cursor.execute(sql)
           ^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.errors.UndefinedTable: relation "users" does not exist
....
snip
....
django.db.utils.ProgrammingError: relation "users" does not exist

Solution

  • Actually I was able to solve this using migrations.RunSQL

    from django.db import migrations
    
    
    class Migration(migrations.Migration):
        dependencies = [
            ("auth", "0001_initial"),
            ("myapp", "0012_alter_mymodel_some_field"),
        ]
    
        operations = [
            migrations.RunSQL(
                sql=(
                    "ALTER TABLE myapp_mymodel ADD COLUMN user_id UUID;",
                    "ALTER TABLE myapp_mymodel ADD CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE;",
                ),
                reverse_sql=(
                    "ALTER TABLE myapp_mymodel DROP CONSTRAINT fk_user_id;",
                    "ALTER TABLE myapp_mymodel DROP COLUMN user_id;",
                ),
            ),
        ]
    
    

    In this sql field is executed whenever the migration is applied and reverse_sql is executed whenever the migration is reverted. Reverse SQL is optional, but it is generally added to cleanup the mess from migrations.