Django - how to specify a database for a model?

前端 未结 6 1360
滥情空心
滥情空心 2020-11-29 04:53

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.

相关标签:
6条回答
  • 2020-11-29 05:33

    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.

    0 讨论(0)
  • 2020-11-29 05:39

    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
    
    0 讨论(0)
  • 2020-11-29 05:46

    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': {
            ....
        }
    }
    
    0 讨论(0)
  • 2020-11-29 05:51

    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 讨论(0)
  • 2020-11-29 05:52

    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/

    0 讨论(0)
  • 2020-11-29 05:53

    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.

    0 讨论(0)
提交回复
热议问题