Django中是否明确支持单表继承?最后我听说,这个特征仍处于开发和辩论之中。
我是否可以在此期间使用库/黑客来捕获基本行为?我有一个混合不同对象的层次结构。具有Employee类的公司结构的规范示例,雇员类型的子类以及manager_id(parent_id)将是我正在解决的问题的良好近似。
在我的情况下,我想表达一个想法,即员工可以在由其他员工管理的同时管理其他员工。 Manager和Worker没有单独的类,这使得这很难跨表传播。子类将代表员工类型 - 程序员,会计师,销售等,并且独立于谁监督谁(好吧,我想它在某些方面不再是典型的公司)。
答案 0 :(得分:16)
我认为OP要求将单表继承称为defined here:
关系数据库不支持继承,因此当从对象映射到数据库时,我们必须考虑如何在关系表中表示我们的良好继承结构。映射到关系数据库时,我们尝试最小化在多个表中处理继承结构时可以快速挂载的连接。单表继承将继承结构的所有类的所有字段映射到单个表中。
即,整个实体类层次结构的单个数据库表。 Django不支持这种继承。
答案 1 :(得分:15)
答案 2 :(得分:11)
Django的proxy models为单表继承提供了基础。
但是,需要付出一些努力才能使其正常工作。
跳到最后一个可重复使用的示例。
Martin Fowler对单表继承(STI)的描述如下:
单表继承将继承结构的所有类的所有字段映射到单个表中。
这正是Django proxy model inheritance所做的。
请注意,根据blog post from 2010,proxy
模型自Django 1.1起就存在。但是,只有其他答案之一明确提到了它们。
“普通” Django模型是具体模型,即它在数据库中具有专用表。 有两种类型的Django模型, not 没有专用的数据库表,即。 抽象模型和代理模型:
抽象模型充当具体模型的超类。抽象模型可以定义字段,但是没有数据库表。这些字段仅添加到其具体子类的数据库表中。
代理模型充当具体模型的子类。代理模型无法定义新字段。相反,它对与其具体超类关联的数据库表进行操作。换句话说,Django具体模型及其代理都共享一个表。
Django的代理模型为单表继承提供了基础,即。它们允许不同的模型共享一个表,并且允许我们在Python端定义特定于代理的行为。但是,Django的默认对象关系映射(ORM)并未提供所有预期的行为,因此需要进行一些自定义。多少,取决于您的需求。
让我们根据下图中的简单数据模型逐步构建一个最小的示例:
以下是models.py
的内容,用于基本的代理继承实现:
from django.db import models
class Party(models.Model):
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
class Person(Party):
class Meta:
proxy = True
class Organization(Party):
class Meta:
proxy = True
Person
和Organization
是两种类型的参与方。
只有Party
模型具有数据库表,因此 all 字段都在此模型上定义,包括Person
或{{ 1}}。
由于Organization
,Party
和Person
都使用Organization
数据库表,因此我们可以为Party
定义一个ForeignKey
字段,并将三个模型中的任何一个的实例分配给该字段,如图中继承关系所暗示的。请注意,如果没有继承,则每个模型都需要一个单独的Party
字段。
例如,假设我们定义一个ForeignKey
模型,如下所示:
Address
然后我们可以使用例如来初始化class Address(models.Model):
party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
对象Address
或Address(party=person_instance)
。
到目前为止,很好。
但是,如果我们尝试使用以下方式获取与代理模型相对应的对象列表: Address(party=organization_instance)
,我们得到的是 all Person.objects.all()
个对象的列表,即Party
个对象和Person
个对象。这是因为代理模型仍然使用超类(即Organization
)中的模型管理器。
为确保Party
仅返回Person.objects.all()
对象,我们需要分配一个单独的model manager来过滤Person
查询集。要启用此过滤,我们需要一个字段,该字段指示应为对象使用哪种代理模型。
要清楚:创建Party
对象意味着向Person
表添加一行。 Party
也是如此。为了区分两者,我们需要一个列来指示一行代表Organization
还是Person
。为了方便和清楚起见,我们添加了一个名为Organization
的字段(即列),并使用该字段存储代理类的名称。
因此,输入proxy_name
模型管理器和ProxyManager
字段:
proxy_name
现在,from django.db import models
class ProxyManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(proxy_name=self.model.__name__)
class Party(models.Model):
proxy_name = models.CharField(max_length=20)
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
def save(self, *args, **kwargs):
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
class Person(Party):
class Meta:
proxy = True
objects = ProxyManager()
class Organization(Party):
class Meta:
proxy = True
objects = ProxyManager()
返回的查询集将仅包含Person.objects.all()
个对象(与Person
相同)。
但是,在与Organization
有ForeignKey
关系的情况下(如上述Party
),这是行不通的,因为无论如何,它将始终返回Address.party
实例Party
字段的值(另请参见docs)。例如,假设我们创建一个proxy_name
,那么address = Address(party=person_instance)
将返回一个address.party
实例,而不是一个Party
实例。
Person
构造函数处理相关字段问题的一种方法是扩展Party
方法,因此它返回在'proxy_name'字段中指定的类的实例。最终结果如下:
Party.__new__
如果class Party(models.Model):
PROXY_FIELD_NAME = 'proxy_name'
proxy_name = models.CharField(max_length=20)
name = models.CharField(max_length=20)
person_attribute = models.CharField(max_length=20)
organization_attribute = models.CharField(max_length=20)
def save(self, *args, **kwargs):
""" automatically store the proxy class name in the database """
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
def __new__(cls, *args, **kwargs):
party_class = cls
try:
# get proxy name, either from kwargs or from args
proxy_name = kwargs.get(cls.PROXY_FIELD_NAME)
if proxy_name is None:
proxy_name_field_index = cls._meta.fields.index(
cls._meta.get_field(cls.PROXY_FIELD_NAME))
proxy_name = args[proxy_name_field_index]
# get proxy class, by name, from current module
party_class = getattr(sys.modules[__name__], proxy_name)
finally:
return super().__new__(party_class)
字段为address.party
,现在Person
实际上将返回一个proxy_name
实例。
最后一步,我们可以使整个过程可重复使用:
要使基本单表继承实现可重用,我们可以使用Django的抽象继承:
Person
:
inheritance/models.py
然后我们可以按如下方式实现我们的继承结构:
import sys
from django.db import models
class ProxySuper(models.Model):
class Meta:
abstract = True
proxy_name = models.CharField(max_length=20)
def save(self, *args, **kwargs):
""" automatically store the proxy class name in the database """
self.proxy_name = type(self).__name__
super().save(*args, **kwargs)
def __new__(cls, *args, **kwargs):
""" create an instance corresponding to the proxy_name """
proxy_class = cls
try:
field_name = ProxySuper._meta.get_fields()[0].name
proxy_name = kwargs.get(field_name)
if proxy_name is None:
proxy_name_field_index = cls._meta.fields.index(
cls._meta.get_field(field_name))
proxy_name = args[proxy_name_field_index]
proxy_class = getattr(sys.modules[cls.__module__], proxy_name)
finally:
return super().__new__(proxy_class)
class ProxyManager(models.Manager):
def get_queryset(self):
""" only include objects in queryset matching current proxy class """
return super().get_queryset().filter(proxy_name=self.model.__name__)
:
parties/models.py
根据您的需要,可能需要做更多的工作,但是我认为这涵盖了一些基础知识。
答案 3 :(得分:3)
看我的尝试:
http://djangosnippets.org/snippets/2408/
在Django中模拟“每个层次结构的表”a.k.a.“单表继承”。基类必须包含所有字段。它的子类不允许包含任何其他字段,并且最佳它们应该是代理。
不完全是“单表继承”,但在很多情况下足够接近。
答案 4 :(得分:2)
我认为你可以做类似的事情。
我必须自己实现这个问题的解决方案,这就是我解决它的方法:
class Citrus(models.Model)
how_acidic = models.PositiveIntegerField(max_value=100)
skin_color = models.CharField()
type = models.CharField()
class TangeloManager(models.Manager)
def get_query_set(self):
return super(TangeloManager, self).get_query_set().filter(type='Tangelo')
class Tangelo(models.Model)
how_acidic = models.PositiveIntegerField(max_value=100)
skin_color = models.CharField()
type = models.CharField()
objects = TangeloManager()
class Meta:
# 'appname' below is going to vary with the name of your app
db_table = u'appname_citrus'
这可能有一些锁定问题......我不确定django是如何解决这个问题的。此外,我没有真正测试上面的代码,它完全是为了娱乐目的,希望能让你走上正轨。
答案 5 :(得分:2)
这可能有用:https://github.com/craigds/django-typed-models 它看起来有点像单表继承的实现,但它有一个限制,即子类不能有任何额外的字段。
还有一个fork解决了无法创建额外字段的问题: https://github.com/KrzysiekJ/django-typed-models
更新:我认为fork可能已经合并回来了
这是关于STI的django开发者邮件列表的最新讨论: https://groups.google.com/forum/#!msg/django-developers/-UOM8HNUnxg/6k34kopzerEJ