Django 模型子类化方法

Posted

技术标签:

【中文标题】Django 模型子类化方法【英文标题】:Django model subclassing approaches 【发布时间】:2016-01-11 09:18:16 【问题描述】:

我正在设计一个新的 Django 应用程序,由于有多种可能性,我不确定哪个是最好的,因此我想征求意见,并希望改进我目前所得到的。

This question 接近但不完全。 This one 触及了有帮助的平面/嵌套主题,但仍然没有回答问题。 同一主题还有很多其他人,但没有人告诉我我想知道什么。

背景

这些模型具有每个具有一些共享属性的独特属性,我需要在另一个模型中引用它们,最好使用单个入口点,而不是为每个可能的模型都有一个字段。

我希望能够执行涉及基类的复杂 Django ORM 查询,并在需要时按子类进行过滤。例如 Event.objects.all() 返回所有事件。我知道Django model utils Inheritance Manager 并打算尽可能使用它。

另外,我将使用 django admin 来创建和管理对象,因此必须进行简单的集成。我希望能够直接创建一个新的 SubEvent,而不必先创建一个 Event 实例。

示例

为了说明,假设我有以下应用程序A 的模型。

class Event(models.Model):
    commom_field = models.BooleanField()

    class Meta:
        abstract = True

class SubEventA(Event):
    email = models.EmailField(unique=True)

class SubEventB(Event):
    title = models.TextField()

class SubEventC(Event):
    number = models.IntegerField(default=10)

# and so on

还有一个应用程序B,我希望能够在其中引用可以是任何类型的事件,例如:

class OtherModel(models.Model):
    event = models.ForeignKey('A.Event')

# This won't work, because `A.Event` is abstract.

可能的解决方案

    使用GenericForeignKey。

    # B.models.py
    class OtherModel(models.Model):
        content_type = models.ForeignKey(ContentType)
        object_id = models.PositiveIntegerField()
        event = GenericForeignKey('content_type', 'object_id')
    

    对此我不喜欢的是,我将失去 Django ORM 的查询功能,并且我可能需要做一些额外的操作才能让它在管理员上运行。不确定,以前从未处理过此问题

    扁平化Event

    我可以将其全部带到基类中,并在模型定义之外进行标志或检查,例如:

    class Event(models.Model):
        commom_field = models.BooleanField()
        email = models.EmailField(blank=True)
        title = models.TextField(blank=True)
        number = models.IntegerField(default=10)
    

    起初这似乎是最好的主意,但当然还有其他类型的字段,这迫使我允许大多数字段(如电子邮件字段)为空/空白,从而丢失了数据库级别的完整性检查。

    一对一关系

    而不是像 1 上的抽象或 2 上的扁平化,可以为每个都有一个 db 表,模型看起来像这样:

    class Event(models.Model):
        commom_field = models.BooleanField()
    
    class SubEventA(models.Model):
        event = models.OneToOneField(Event)
        email = models.EmailField(unique=True)
    
    class SubEventB(models.Model):
        event = models.OneToOneField(Event)
        title = models.TextField(blank=True)
    
    class SubEventC(models.Model):
        event = models.OneToOneField(Event)
        number = models.IntegerField(default=10)
    

    到目前为止,它解决了最初的两个问题,但现在当我进入管理界面时,我必须自定义每个表单以创建基础 Event,然后再保存 SubEvent 实例。

问题

    有更好的方法吗?

    我提出的任何选择都可以在任何方向(ORM 查询、数据库约束、管理界面)进行改进吗?

【问题讨论】:

【参考方案1】:

我的 .2c 对此。不确定您在#3 中的设计。每个 SubEvent 都是 Event 的子类,并且有一对一的 Event?不是一样的吗?

您对通用密钥的提议正是它的设计目的。

另一种可能性 - Mixins 的多态性。使用 Django-polymorphic 之类的东西,因此查询会返回您想要的子类。我一直在使用它,它非常有用。然后为将在许多类中重用的属性制作 Mixins。所以一个简单的例子,制作一个邮件 Mixin

class EmailMixin(models.Model):

    email = models.EmailField(unique=True)

    class Meta:
        abstract = True

那就用吧

class MySubEvent(EmailMixin, models.Model):

    <do stuff>

这样你就不会在子类上有多余的属性,就像它们都在父类中一样。

【讨论】:

复制粘贴错误。更新了第三种情况继承。 我想我明白你对 Mixins 的看法,但是在另一个模型中父类的引用有什么变化? 那是一个单独的问题 - 只是不要将 Event 抽象化,并在另一个模型中对 Event 进行 GFK。 如果我使用 GFK,我最终会选择第一个解决方案,但我不明白为什么 Event 对于这种情况不应该是抽象的。 当然可以是抽象的,那只是设计选择。它没有对错,但如果你真的去抽象,你就不能拥有 GFK。【参考方案2】:

为什么不是multi-table inheritance?

class Event(models.Model):
    commom_field = models.BooleanField()

class SubEventA(Event):
    email = models.EmailField(unique=True)

class SubEventB(Event):
    title = models.TextField(blank=True)

class SubEventC(Event):
    number = models.IntegerField(default=10)

【讨论】:

鉴于“继承关系引入了子模型与其每个父模型之间的链接(通过自动创建的 OneToOneField)”,这与第三种情况有何不同? 它更简单,因为 django 为您维护 OneToOne 关系,所以我认为您提到的问题(“在保存 SubEvent 实例之前自定义每个表单以创建基本事件”)没有还有更多。 @alfetopito:你能解释一下为什么多表继承不符合你的需要吗? 多表继承本身对 ORM 查询和管理集成没有多大帮助。是的,它有效,但我一直在寻找一种更简单的方法来做到这一点。我添加了一个答案来解释我的最终选择。【参考方案3】:

我对这两个答案都进行了思考,并根据这些建议提出了一些建议。因此,我添加了我自己的这个答案。

我选择使用django-polymorphic,@professorDante 推荐的非常好的工具。由于这是多表继承,@albar 的回答也有点正确。

tl;博士

django-polymorphic出席3个主要要求:

    允许 django ORM 查询样式 通过多表继承和每个子类一个表来保持数据库级别的约束 简单的 django 管理集成

加长版

Django-polymorphic 允许我从基类查询所有不同的事件实例,例如:

# assuming the objects where previously created
>>> Event.objects.all()
[<SubEventA object>, <SubEventB object>, <SubEventC object>]

它还有很棒的django admin integration,允许无缝创建和编辑对象。

使用 django-polymorphic 的模型如下所示:

# A.models.py
from polymorphic import PolymorphicModel

class Event(PolymorphicModel):
    commom_field = models.BooleanField()

    # no longer abstract

class SubEventA(Event):
    email = models.EmailField(unique=True)

class SubEventB(Event):
    title = models.TextField()

class SubEventC(Event):
    number = models.IntegerField(default=10)



# B.models.py

# it doesnt have to be polymorphic to reference polymorphic models
class OtherModel(models.Model):
    event = models.ForeignKey('A.Event')

此外,我只能从另一个类中引用基础模型,并且可以直接分配任何子类,例如:

>>> sub_event_b = SubEventB.objects.create(title='what a lovely day')
>>> other_model = OtherModel()
>>> other_model.event = sub_event_b

【讨论】:

以上是关于Django 模型子类化方法的主要内容,如果未能解决你的问题,请参考以下文章

你如何在 django 中扩展站点模型?

一个子类化 abc 的 django 模型,给出了元类冲突

如何在 Django 模型上存储字典?

如何使子类化的自定义 Django 表单字段不再是必需的?

在 Django 模型中存储项目的类别和子类别的最佳方法

Django 重写用户模型