Django 代理模型到不同的数据库

Django proxy model to different database

情况


我们有几个不同的应用程序,它们使用票证支持系统中的票证来实现不同类型的功能。

首先,我们有一个应用程序,其中有几个模型代表我们的票务支持系统 Kayako 的模型。这个应用程序不应该知道任何关于使用它的其他应用程序的信息,并且应该尽可能保持通用。由于此应用程序使用的是 Kayako 的现有表,因此我们在同一数据库中拥有它 运行。我们称此应用程序为 kayakodb.

一个应用程序 links 客户从我们的客户数据库到票务支持系统中的票务。以前,该系统在我们的票务支持系统中有自己的票证表示,使用 kayakodb 提供的 API 查询票证。然后,它使用这种表示 link 客户和域的门票。然而,这太复杂而且不合逻辑。因此,我们选择将其切换为代理模型,并将代表 link 的模型移至客户,将域移至 kayakodb。我们称此应用程序为 sidebar.

另一个新应用程序以清晰的概览方式显示来自工单支持系统的工单以及电话,因此我们的支持部门可以轻松查看哪些电话和工单与哪些客户相关。此系统具有 sidebar 代理模型的代理模型,因为此应用程序还需要 sidebar 模型提供的某些功能以及新代理模型声明的其他一些功能。我们称这个项目为 WOW.

sidebarWOW 应用程序都是同一个 project/repository 的一部分。我们将此存储库称为 Coneybeach,它有自己的数据库。但是,kayakodb 是一个完全不相关的项目。它通过我们通过 pip.

安装的需求文件包含在 Coneybeach

问题


在为新设置创建迁移时,Django 会为已安装的 kayakodb 创建一个代理模型迁移,这当然是不行的。每当我们安装 kayakodb 的新版本时,它都会覆盖此迁移。更不用说 kayakodb 不应该知道哪些模型使用它了。

代码


kayakodb中的Ticket模型:

class Ticket(models.Model):
    """
    This model is a representation of the data stored in the "kayako" database table "swtickets". Minus a lot of stuff
    we don't use. If you add a field make sure it has the same name as the field in kayako.swtickets.
    """
    # Fields, functions and manager etc.

    class Meta:
        db_table = 'swtickets'
        managed = False

SidebarTicket里面的代理模型sidebar:

from kayakodb.models import Ticket    

class SidebarTicket(Ticket):
    class Meta:
        # Since this class is a wrapper we don't want to create a table for it. We only want to access the original
        # model as we always do, but provide a different interface (when it comes to functions). Proxy models allow us
        # to do this: https://docs.djangoproject.com/en/1.10/topics/db/models/#proxy-models
        proxy = True

        # Don't look for this model in the sidebar tables, but in the kayakodb tables.
        app_label = 'kayakodb'

    # Some extra functions

Contact class TicketWrapper 继承自(根据 Hynekcer 的要求)。该模型用作 TicketWrapper 的基础模型和另一个表示调用的模型(尽管据我所知该模型没有问题):

class Contact(models.Model):
    type = None

    class Meta:
        abstract = True

    def __getattr__(self, attr):
        if attr in ['customers', 'add_customer_id', 'remove_all_customers', 'byters', 'domainnames', 'add_domain_name',
                    'remove_domain_name', 'add_text', 'remove_text', 'texts', 'creation_date', 'add_tag', 'get_tags',
                    'remove_tag', 'identifier']:
            raise NotImplementedError('You should implement {}'.format(attr))
        raise AttributeError(attr)

TicketWrapper里面的代理模型WOW:

from sidebar.models import SidebarTicket

class TicketWrapper(Contact, SidebarTicket):
    class Meta:
        # Since this class is a wrapper we don't want to create a table for it. We only want to access the original
        # model as we always do, but provide a different interface (when it comes to functions). Proxy models allow us
        # to do this: https://docs.djangoproject.com/en/1.10/topics/db/models/#proxy-models
        proxy = True

        # Don't look for this model in the WOW database, but in the kayakodb database.
        app_label = 'kayakodb'

    # Some extra functions

我尝试了什么


问题


如何为位于不同数据库或项目中的模型创建代理模型?

编辑


kayakodb.Ticket 模型设置为非托管后,WOW 项目尝试为 kayakodb 中的 所有 模型创建迁移。结果:

Migrations for 'sidebar':
  0004_auto_20170116_1210.py:
    - Delete model Ticket

Migrations for 'kayakodb':
  0001_initial.py:
    - Create model Staff
    - Create model Tag
    - Create model Ticket
    - Create model TicketPost
    - Create model TicketTag
    - Create model TicketCustomer
    - Create model TicketDomain
    - Create proxy model SidebarTicket
    - Alter unique_together for ticketdomain (1 constraint(s))
    - Alter unique_together for ticketcustomer (1 constraint(s))
    - Create proxy model TicketWrapper

tl;dr 但是我在你的问题中搜索了一个没有提到的词 router,所以我认为你要找的东西是 Database Routers

正如@hynekcer 所说,如果 kayakodb 是现有数据库,则需要为其所有模型设置 managed = False。 然而,这仍然留下了在错误的应用程序中创建的代理模型的迁移问题 (kayakodb)。

可能有效的 hacky 修复是将代理模型的 app_label 更改为可以迁移到的任何应用程序(在本例中为 sidebar),并制作一个路由器将此代理模型指向从 kayakodb.

读取和写入

例如代理模型:

# in sidebar/models.py

class SidebarTicket(KayakoTicket):
    class Meta:
        proxy = True
        app_label = 'sidebar'

以及项目中使用它的路由器:

from django.conf import settings
from kayakodb.models import Ticket

class ProxyDatabaseRouter(object):
    def allow_proxy_to_different_db(self, obj_):
        # check if this is a sidebar proxy to the Ticket model in kayakodb
        return isinstance(obj_, Ticket) and obj_._meta.proxy and obj_._meta.app_label == 'sidebar'

    def db_for_read(self, model, **hints):
        if issubclass(model, Ticket) and model._meta.proxy and model._meta.app_label == 'sidebar':
            return 'kayakodb'
        # the rest of the method goes here

    def db_for_write(self, model, **hints):
        if issubclass(model, Ticket) and model._meta.proxy and model._meta.app_label == 'sidebar':
            return 'kayakodb'
        return None
        # the rest of the method goes here

    def allow_relation(self, obj1, obj2, **hints):
        if self.allow_proxy_to_different_db(obj1) or self.allow_proxy_to_different_db(obj2):
            return True
        # the rest of the method goes here

tl;dr 使用

class Meta:
    managed = False

对于数据库中不应由 Django 控制的所有模型。 (即 kayakodb)


需要注意的是,PyPI Kayako 是 Python API(没有 Djago)到一些用另一种语言编写的 Kayako 应用程序。

从您那里得知 Kayako 和 WOW 在不同的数据库中是很有用的,但这不是基本信息。 Django 允许例如一个模型存在于两个数据库中:一个主数据库和一个辅助数据库。最重要的是元选项,在本例中为 managed = False 选项。这是针对 Integrating Django with a legacy database 的情况。这并不仅仅意味着一个非常古老的项目,而是一个不是用 Python+Django 编写或不支持迁移并且不共享尚未应用迁移的信息的项目。也许如果新版本的 Kayako 将向数据库添加新字段,则您不需要读取该字段,或者如果您将其添加到模型,Django 不负责将字段添加到数据库,因为它由 Kayako 升级控制。

您也可以使用数据库路由器不为 kayakodb 应用程序(无处)创建表,也不在 kayakodb 数据库中创建表,例如对于 Django 用户和组:

文件 myrouter.py 或类似文件

class MyRouter(object):
    allow_migrate(db, app_label, model_name=None, **hints):
        if app_label == 'kayakodb' or db == 'kayakodb':
            return False

文件settings.py

DATABASE_ROUTERS = ['path.to.myrouter.MyRouter',...]
# ... if another router has been defined previously

优点是这会禁用任何当前或未来模型的迁移,但我建议在 kayakodb/models.py 的评论中的某处添加文本 class Meta: managed = False 以便以后清楚开发人员,因为他很容易忘记先阅读路由器。

您可以在最小和最大版本的 Kayako API 上编写项目版本的依赖项,但不能以迁移的形式。


您的另一个问题是“A proxy model must inherit from exactly one non-abstract model class...”,但是您的代理模型TicketWrapper 继承自ContactSidebarTicket。这看起来像是在胡说八道,我想知道您没有看到错误 TypeError: Proxy model 'TicketWrapper' has more than one non-abstract model base class. 同一个联系人可以被更多票证共享。 (不是注册用户吗?难道他不能在历史问题中改变他的用户资料吗?)应该是Contact的外键,而不是多重继承。