Django - 如何为模型指定数据库?

55
有没有一种方法可以指定一个模型(或应用程序)只使用一个特定的数据库?
我正在使用一个我不想更改的旧数据库。我有两个数据库-“默认”是sqlite,可用于管理等,而另一个是旧数据库。我使用inspectdb为旧数据库(部分)创建了一个模型,并将其设置为 managed = False。但是,是否有一种方法在模型本身中指定它仅适用于特定的数据库?
我发现您可以在某些查询集等中使用 指定using=databasename,但对于诸如 Databrowse (以及可能还有通用视图?)等情况,这样做没什么用。也许不能指定数据库是Databrowse的缺点,但似乎指定它的正确位置是模型本身...
接着我想也许答案是编写一个自定义模型管理器,它仅涉及到我的旧数据库 - 但文档中没有提到任何类似的内容。
我是否只是对多个数据库可能如何使用的Django世界有不同的心理模型?
7个回答

71

您无法为模型指定数据库,但可以在自定义的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': {
        ....
    }
}

19

简单的解决方法是将管理器设置为始终使用特定数据库来处理模型。请查看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 怎么样? - alexzander
这看起来恰好是最受欢迎的答案说不可能的东西。 - NeilG

18

12

我发现你可以使用这个管理器轻松地路由模型:

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 ='数据库名称'和此管理器添加到您的模型中,即可正常工作。

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

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

    class Meta:
        abstract = True

跟着这个步骤,你只需要像这样扩展它。不是:

class Customer(models.Model):

只需要这样做,它就可以运行:

class Customer(SecondDbBase):

附注: 我不确定这是否是一个好的做法或最佳解决方案,但它可行并且轻松地路由到其他数据库 :)

另外,我仅将其用于读写未由Django(managed=False)管理的表格,因此如果您需要为其创建迁移,则不确定是否有效。可能仍需要使用DATABASE_ROUTERS


我将前两个类添加到了我的 models.py 文件中,并按照您的示例更改了类定义。但是我遇到了错误 TypeError: super() takes at least 1 argument (0 given)。为了在不同版本的 Perl 之间实现交叉兼容性,请将 super() 替换为 super(SecondDbManager, self)。除此之外,它完美地工作了,我喜欢可以将所有更改都保存在一个地方的功能。 - msb
它正在工作。谢谢。 - alexzander

10

在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__ 的小写传递,因此我们需要将该属性转换为小写来构建查找字典。

然后应该运行迁移:

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

6

已测试适用于Django 2.2和pytest。
为了简化优秀的@chris-schon 答案,只需进行一些改进:

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 数据库的模型创建 params 嵌套类。
  2. 我们利用了已经是 Django 的一部分的 Meta 嵌套类。
  3. 路由器即使在测试时也可以不使用 allow_migrate

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

class LegacyModel(LegacyDbModel):
    # ... docs, fields, etc.

    class Meta(LegacyDbModel.Meta):
        managed = False
        db_table = 'legacy_table'

但即使如此,它仍然更加符合Django的工作方式
记住

明确胜于含蓄。


0

您可以将数据库中的对象设置为默认值,或者创建特定的对象

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()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接