[Django]-Django – how to specify a database for a model?

18👍

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/

71👍

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': {
        ....
    }
}

19👍

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.

12👍

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.

👤rain01

10👍

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

6👍

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:

  1. We don’t need a params nested class for models from default DB.
  2. We leverage Meta nested class which is already part of Django.
  3. Router works without 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.

0👍

You can set objects to default for your database or create specific objects.

models.py:

class YourManagerName(models.Manager):
    def get_queryset(self):
        return super().get_queryset().using('YourDatabaseName')

class YourModel(models.Model):
    # table fields
    objects = models.Manager() # The default manager
    your_objects = YourManagerName() # The your specific manager

views.py:

def get_data(request):
    default_db_data = YourModel.objects.all()
    specific_db_data = YourModel.your_objects.all()
👤MRT

Leave a comment