Django模型子类化的方法

15

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

这个问题 接近但不完全。 这篇文章 涉及到扁平/嵌套的主题,很有帮助,但仍然没有回答我的问题。还有许多其他关于相同主题的文章,但没有一个告诉我我想知道的东西。

背景

这些模型都具有各自独特的属性和一些共享的属性,并且我需要在另一个模型中引用它们,最理想的情况是有一个单一的入口,而不是为每个可能的模型都有一个字段。

我希望能够使用复杂的Django ORM查询涉及基类并在需要时通过子类进行过滤。例如 Event.objects.all() 返回所有事件。我知道 Django模型工具继承管理器 并打算在可能的情况下使用它。

此外,我将使用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

我需要翻译关于IT技术的内容。以下是需要翻译的内容:

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

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

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

可能的解决方案

  1. Use a GenericForeignKey.

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

    What I don't like about this is that I'll lose the querying capabilities Django ORM has, and I might need to do additional fiddling to get it working on admin. Not sure, never dealt with this before

  2. Flatten Event

    I can bring it all up to the base class and have flags or checks outside the model definition, something like:

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

    This might seem like the best idea at first, but of course there are other kind of fields, and that forces me to allow nulls/blanks for most of them (like the email field), losing the db level integrity check.

  3. OneToOne relationships

    Rather than abstract like on 1 or flatten on 2 it is possible to have a db table for each, where the models will look like:

    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)
    

    So far it solved the two initial problems, but now when I get to the admin interface, I'll have to customize each form to create the base Event before saving a SubEvent instance.

问题

  1. 有更好的方法吗?

  2. 我提出的任何选择都可以在任何方向上进行改进吗(ORM查询,DB约束,管理界面)?

3个回答

12

我思考了两个答案并结合建议提出了自己的答案。

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

简洁版(tl;dr)

django-polymorphic满足三个主要需求:

  1. 允许使用 Django ORM 查询语句
  2. 通过使用多表继承和每个子类一个表来保留数据库级别的约束条件
  3. 易于与 Django 管理界面集成

详细版

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

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

它还具有出色的Django管理集成,可无缝创建和编辑对象。

使用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

4

以下是我的看法。我对您在第三点中的设计并不确定。每个SubEvent都是Event的子类,并且与Event有一对一的关系?这不是同样的事情吗?

您提出的通用键(Generic Key)正是它设计的目的。

另一个可能性是使用混合多态性(Polymorphism with 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>

这样做可以避免子类中出现冗余的属性,因为如果这些属性都在父类中,那么它们就会重复。


复制粘贴错误。更新第三个案例的继承。 - alfetopito
我认为我理解了你关于Mixins的观点,但是在另一个模型中关于父类的引用会发生什么变化呢? - alfetopito
那是一个单独的问题 - 只需不要将事件设为抽象,并在其他模型中具有对事件的GFK。 - professorDante
如果我使用GFK,我最终会得到第一种解决方案,但我不明白为什么在这种情况下Event不应该是抽象的。 - alfetopito
选择GFK的原因是为了保持Event的抽象性。当然,我不会直接链接到事件类本身。我会更新问题以澄清这一点。 - alfetopito
显示剩余2条评论

2
为什么不考虑使用多表继承呢?
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在子模型和每个父模型之间引入链接”? - alfetopito
作为 Django,它会为您处理维护 OneToOne 关系的工作,因此我认为您提到的问题(“在保存 SubEvent 实例之前自定义每个表单以创建基本 Event”)已经不存在了,这使得问题更加简单。 - albar
@alfetopito:您能解释一下为什么多表继承不符合您的需求吗? - albar
多表继承本身对于ORM查询和管理集成并没有太大帮助。是的,它可以工作,但我正在寻找一种更简单的方法来完成这两个任务。我添加了一个答案来解释我的最终选择。 - alfetopito

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接