抽象基类的Django模型字段

Bri*_*ian 14 django class base abstract django-queryset

我已经搜索了堆栈溢出来寻找这个(可能是简单的)问题的答案,但我看到的大多数解决方案看起来都过于复杂且难以理解.

我有一个模型"Post",它是一个抽象的基类.模型"公告"和"事件"继承自Post.

现在我在其他模型中保留相关的事件和公告列表.例如,我在另一个模型中有"removed_events"和"removed_announcements"字段.

但是,在我的项目中,"removed_events"和"removed_announcements"的处理方式完全相同.没有必要消除"删除的事件"和"删除的公告"之间的歧义.换句话说,保持跟踪"removed_posts"的字段就足够了.

我不知道如何(或许不能)创建一个字段"removed_posts",因为Post是抽象的.但是,现在我觉得我在代码中重复自己(并且不得不做一些混乱 - 一些检查以确定我正在看的帖子是一个事件还是一个公告并将其添加到相应的删除字段).

这里最好的选择是什么?我可以使Posts非抽象,但Post对象本身永远不应该创建,我不认为我可以在非抽象对象上强制执行.

我对数据库的理解很薄弱,但我的印象是,由于连接,使Post非抽象会使数据库复杂化.这是一个大问题吗?

最后,在其他模型中还有其他字段,我想将event_list和announcement_list中的内容压缩到post_list中,但这些字段确实需要消除歧义.我可以根据帖子类型过滤post_list,但是对filter()的调用比单独直接访问事件和公告列表要慢,不是吗?这里有什么建议?

非常感谢您阅读本文.

Jos*_*ton 13

Django中有两种模型子类 - 抽象基类; 和多表继承.

抽象基类本身并不使用,也没有数据库表或任何形式的标识.它们只是一种缩短代码的方法,它通过在代码中而不是在数据库中对公共字段集进行分组.

例如:

class Address(models.Model):
    street = ...
    city = ...

    class Meta:
        abstract = True


class Employee(Address):
    name = ...

class Employer(Address):
    employees = ...
    company_name = ...
Run Code Online (Sandbox Code Playgroud)

这是一个人为的例子,但正如你所看到的,一个Employee不是一个Address,也不是一个Employer.它们都包含与地址相关的字段.这个例子中只有两个表; Employee,和Employer- 它们都包含Address的所有字段.雇主地址无法与数据库级别的员工地址进行比较 - 地址没有自己的密钥.

现在,通过多表继承(从地址中删除abstract = True),Address 确实拥有一个表格.这将产生3个不同的表格; Address,EmployerEmployee.雇主和员工都将拥有一个唯一的外键(OneToOneField)回到地址.

您现在可以引用地址而无需担心它是什么类型的地址.

for address in Address.objects.all():
    try:
        print address.employer
    except Employer.DoesNotExist: # must have been an employee
        print address.employee
Run Code Online (Sandbox Code Playgroud)

每个地址都有自己的主键,这意味着它可以自己保存在第四个表中:

class FakeAddresses(models.Model):
    address = models.ForeignKey(Address)
    note = ...
Run Code Online (Sandbox Code Playgroud)

多表继承就是你所追求的,如果你需要使用类型的对象Post而不必担心它是什么类型的Post.如果从子类访问任何Post字段,将会有连接开销; 但开销很小.它是一个独特的索引连接,应该非常快.

只需确保,如果您需要访问在查询集Post上使用select_related的那个.

Events.objects.select_related(depth=1)
Run Code Online (Sandbox Code Playgroud)

这将避免额外的查询来获取父数据,但会导致连接发生.因此,如果您需要Post,请仅使用select相关.

两个最后的笔记; 如果帖子既可以是公告也可以是活动,那么你需要做传统的事情,并通过ForeignKey链接到Post.在这种情况下,子类化不起作用.

最后一点是,如果连接在父级和子级之间具有性能关键,则应该使用抽象继承; 并使用通用关系来从表格中引用抽象帖子,这些表格的性能要低得多.

通用关系本质上存储如下数据:

class GenericRelation(models.Model):
    model = ...
    model_key = ...


DeletedPosts(models.Model):
    post = models.ForeignKey(GenericRelation)
Run Code Online (Sandbox Code Playgroud)

加入SQL会更加复杂(django可以帮助你),但它的性能也不如简单的OneToOne连接.如果OneToOne加入会严重损害应用程序的性能,那么您应该只需沿着这条路走下去,这可能不太可能.


rew*_*ten 2

通用关系和外键是您成功之路上的朋友。定义一个中间模型,其中一侧是通用的,然后另一侧将获得多态模型的相关列表。它只是比标准 m2m 连接模型稍微复杂一点,因为通用侧有两列,一列对应 ContentType(实际上是 FK),另一列对应实际链接模型实例的 PK。您还可以使用标准 FK 参数限制要链接的模型。你很快就会习惯它。

(现在我有了一个可以用来写字的实际键盘,这里有一个例子:)

class Post(models.Model):
    class Meta: abstract = True
    CONCRETE_CLASSES = ('announcement', 'event',)
    removed_from = generic.GenericRelation('OwnerRemovedPost',
        content_type_field='content_type',
        object_id_field='post_id',
    )

class Announcement(Post): pass

class Event(Post): pass

class Owner(models.Model):

    # non-polymorphic m2m
    added_events = models.ManyToManyField(Event, null=True)

    # polymorphic m2m-like property
    def removed_posts(self):
        # can't use ManyToManyField with through.
        # can't return a QuerySet b/c it would be a union.
        return [i.post for i in self.removed_post_items.all()]

    def removed_events(self):
        # using Post's GenericRelation
        return Event.objects.filter(removed_from__owner=self)


class OwnerRemovedPost(models.Model):
    content_type = models.ForeignKey(ContentType,
        limit_choices_to={'name__in': Post.CONCRETE_CLASSES},
    )
    post_id = models.PositiveIntegerField()
    post = generic.GenericForeignKey('content_type', 'post_id')
    owner = models.ForeignKey(Owner, related_name='removed_post_items')

    class Meta:
        unique_together = (('content_type', 'post_id'),)  # to fake FK constraint
Run Code Online (Sandbox Code Playgroud)

您无法像经典的多对多那样过滤到相关集合中,但是通过 中的正确方法Owner,并巧妙地使用具体类的管理器,您可以到达您想要的任何地方。