Django模型子类化方法

时间:2015-10-13 14:48:40

标签: python django django-models orm

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

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

背景

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

我希望能够执行涉及Base类的复杂Django ORM查询,并在需要时按SubClass进行过滤。 E.g 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.

可能的解决方案

  1. 使用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的查询功能,我可能需要做额外的小工作才能让它在管理上工作。不确定,在

  2. 之前从未处理过这个问题
  3. 展平Event

    我可以把它全部带到基类,并在模型定义之外有标志或检查,如:

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

    这看起来似乎是最好的想法,但当然还有其他类型的字段,这迫使我允许大多数字段(如电子邮件字段)的空值/空格,丢失数据库级别完整性检查。

  4. OneToOne关系

    不是像在 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。 / p>

  5. 问题

    1. 有更好的方法吗?

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

3 个答案:

答案 0 :(得分:3)

我的.2c就此而言。不确定你在#3的设计。每个SubEvent都是Event的子类,并且与Event一对一?那不是一回事吗?

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

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

类EmailMixin(models.Model):

email = models.EmailField(unique=True)

class Meta:
    abstract = True

然后使用它

class MySubEvent(EmailMixin, models.Model):

    <do stuff>

这样你就没有子类的冗余属性,就像它们都在父类中一样。

答案 1 :(得分:2)

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

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

TL;博士

django-polymorphic参加了3个主要要求:

  1. 允许django ORM查询样式
  2. 通过具有多表继承和每个子类的一个表
  3. 来保持数据库级别约束
  4. Easy django admin integration
  5. 更长的版本

    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
    

答案 2 :(得分:1)

为什么不是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)