【问题标题】:Django - how to specify a database for a model?Django - 如何为模型指定数据库?
【发布时间】:2011-03-31 22:53:57
【问题描述】:

有没有办法指定一个模型(或应用程序,甚至)应该只使用一个特定的数据库?

我正在使用不想更改的旧数据库。我有两个数据库——“默认”是一个可用于管理员等的 sqlite 数据库,以及旧数据库。我使用inspectdb 为(部分)遗留数据库创建了一个模型,它有managed = False。但是有没有办法在模型本身中指定它只适用于特定的数据库?

我看到您可以在某些查询集等中使用specify using=databasename,但这对Databrowse(可能还有通用视图?)之类的东西没有好处。无法指定数据库可能是 Databrowse 的一个缺点,但似乎指定它的正确位置是模型......

然后我想也许答案是编写一个自定义的model manager,它只引用我的旧数据库 - 但文档没有提到类似的东西。

对于如何使用多个数据库,对于 Django 世界,我是否有不同的心智模型?

【问题讨论】:

    标签: django django-models multiple-databases django-database


    【解决方案1】:

    您不能为模型指定数据库,但可以在自定义 DB 路由器类中定义它。

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

    【讨论】:

      【解决方案2】:

      据我所知,您不能直接使用模型指定数据库,因为它会阻止应用程序可重复使用,但从我在文档中可以看到:

      https://docs.djangoproject.com/en/1.8/topics/db/multi-db/

      【讨论】:

        【解决方案3】:

        我发现您可以使用此管理器非常简单地路由模型:

        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
        

        只需将use_db='databasename' 和此经理添加到您的模型中即可。

        或者为了进一步简化,我为它创建了一个基本模型:

        class SecondDbBase(models.Model):
            use_db = 'my_second_db'
            objects = SecondDbManager()
        
            class Meta:
                abstract = True
        

        有了这个,你需要做的就是像这样扩展它。而不是:

        class Customer(models.Model):
        

        只要这样做就可以了:

        class Customer(SecondDbBase):
        

        附言。我不确定这是一个好的做法还是最好的解决方案,但它可以工作并且路由到其他数据库是一件轻而易举的事:)

        PPS。我只使用这些来读取和写入不受 Django 管理的表(managed = False),所以如果您需要为它们创建迁移,我不确定它是否有效。可能仍然需要使用DATABASE_ROUTERS

        【讨论】:

        • 我将前 2 个类添加到我的 models.py 并更改了我的类定义作为您的示例。但我得到了错误TypeError: super() takes at least 1 argument (0 given)。对于 perl 不同版本之间的交叉兼容性,请将 super() 替换为 super(SecondDbManager, self)。除此之外,它运行良好,我喜欢我可以将所有更改保存在一个地方。
        • 它的工作。谢谢。
        【解决方案4】:

        简单的解决方案是将管理器设置为始终为模型使用特定数据库。查看 Django 的using

        例子:

        class User(models.Model):
            birth_date = models.DateField()
        
            class Meta:
                managed = False
                db_table = 'myotherapp_user'
        
        User.objects = User.objects.using('myotherdb')
        

        然后您可以使用User.objects,它将始终使用'myotherdb' 数据库而不是'default'

        请注意,来自不同数据库的模型之间的关系将不起作用,但这是 Django 的一个问题,因为它不支持开箱即用。

        【讨论】:

        • 不使用using怎么样?
        【解决方案5】:

        以 Mark 的出色回答为基础 - 以下是如何根据模型的属性将模型读/写操作和迁移路由到不同的数据库

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

        在此处查看文档:https://docs.djangoproject.com/en/3.0/topics/db/multi-db/#database-routers

        model_name 参数在运行时作为 model.__name__ 的小写字母传递,因此我们通过将此属性转换为 lower 来构建查找字典。

        迁移应该运行为

        python3 manage.py migrate app --database default
        python3 manage.py migrate app --database otherdb
        

        【讨论】:

          【解决方案6】:

          使用 Django 2.2 和 pytest 测试。
          只是为了精简一点优秀的@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)
          

          它有几个小好处:

          1. 对于来自 default DB 的模型,我们不需要 params 嵌套类。
          2. 我们利用 Meta 嵌套类,它已经是 Django 的一部分。
          3. 即使在测试中,路由器也可以在没有 allow_migrate 的情况下工作。

          唯一的缺点是我们需要显式地从Meta继承:

          class LegacyModel(LegacyDbModel):
              # ... docs, fields, etc.
          
              class Meta(LegacyDbModel.Meta):
                  managed = False
                  db_table = 'legacy_table'
          

          但即便如此,aligned with how Django works.
          Remember:

          显式优于隐式。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-08-13
            • 2017-02-05
            • 2020-07-12
            • 2011-05-04
            • 1970-01-01
            • 2019-03-18
            • 2015-10-10
            相关资源
            最近更新 更多