Django 代理模型到不同的数据库
Django proxy model to different database
情况
我们有几个不同的应用程序,它们使用票证支持系统中的票证来实现不同类型的功能。
首先,我们有一个应用程序,其中有几个模型代表我们的票务支持系统 Kayako 的模型。这个应用程序不应该知道任何关于使用它的其他应用程序的信息,并且应该尽可能保持通用。由于此应用程序使用的是 Kayako 的现有表,因此我们在同一数据库中拥有它 运行。我们称此应用程序为 kayakodb
.
一个应用程序 links 客户从我们的客户数据库到票务支持系统中的票务。以前,该系统在我们的票务支持系统中有自己的票证表示,使用 kayakodb
提供的 API 查询票证。然后,它使用这种表示 link 客户和域的门票。然而,这太复杂而且不合逻辑。因此,我们选择将其切换为代理模型,并将代表 link 的模型移至客户,将域移至 kayakodb
。我们称此应用程序为 sidebar
.
另一个新应用程序以清晰的概览方式显示来自工单支持系统的工单以及电话,因此我们的支持部门可以轻松查看哪些电话和工单与哪些客户相关。此系统具有 sidebar
代理模型的代理模型,因为此应用程序还需要 sidebar
模型提供的某些功能以及新代理模型声明的其他一些功能。我们称这个项目为 WOW
.
sidebar
和 WOW
应用程序都是同一个 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
我尝试了什么
- 我试过不为两个代理模型指定
app_label
。这会创建正确的迁移,但会导致代理模型在 Coneybeach 数据库中查找 kayakodb.Ticket
模型。
- 我已经尝试为子classes 指定
abstract = True
,但不确定这是不是因为我仍然希望能够为模型使用管理器。
- 我考虑过将当前创建的迁移移动到实际的
kayakodb
项目,但我认为这不是一个好的解决方案。 kayakodb
不应该对其模型的实现或使用位置一无所知。
./manage.py check
returns 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
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
继承自Contact
和SidebarTicket
。这看起来像是在胡说八道,我想知道您没有看到错误 TypeError: Proxy model 'TicketWrapper' has more than one non-abstract model base class.
同一个联系人可以被更多票证共享。 (不是注册用户吗?难道他不能在历史问题中改变他的用户资料吗?)应该是Contact的外键,而不是多重继承。
情况
我们有几个不同的应用程序,它们使用票证支持系统中的票证来实现不同类型的功能。
首先,我们有一个应用程序,其中有几个模型代表我们的票务支持系统 Kayako 的模型。这个应用程序不应该知道任何关于使用它的其他应用程序的信息,并且应该尽可能保持通用。由于此应用程序使用的是 Kayako 的现有表,因此我们在同一数据库中拥有它 运行。我们称此应用程序为 kayakodb
.
一个应用程序 links 客户从我们的客户数据库到票务支持系统中的票务。以前,该系统在我们的票务支持系统中有自己的票证表示,使用 kayakodb
提供的 API 查询票证。然后,它使用这种表示 link 客户和域的门票。然而,这太复杂而且不合逻辑。因此,我们选择将其切换为代理模型,并将代表 link 的模型移至客户,将域移至 kayakodb
。我们称此应用程序为 sidebar
.
另一个新应用程序以清晰的概览方式显示来自工单支持系统的工单以及电话,因此我们的支持部门可以轻松查看哪些电话和工单与哪些客户相关。此系统具有 sidebar
代理模型的代理模型,因为此应用程序还需要 sidebar
模型提供的某些功能以及新代理模型声明的其他一些功能。我们称这个项目为 WOW
.
sidebar
和 WOW
应用程序都是同一个 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
我尝试了什么
- 我试过不为两个代理模型指定
app_label
。这会创建正确的迁移,但会导致代理模型在 Coneybeach 数据库中查找kayakodb.Ticket
模型。 - 我已经尝试为子classes 指定
abstract = True
,但不确定这是不是因为我仍然希望能够为模型使用管理器。 - 我考虑过将当前创建的迁移移动到实际的
kayakodb
项目,但我认为这不是一个好的解决方案。kayakodb
不应该对其模型的实现或使用位置一无所知。 ./manage.py check
returns 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
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
继承自Contact
和SidebarTicket
。这看起来像是在胡说八道,我想知道您没有看到错误 TypeError: Proxy model 'TicketWrapper' has more than one non-abstract model base class.
同一个联系人可以被更多票证共享。 (不是注册用户吗?难道他不能在历史问题中改变他的用户资料吗?)应该是Contact的外键,而不是多重继承。