Skip to content

Instantly share code, notes, and snippets.

@merit-mthompson
Last active November 6, 2020 22:53
Show Gist options
  • Save merit-mthompson/5c266df91f4e61732bb71c97fad26ebd to your computer and use it in GitHub Desktop.
Save merit-mthompson/5c266df91f4e61732bb71c97fad26ebd to your computer and use it in GitHub Desktop.
Django Multi-DB ForeignKey

Multi-DB Django example.

Doing this we're able to load our example model into the admin, view it and all related bits and even save without issue. There are some issues when using foreign keys from separate databases in list views in the admin.

Most of this is documented at https://docs.djangoproject.com/en/3.1/topics/db/multi-db/. We've just modified it a bit so things live in settings.

Names for things might seem weird because we've changed them to protect the innocent...

# oracle_db1/routers.py (gists don't like slashes)
from django.conf import settings
class OracleDb1Router:
"""Oracle db1 schema rules for legacy DB.
Avoid read/write/migrate for apps using db1 schema unless we are also
trying to use the 'db1' DB as defined in settings.
When we leave a method out or return "None" we are allowing the default
routing to take over as defined in django.db.utils.ConnectionRouter.
More: https://docs.djangoproject.com/en/3.0/topics/db/multi-db/
"""
def _db_for_action(self, model, **hints):
"""Route to our database if one of our apps."""
if model._meta.app_label in settings.ORACLE_DB1_ROUTER_APP_LABELS:
return settings.ORACLE_DB1_ROUTER_DATABASE_NAME
return None
db_for_read = _db_for_action
db_for_write = _db_for_action
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""Only allow migration for our apps in the db1 db/schema.
This will only pass with "--database=db1" on your migration
command(s) on the CLI.
"""
# If in our DB/schema, only migrate our apps (block django_* bits)
if db == settings.ORACLE_DB1_ROUTER_DATABASE_NAME:
return app_label in settings.ORACLE_DB1_ROUTER_APP_LABELS
# If one of our apps, only migrate within our DB/schema
if app_label in settings.ORACLE_DB1_ROUTER_APP_LABELS:
return db == settings.ORACLE_DB1_ROUTER_DATABASE_NAME
return None
def allow_relation(self, obj1, obj2, **hints):
db_set = {
settings.ORACLE_DB1_ROUTER_DATABASE_NAME,
*settings.ORACLE_DB1_ROUTER_ALLOWED_RELATIONS,
}
if obj1._state.db in db_set and obj2._state.db in db_set:
return True
return None
# oracle_db2/models.py (gists don't like slashes)
from django.db import models
class SomeModel(models.Model):
db1_field = models.ForeignKey('oracle_db2.Directory', models.DO_NOTHING, db_column='db1_field')
class Meta:
db_table = 'some_table'
managed = False
# oracle_db2/routers.py (gists don't like slashes)
from django.conf import settings
class OracleDb2Router:
"""Oracle db2 schema rules for legacy DB.
Avoid read/write/migrate for apps using db2 schema unless we are also
trying to use the 'db2' DB as defined in settings.
When we leave a method out or return "None" we are allowing the default
routing to take over as defined in django.db.utils.ConnectionRouter.
More: https://docs.djangoproject.com/en/3.0/topics/db/multi-db/
"""
def _db_for_action(self, model, **hints):
"""Route to our database if one of our apps."""
if model._meta.app_label in settings.ORACLE_DB2_ROUTER_APP_LABELS:
return settings.ORACLE_DB2_ROUTER_DATABASE_NAME
return None
db_for_read = _db_for_action
db_for_write = _db_for_action
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""Only allow migration for our apps in the db2 db/schema.
This will only pass with "--database=db2" on your migration
command(s) on the CLI.
"""
# If in our DB/schema, only migrate our apps (block django_* bits)
if db == settings.ORACLE_DB2_ROUTER_DATABASE_NAME:
return app_label in settings.ORACLE_DB2_ROUTER_APP_LABELS
# If one of our apps, only migrate within our DB/schema
if app_label in settings.ORACLE_DB2_ROUTER_APP_LABELS:
return db == settings.ORACLE_DB2_ROUTER_DATABASE_NAME
return None
def allow_relation(self, obj1, obj2, **hints):
db_set = {
settings.ORACLE_DB2_ROUTER_DATABASE_NAME,
*settings.ORACLE_DB2_ROUTER_ALLOWED_RELATIONS,
}
if obj1._state.db in db_set and obj2._state.db in db_set:
return True
return None
# DATABASE
# ------------------------------------------------------------------------------
# Database: OracleDb1Router: Apps using our schema.
ORACLE_DB1_ROUTER_APP_LABELS = {'oracle_db1', }
# Database: OracleDb1Router: Prefix for django_migrations table.
ORACLE_DB1_MIGRATIONS_PREFIX = 'proj_foo_'
# Database: OracleDb1Router: DB name from settings.DATABASES.
ORACLE_DB1_ROUTER_DATABASE_NAME = 'db1'
# Database: OracleDb2Router: Apps using our schema.
ORACLE_DB2_ROUTER_APP_LABELS = {'oracle_db2', }
# Database: OracleDb2Router: Prefix for django_migrations table.
ORACLE_DB2_MIGRATIONS_PREFIX = 'proj_foo'
# Database: OracleDb2Router: DB name from settings.DATABASES.
ORACLE_DB2_ROUTER_DATABASE_NAME = 'db2'
# Database: OracleDb1Router: Allowed relations
ORACLE_DB1_ROUTER_ALLOWED_RELATIONS = {ORACLE_DB2_ROUTER_DATABASE_NAME, }
# Database: OracleDb2Router: Allowed relations
ORACLE_DB2_ROUTER_ALLOWED_RELATIONS = {ORACLE_DB1_ROUTER_DATABASE_NAME, }
# Database: Core
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.oracle',
'HOST': '',
'NAME': env('ORACLE_DSN'),
'PASSWORD': env('ORACLE_PROJ_FOO_PASSWORD'),
'PORT': '',
'USER': env('ORACLE_PROJ_FOO_USER'),
},
ORACLE_DB1_ROUTER_DATABASE_NAME: {
'ENGINE': 'django.db.backends.oracle',
'HOST': '',
'NAME': env('ORACLE_DSN'),
'PASSWORD': env('ORACLE_DB1_PASSWORD'),
'PORT': '',
'USER': env('ORACLE_DB1_USER'),
},
ORACLE_DB2_ROUTER_DATABASE_NAME: {
'ENGINE': 'django.db.backends.oracle',
'HOST': '',
'NAME': env('ORACLE_DSN'),
'PASSWORD': env('ORACLE_DB2_PASSWORD'),
'PORT': '',
'USER': env('ORACLE_DB2_USER'),
},
}
# Database: Routers for which of our DBs to use for what.
DATABASE_ROUTERS = [
'proj_foo.oracle_db1.routers.OracleDb1Router',
'proj_foo.oracle_db2.routers.OracleDb2Router',
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment