34

Django 中是否明确支持单表继承?上次我听说,该功能仍在开发和辩论中。

是否有我可以同时使用的库/黑客来捕获基本行为?我有一个混合不同对象的层次结构。具有 Employee 类、员工类型的子类和 manager_id (parent_id) 的公司结构的典型示例将是我正在解决的问题的一个很好的近似值。

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

4

6 回答 6

23

概括

Django 的代理模型为单表继承提供了基础。

但是,需要一些努力才能使其发挥作用。

跳到最后一个可重用的例子。

背景

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

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

这正是 Django 的代理模型继承所做的。

请注意,根据2010 年的这篇博文proxy模型自 Django 1.1 以来就已经存在。

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

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

  • 代理模型充当具体模型的子类。代理模型不能定义新字段。相反,它对与其具体超类关联的数据库表进行操作。换句话说,一个 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

PersonOrganization是两种类型的聚会。

只有Party模型有一个数据库表,所以所有字段都在这个模型上定义,包括特定于Person或 的任何字段Organization

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

例如,假设我们定义一个Address模型如下:

class Address(models.Model):
    party = models.ForeignKey(to=Party, on_delete=models.CASCADE)

Address然后我们可以使用 egAddress(party=person_instance)或初始化一个对象Address(party=organization_instance)

到现在为止还挺好。

但是,如果我们尝试获取与代理模型相对应的对象列表,使用 eg Person.objects.all(),我们将获得所有 Party对象的列表,即Person对象和Organization对象。这是因为代理模型仍然使用超类(即Party)的模型管理器。

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

为了确保Person.objects.all()只返回Person对象,我们需要分配一个单独的模型管理器来过滤查询Party集。要启用此过滤,我们需要一个字段来指示应该为对象使用哪个代理模型。

需要明确的是:创建一个Person对象意味着向Party表中添加一行。也是如此Organization。为了区分这两者,我们需要一个列来指示一行是代表 aPerson还是代表 a 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_nameaddress = Address(party=person_instance)address.partyPartyPerson

第三步:扩展Party构造函数

处理相关字段问题的一种方法是扩展该Party.__new__方法,因此它返回“proxy_name”字段中指定的类的实例。最终结果如下所示:

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)

如果字段为.Nowaddress.party将实际返回一个Person实例。proxy_namePerson

作为最后一步,我们可以使整个东西可重用:

第 4 步:使其可重复使用

为了使我们基本的单表继承实现可重用,我们可以使用 Django 的抽象继承:

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

from django.db import models
from inheritance.models import ProxySuper, ProxyManager


class Party(ProxySuper):
    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

    objects = ProxyManager()


class Organization(Party):
    class Meta:
        proxy = True

    objects = ProxyManager()


class Placement(models.Model):
    party = models.ForeignKey(to=Party, on_delete=models.CASCADE)

根据您的需要,可能需要做更多的工作,但我相信这涵盖了一些基础知识。

于 2020-03-27T21:54:23.090 回答
21

我认为 OP 正在询问此处定义的单表继承:

关系数据库不支持继承,所以当从对象映射到数据库时,我们必须考虑如何在关系表中表示我们良好的继承结构。当映射到关系数据库时,我们会尽量减少在处理多个表中的继承结构时可能快速安装的连接。单表继承将继承结构的所有类的所有字段映射到一个表中。

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

于 2009-11-12T08:25:21.663 回答
19

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

我写了一篇关于幕后发生的事情的教程。

您还可以参考模型继承的官方文档。

于 2008-10-28T14:29:20.990 回答
4

看看我的尝试:

http://djangosnippets.org/snippets/2408/

在 Django 中模拟“每个层次结构的表”又名“单表继承”。基类必须包含所有字段。它的子类不允许包含任何额外的字段,最好它们应该是代理。

不完全是“单表继承”,但对于许多情况来说足够接近。

于 2011-04-11T01:00:01.807 回答
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 是如何处理这个问题的。另外,我并没有真正测试上面的代码,这完全是为了娱乐目的,希望能让你走上正轨。

于 2010-03-05T00:28:58.310 回答
2

这可能有用:https ://github.com/craigds/django-typed-models 它看起来有点像单表继承的实现,但它的限制是子类不能有任何额外的字段。

这是最近关于 STI 的 django 开发者邮件列表的讨论: https ://groups.google.com/forum/#!msg/django-developers/-UOM8HNUnxg/6k34kopzerEJ

于 2013-02-02T12:31:11.727 回答