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

时间:2017-01-11 11:18:57

标签: python django django-migrations proxy-classes

情况

<小时/> 我们有一些不同的应用程序,它们使用票证支持系统的票证来实现不同类型的功能。

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

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

另一个新的应用程序会在售票支持系统中显示门票,并在电话中进行清晰的概述,以便我们的支持部门可以轻松查看哪些电话和票证与哪些客户相关。此系统具有代理模型sidebar的代理模型,因为此sidebar模型提供的某些功能也是新代理模型声明的其他一些应用程序所必需的。我们称这个项目为WOW

sidebarWOW应用程序都是同一项目/存储库的一部分。我们将此存储库称为Coneybeach,它具有自己的数据库。但是,kayakodb是一个完全不相关的项目。它通过我们通过Coneybeach安装的需求文件包含在pip中。

问题

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

代码

<小时/> Ticket内的kayakodb模型:

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

ContactTicketWrapper继承自(根据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

我尝试了什么

  • 我没有尝试为两个代理模型指定app_label。这会创建正确的迁移,但会导致代理模型在Coneybeach数据库中查找kayakodb.Ticket模型。
  • 我尝试为子类指定abstract = True,但不确定这是因为我仍然希望能够使用模型的管理器。
  • 我考虑过将当前创建的迁移移动到实际的kayakodb项目,但我不认为这是一个很好的解决方案。 kayakodb不应该对其模型的实现或使用它们的位置有所了解。
  • ./manage.py check返回0个问题。

问题

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

修改

<小时/> 将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

3 个答案:

答案 0 :(得分:5)

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

可能有用的hacky修复方法是将代理模型的app_label更改为任何可以迁移到(在这种情况下为sidebar)的应用程序,并创建一个指向此的路由器代理模型从kayakodb读取和写入。

E.g。代理模型:

# 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

答案 1 :(得分:4)

tl; dr但是我问了你的问题router没有提及,所以我认为你要找的是Database Routers

答案 2 :(得分:4)

tl; dr使用

class Meta:
    managed = False

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

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

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

您也可以使用数据库路由器为kayakodb app(无处)创建表格,也不在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

优点是这会禁用任何当前或未来模型的迁移,但我建议在class Meta: managed = False中的某些位置添加文本kayakodb/models.py,以便向任何后来的开发者明确说明,因为他很容易忘记先读取路由器。

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

您的另一个问题是&#34; A proxy model must inherit from exactly one non-abstract model class...。&#34;,但您的代理模型TicketWrapper继承自ContactSidebarTicket。这看起来像一个废话,我想知道你没有看到错误TypeError: Proxy model 'TicketWrapper' has more than one non-abstract model base class.同一个联系人可以被更多的门票分享。 (它不是注册用户吗?在发布历史记录期间,他不能在用户配置文件中更改任何内容吗?)它应该是联系人的外键,而不是多重继承。