How to properly set up custom User model and multiple databases with Django?

Question:

I created a custom application to authenticate users and have hard times to make everything work with multiple databases.

Basically, I create multiple Django projects and want a unique database for authentication and a custom AUTH_USER_MODEL. The databases are declared in settings.py with two routers, one for the auth, contenttypes, and authentication application, and the other router for the rest of the applications.

I successfuly migrated the custom User model and created createsuperuser with the option --database=auth_db but then, when I work in the admin section an create an object in one of my applications Django throws an error :

IntegrityError at /admin/django_celery_beat/crontabschedule/add/
insert or update on table "django_admin_log" violates foreign key constraint "django_admin_log_user_id_c564eba6_fk_authentication_user_id"
DETAIL:  Key (user_id)=(1) is not present in table "authentication_user".

As you can see, it says the user with ID 1 isn’t created, but I’m logged with it and I’m 100% sure the custom User model was created in the authentication app:

root@ec00652b9b9a:/app# python manage.py migrate authentication --database=auth_db
Operations to perform:
  Apply all migrations: authentication
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying authentication.0001_initial... OK

root@ec00652b9b9a:/app# python manage.py migrate contenttypes --database=auth_db
Operations to perform:
  Apply all migrations: contenttypes
Running migrations:
  No migrations to apply.

root@ec00652b9b9a:/app# python manage.py migrate auth --database=auth_db
Operations to perform:
  Apply all migrations: auth
Running migrations:
  No migrations to apply.

root@ec00652b9b9a:/app# python manage.py migrate admin --database=default
Operations to perform:
  Apply all migrations: admin
Running migrations:
  Applying admin.0001_initial... OK

root@ec00652b9b9a:/app# python manage.py migrate sessions --database=default
Operations to perform:
  Apply all migrations: sessions
Running migrations:
  Applying sessions.0001_initial... OK

Does it means Celery beat or admin is looking in the wrong database ? How could I investigate the issue further more ?

databases_router.py

class AuthRouter:
    route_app_labels = {'auth', 'contenttypes', 'authentication'}

    def db_for_write(self, model, **hints):
        if model._meta.app_label in self.route_app_labels:
            return 'auth_db'
        return None

    def db_for_read(self, model, **hints):
        if model._meta.app_label in self.route_app_labels:
            return 'auth_db'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if (
            obj1._meta.app_label in self.route_app_labels or
            obj2._meta.app_label in self.route_app_labels
        ):
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label in self.route_app_labels:
            return db == 'auth_db'
        return None


class AppsRouter:

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

    def db_for_read(self, model, **hints):
        return 'default'

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

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return True

settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': env("PROJECT_DB_NAME"),
        'HOST': env("PROJECT_DB_HOST"),
        'PORT': env("PROJECT_DB_PORT"),
        'USER': env("PROJECT_DB_USER"),
        'PASSWORD': env("PROJECT_DB_PASSWORD"),
        'OPTIONS': {'sslmode': 'require',
                    'options': '-c search_path=public'
                    },
    },
    'auth_db': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': env("AUTHENTICATION_DB_NAME"),
        'HOST': env("AUTHENTICATION_DB_HOST"),
        'USER': env("AUTHENTICATION_DB_USER"),
        'PORT': env("AUTHENTICATION_DB_PORT"),
        'PASSWORD': env("AUTHENTICATION_DB_PASSWORD"),
        'OPTIONS': {'sslmode': 'require',
                    'options': '-c search_path=public'
                    },
    },
}

DATABASE_ROUTERS = ['project.databases_router.AuthRouter', 'project.databases_router.AppsRouter']

AUTH_USER_MODEL = 'authentication.User'
Asked By: Florent

||

Answers:

I’m not sure about recent Django versions, but I do not recall Django (nor Postgres) being able to handle foreign key relationships with other databases (Django’s LogEntry model relies on one)

It might be easier to omit the Django admin and write something yourself, or patch the logging model.

However I’d reassess your requirement to separate the user from the default database at all. If you’re really separating the User from the context application, you probably shouldn’t rely on any foreign keys to user related data either.

Answered By: Hedde van der Heide
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.