Django中的单表继承

时间:2008-10-27 20:18:08

标签: python django django-models single-table-inheritance

Django中是否明确支持单表继承?最后我听说,这个特征仍处于开发和辩论之中。

我是否可以在此期间使用库/黑客来捕获基本行为?我有一个混合不同对象的层次结构。具有Employee类的公司结构的规范示例,雇员类型的子类以及manager_id(parent_id)将是我正在解决的问题的良好近似。

在我的情况下,我想表达一个想法,即员工可以在由其他员工管理的同时管理其他员工。 Manager和Worker没有单独的类,这使得这很难跨表传播。子类将代表员工类型 - 程序员,会计师,销售等,并且独立于谁监督谁(好吧,我想它在某些方面不再是典型的公司)。

6 个答案:

答案 0 :(得分:16)

我认为OP要求将单表继承称为defined here

  

关系数据库不支持继承,因此当从对象映射到数据库时,我们必须考虑如何在关系表中表示我们的良好继承结构。映射到关系数据库时,我们尝试最小化在多个表中处理继承结构时可以快速挂载的连接。单表继承将继承结构的所有类的所有字段映射到单个表中。

即,整个实体类层次结构的单个数据库表。 Django不支持这种继承。

答案 1 :(得分:15)

Django目前有两种形式的继承--MTI(模型表继承)和ABC(抽象基类)。

我写了tutorial关于幕后发生的事情。

您还可以参考model inheritance上的官方文档。

答案 2 :(得分:11)

摘要

Django的proxy models为单表继承提供了基础。

但是,需要付出一些努力才能使其正常工作。

跳到最后一个可重复使用的示例。

背景

Martin Fowler对单表继承(STI)的描述如下:

单表继承将继承结构的所有类的所有字段映射到单个表中。

这正是Django proxy model inheritance所做的。

请注意,根据blog post from 2010proxy模型自Django 1.1起就存在。但是,只有其他答案之一明确提到了它们。

“普通” Django模型是具体模型,即它在数据库中具有专用表。 有两种类型的Django模型, not 没有专用的数据库表,即。 抽象模型和代理模型:

  • 抽象模型充当具体模型的超类。抽象模型可以定义字段,但是没有数据库表。这些字段仅添加到其具体子类的数据库表中。

  • 代理模型充当具体模型的子类。代理模型无法定义新字段。相反,它对与其具体超类关联的数据库表进行操作。换句话说,Django具体模型及其代理都共享一个表。

Django的代理模型为单表继承提供了基础,即。它们允许不同的模型共享一个表,并且允许我们在Python端定义特定于代理的行为。但是,Django的默认对象关系映射(ORM)并未提供所有预期的行为,因此需要进行一些自定义。多少,取决于您的需求。

让我们根据下图中的简单数据模型逐步构建一个最小的示例:

simple party data model

步骤1:基本的“代理模型继承”

以下是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

PersonOrganization是两种类型的参与方。

只有Party模型具有数据库表,因此 all 字段都在此模型上定义,包括Person或{{ 1}}。

由于OrganizationPartyPerson都使用Organization数据库表,因此我们可以为Party定义一个ForeignKey字段,并将三个模型中的任何一个的实例分配给该字段,如图中继承关系所暗示的。请注意,如果没有继承,则每个模型都需要一个单独的Party字段。

例如,假设我们定义一个ForeignKey模型,如下所示:

Address

然后我们可以使用例如来初始化class Address(models.Model): party = models.ForeignKey(to=Party, on_delete=models.CASCADE) 对象AddressAddress(party=person_instance)

到目前为止,很好。

但是,如果我们尝试使用以下方式获取与代理模型相对应的对象列表: Address(party=organization_instance),我们得到的是 all Person.objects.all()个对象的列表,即Party个对象和Person个对象。这是因为代理模型仍然使用超类(即Organization)中的模型管理器。

步骤2:添加代理模型管理器

为确保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相同)。

但是,在与OrganizationForeignKey关系的情况下(如上述Party),这是行不通的,因为无论如何,它将始终返回Address.party实例Party字段的值(另请参见docs)。例如,假设我们创建一个proxy_name,那么address = Address(party=person_instance)将返回一个address.party实例,而不是一个Party实例。

步骤3:扩展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实例。

最后一步,我们可以使整个过程可重复使用:

第4步:使其可重复使用

要使基本单表继承实现可重用,我们可以使用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