Is there a way to specify that a model (or app, even) should only ever use one particular database?
I am working with a legacy database that I don\'t want to change.
The simple solution is to set the manager to always use a specific database for the model. Look into Django's using
.
Example:
class User(models.Model):
birth_date = models.DateField()
class Meta:
managed = False
db_table = 'myotherapp_user'
User.objects = User.objects.using('myotherdb')
Then you can use User.objects
and it will always use the 'myotherdb'
database instead of 'default'
.
Note that relations between models from different databases will not work, but this is an issue with Django since it does not support this out of the box.
Building on Mark's excellent answer - here's how to route both model read/write operations and migrations to different databases based on an attribute of the model
# app/models.py
class SomeModel(models.Model):
class params:
db = 'default'
class SomeOtherDbModel(models.Model):
class params:
db = 'otherdb'
...
# app/dbrouters.py
import app.models
allmodels = dict([(name.lower(), cls) for name, cls in app.models.__dict__.items() if isinstance(cls, type)])
...
class MyDBRouter(object):
def db_for_read(self, model, **hints):
""" reading model based on params """
return getattr(model.params, 'db')
def db_for_write(self, model, **hints):
""" writing model based on params """
return getattr(model.params, 'db')
def allow_migrate(self, db, app_label, model_name = None, **hints):
""" migrate to appropriate database per model """
model = allmodels.get(model_name)
return(model.params.db == db)
# app/settings.py
DATABASE_ROUTERS = ('app.dbrouters.MyDBRouter',)
...
DATABASES = {
...
'otherdb': {
....
}
}
See docs here: https://docs.djangoproject.com/en/3.0/topics/db/multi-db/#database-routers
The model_name argument is passed at runtime as the lower case of the model.__name__
, hence why we built the lookup dictionary by casting this attribute to lower.
Migrations should then be run as
python3 manage.py migrate app --database default
python3 manage.py migrate app --database otherdb
You can't specify a database for a model, but you can define it in a custom DB router class.
# app/models.py
class SomeModel(models.Model):
...
# app/dbrouters.py
from app.models import SomeModel
...
class MyDBRouter(object):
def db_for_read(self, model, **hints):
""" reading SomeModel from otherdb """
if model == SomeModel:
return 'otherdb'
return None
def db_for_write(self, model, **hints):
""" writing SomeModel to otherdb """
if model == SomeModel:
return 'otherdb'
return None
# app/settings.py
DATABASE_ROUTERS = ('app.dbrouters.MyDBRouter',)
...
DATABASES = {
...
'otherdb': {
....
}
}
Tested with Django 2.2 and pytest.
Just to streamline a bit the excellent @chris-schon answer:
class LegacyDbModel(models.Model):
class Meta:
abstract = True
_db = 'legacy_db_alias'
class LegacyDbRouter(object):
def db_for_read(self, model, **hints):
""" reading model based on params """
if not hasattr(model, 'Meta'):
return None
return getattr(model.Meta, '_db', None)
def db_for_write(self, model, **hints):
""" writing model based on params """
if not hasattr(model, 'Meta'):
return None
return getattr(model.Meta, '_db', None)
It has couple of minor benefits:
params
nested class for models from default
DB.Meta
nested class which is already part of Django.allow_migrate
even for tests.The only drawback is that we need to inherit from Meta
explicitly:
class LegacyModel(LegacyDbModel):
# ... docs, fields, etc.
class Meta(LegacyDbModel.Meta):
managed = False
db_table = 'legacy_table'
But even that is more aligned with how Django works.
Remember:
Explicit is better than implicit.
As far as I know you can't specify the database directly with the model since it would kind of prevent the app from ever being reusable, but from what I can see in the docs:
https://docs.djangoproject.com/en/1.8/topics/db/multi-db/
I found that you can route models pretty simply with this manager:
class SecondDbManager(models.Manager):
def get_queryset(self):
qs = super().get_queryset()
# if `use_db` is set on model use that for choosing the DB
if hasattr(self.model, 'use_db'):
qs = qs.using(self.model.use_db)
return qs
Just add use_db='databasename'
and this manager to your model and it works.
Or to further simplify it I created a base model for it:
class SecondDbBase(models.Model):
use_db = 'my_second_db'
objects = SecondDbManager()
class Meta:
abstract = True
And with this all you need to do is extend it like so. Instead of:
class Customer(models.Model):
Just do this and it works:
class Customer(SecondDbBase):
PS. I'm not sure if it's a good practice or the best solution but it works and routing to other databases is a breeze :)
PPS. I've only ever used these for only reading and writing tables that are not managed by Django(managed = False
) so if you need to create migrations for them, I'm not sure if it works or not. Might still need to use DATABASE_ROUTERS
for that.