python Django 中的单表继承

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/241250/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 19:43:03  来源:igfitidea点击:

Single Table Inheritance in Django

pythondjangodjango-modelssingle-table-inheritance

提问by sutee

Is there explicit support for Single Table Inheritance in Django? Last I heard, the feature was still under development and debate.

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

Are there libraries/hacks I can use in the meantime to capture the basic behavior? I have a hierarchy that mixes different objects. The canonical example of a corporation structure with an Employee class, subclasses for types of employees, and a manager_id (parent_id) would be a good approximation of the problem I am solving.

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

In my case, I would like to represent the idea that an employee can manage other employees while being managed by a different employee. There are not separate classes for Manager and Worker, which makes this hard to spread across tables. Sub-classes would represent types of employees-programmers, accountants, sales, etc and would be independent of who supervises who (OK, I guess it's no longer a typical corporation in some respect).

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

采纳答案by montylounge

There are currently two forms of inheritance in Django - MTI (model table inheritance) and ABC (abstract base classes).

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

I wrote a tutorialon what's going on under the hood.

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

You can also reference the official docs on model inheritance.

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

回答by Bj?rn Lindqvist

I think the OP is asking about Single-Table Inheritance as defined here:

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

Relational databases don't support inheritance, so when mapping from objects to databases we have to consider how to represent our nice inheritance structures in relational tables. When mapping to a relational database, we try to minimize the joins that can quickly mount up when processing an inheritance structure in multiple tables. Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.

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

That is, a single database table for a whole hierarchy of entity classes. Django does not support that kind of inheritance.

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

回答by julx

See my attempt:

看我的尝试:

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

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

An emulation of "table per hierarchy" a.k.a. "single table inheritance" in Django. The base class must hold all the fields. It's subclasses are not allowed to contain any additional fields and optimally they should be proxies.

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

Not exactly "single table inheritance", but close enough for many situations.

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

回答by djvg

Summary

概括

Django's proxy modelsprovide the basis for Single Table Inheritance.

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

However, some effort is required to make it work.

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

Skip to the end for a re-usable example.

跳到最后以获得可重用的示例。

Background

背景

Martin Fowlerdescribes Single Table Inheritance (STI) as follows:

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

Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.

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

This is precisely what Django's proxy model inheritancedoes.

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

Note, that, according to this blog post from 2010, proxymodels have been around since Django 1.1. However, only one of the other answers mentions them explicitly.

请注意,根据2010 年的这篇博客文章proxy模型自 Django 1.1 以来就已经存在。但是,其他答案中只有一个明确提到了它们。

A "normal" Django model is a concretemodel, i.e. it has a dedicated table in the database. There are two types of Django model that do nothave dedicated database tables, viz. abstractmodels and proxymodels:

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

  • Abstract models act as superclassesfor concrete models. An abstract model can define fields, but it does not have a database table. The fields are only added to the database tables for its concrete subclasses.

  • Proxy models act as subclassesfor concrete models. A proxy model cannot define new fields. Instead, it operates on the database table associated with its concrete superclass. In other words, a Django concrete model and its proxies all share a single table.

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

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

Django's proxy models provide the basis for Single Table Inheritance, viz. they allow different models to share a single table, and they allow us to define proxy-specific behavior on the Python side. However, Django's default object-relational mapping (ORM) does not provide all the behavior that would be expected, so a little customization is required. How much, that depends on your needs.

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

Let's build a minimal example, step by step, based on the simple data-model in the figure below:

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

simple party data model

简单的派对数据模型

Step 1: basic "proxy model inheritance"

第一步:基本的“代理模型继承”

Here's the content of models.pyfor a basic proxy inheritance implementation:

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

Personand Organizationare two types of parties.

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

Only the Partymodel has a database table, so allthe fields are defined on this model, including any fields that are specific either to Personor to Organization.

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

Because Party, Person, and Organizationall use the Partydatabase table, we can define a single ForeignKeyfield to Party, and assign instances of any of the three models to that field, as implied by the inheritance relation in the figure. Note, that, without inheritance, we would need a separate ForeignKeyfield for each model.

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

For example, suppose we define an Addressmodel as follows:

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

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

We can then initialize an Addressobject using e.g. Address(party=person_instance)or Address(party=organization_instance).

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

So far, so good.

到现在为止还挺好。

However, if we try to get a list of objects corresponding to a proxy model, using e.g. Person.objects.all(), we get a list of allPartyobjects instead, i.e. both Personobjects and Organizationobjects. This is because the proxy models still use the model manager from the superclass (i.e. Party).

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

Step 2: add proxy model managers

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

To make sure that Person.objects.all()only returns Personobjects, we need to assign a separate model managerthat filters the Partyqueryset. To enable this filtering, we need a field that indicates which proxy model should be used for the object.

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

To be clear: creating a Personobject implies adding a row to the Partytable. The same goes for Organization. To distinguish between the two, we need a column to indicate if a row represents a Personor an Organization. For convenience and clarity, we add a field (i.e. column) called proxy_name, and use that to store the name of the proxy class.

需要明确的是:创建Person对象意味着向Party表中添加一行。也是如此Organization。为了区分这两者,我们需要一列来指示一行是代表 aPerson还是 an Organization。为了方便和清晰,我们添加了一个名为 的字段(即列)proxy_name,并使用它来存储代理类的名称。

So, enter the ProxyManagermodel manager and the proxy_namefield:

因此,输入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()

Now the queryset returned by Person.objects.all()will only contain Personobjects (and the same for Organization).

现在,由 返回的查询集Person.objects.all()将只包含Person对象(对于 也是如此Organization)。

However, this does not work in the case of a ForeignKeyrelation to Party, as in Address.partyabove, because that will always return a Partyinstance, regardless of the value of the proxy_namefield (also see docs). For example, suppose we create an address = Address(party=person_instance), then address.partywill return a Partyinstance, instead of a Personinstance.

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

Step 3: extend the Partyconstructor

第 3 步:扩展Party构造函数

One way to deal with the related-field issue is to extend the Party.__new__method, so it returns an instance of the class specified in the 'proxy_name' field. The end result looks like this:

处理相关字段问题的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)

Now address.partywill actually return a Personinstance if the proxy_namefield is Person.

如果字段为 ,现在address.party将实际返回一个Person实例。proxy_namePerson

As a last step, we can make the whole thing re-usable:

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

Step 4: make it re-usable

第 4 步:使其可重复使用

To make our rudimentary Single-Table Inheritance implementation re-usable, we can use Django's abstract inheritance:

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

inheritance/models.py:

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__)

Then we can implement our inheritance structure as follows:

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

parties/models.py:

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)

More work may be required, depending on your needs, but I believe this covers some of the basics.

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

回答by Michael Bylstra

this might be of use: https://github.com/craigds/django-typed-modelsIt looks to be somewhat of an implementation of Single Table Inheritance but it has the limitation that subclasses can't have any extra fields.

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

there is also a fork that addresses the problem of not being able to create extra fields: https://github.com/KrzysiekJ/django-typed-models

还有一个 fork 解决了无法创建额外字段的问题:https: //github.com/KrzysiekJ/django-typed-models

update: I believe the fork may have been merged back in

更新:我相信叉子可能已经合并回来了

here is a recent discussion on the django developer mailing list about STI: https://groups.google.com/forum/#!msg/django-developers/-UOM8HNUnxg/6k34kopzerEJ

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

回答by James

I think you can do something akin to this.

我认为你可以做一些类似的事情。

I have to implement a solution for this problem myself, and here was how I solved it:

我必须自己为这个问题实现一个解决方案,这是我解决它的方法:

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'

This may have some locking issues... I'm not really sure how django handles that off the top of my head. Also, I didn't really test the above code, it's strictly for entertainment purposes, to hopefully put you on the right track.

这可能有一些锁定问题......我不太确定 django 如何处理这个问题。另外,我并没有真正测试上面的代码,它只是出于娱乐目的,希望能让你走上正轨。