Django 中的单表继承

发布于 2024-07-07 19:08:53 字数 329 浏览 12 评论 0 原文

Django 中是否明确支持单表继承? 据我所知,该功能仍在开发和争论中。

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

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

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

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.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

趁年轻赶紧闹 2024-07-14 19:08:53

摘要

Django 的 代理模型 为单表提供了基础遗产。

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

跳到最后查看可重复使用的示例。

背景

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

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

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

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

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

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

  • 代理模型充当具体模型的子类。 代理模型无法定义新字段。 相反,它对与其具体超类关联的数据库表进行操作。 换句话说,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 模型具有数据库表,因此所有字段都在此模型上定义,包括特定于 Person 或到组织

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

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

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

然后我们可以使用 Address(party=person_instance) 或 <代码>地址(party=organization_instance)。

到目前为止,一切都很好。

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

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

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

需要明确的是:创建 Person 对象意味着向 Party 表添加一行。 组织也是如此。 为了区分两者,我们需要一列来指示一行是代表 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 也是如此)。

但是,这在 ForeignKeyParty 关系的情况下不起作用,如上面的 Address.party 所示,因为它总是返回一个Party 实例,无论 proxy_name 字段的值如何(另请参阅 文档)。 例如,假设我们创建一个 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)

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

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

步骤 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)

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

Summary

Django's proxy models provide the basis for Single Table Inheritance.

However, some effort is required to make it work.

Skip to the end for a re-usable example.

Background

Martin Fowler describes Single Table Inheritance (STI) as follows:

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 inheritance does.

Note, that, according to this blog post from 2010, proxy models have been around since Django 1.1.

A "normal" Django model is a concrete model, i.e. it has a dedicated table in the database.
There are two types of Django model that do not have dedicated database tables, viz. abstract models and proxy models:

  • Abstract models act as superclasses for 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 subclasses for 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'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.

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.py for a basic proxy inheritance implementation:

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 and Organization are two types of parties.

Only the Party model has a database table, so all the fields are defined on this model, including any fields that are specific either to Person or to Organization.

Because Party, Person, and Organization all use the Party database table, we can define a single ForeignKey field 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 ForeignKey field for each model.

For example, suppose we define an Address model as follows:

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

We can then initialize an Address object using e.g. Address(party=person_instance) or 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 all Party objects instead, i.e. both Person objects and Organization objects. This is because the proxy models still use the model manager from the superclass (i.e. Party).

Step 2: add proxy model managers

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

To be clear: creating a Person object implies adding a row to the Party table. The same goes for Organization. To distinguish between the two, we need a column to indicate if a row represents a Person or 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.

So, enter the ProxyManager model manager and the proxy_name field:

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 Person objects (and the same for Organization).

However, this does not work in the case of a ForeignKey relation to Party, as in Address.party above, because that will always return a Party instance, regardless of the value of the proxy_name field (also see docs). For example, suppose we create an address = Address(party=person_instance), then address.party will return a Party instance, instead of a Person instance.

Step 3: extend the Party constructor

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:

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.party will actually return a Person instance if the proxy_name field is Person.

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

Step 4: make it re-usable

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

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:

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.

静水深流 2024-07-14 19:08:53

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

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

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

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

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.

浪漫人生路 2024-07-14 19:08:53

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

我写了一个 教程,了解幕后发生的事情。

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

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

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

You can also reference the official docs on model inheritance.

日暮斜阳 2024-07-14 19:08:53

查看我的尝试:

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

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

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

See my attempt:

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.

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

仲春光 2024-07-14 19:08:53

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

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

请叫√我孤独 2024-07-14 19:08:53

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

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

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 如何处理这个问题。 另外,我并没有真正测试上面的代码,它纯粹是为了娱乐目的,希望能让你走上正轨。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文