Django中的单表继承

sut*_*tee 25 python django django-models single-table-inheritance

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

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

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

djv*_*jvg 19

概括

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
Run Code Online (Sandbox Code Playgroud)

Person并且Organization是两种类型的派对。

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

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

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

class Address(models.Model):
    party = models.ForeignKey(to=Party, on_delete=models.CASCADE)
Run Code Online (Sandbox Code Playgroud)

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

到现在为止还挺好。

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

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

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

需要明确的是:创建Person对象意味着向Party表中添加一行。也是如此Organization。为了区分这两者,我们需要一列来指示一行是代表 aPerson还是 an 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()
Run Code Online (Sandbox Code Playgroud)

现在返回的查询集Person.objects.all()将只包含Person对象(对于Organization)。

但是,这在与 的ForeignKey关系的情况下不起作用Party,如上Address.party所示,因为Party无论proxy_name字段的值如何,它都将始终返回一个实例(另请参阅docs)。例如,假设我们创建了一个address = Address(party=person_instance),然后address.party将返回一个Party实例,而不是一个Person实例。

第 3 步:扩展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)
Run Code Online (Sandbox Code Playgroud)

如果字段为 ,现在address.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__)
Run Code Online (Sandbox Code Playgroud)

然后我们可以实现我们的继承结构如下:

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)
Run Code Online (Sandbox Code Playgroud)

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


小智 17

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

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

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

  • 该问题的标题是“ Django中的单表继承”。这个答案能回答问题吗? (2认同)

Bjö*_*ist 17

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

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

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