【问题标题】:Sort Django QuerySet by distinct reverse FK按不同的反向 FK 对 Django QuerySet 进行排序
【发布时间】:2015-05-26 14:18:37
【问题描述】:

使用 ORM 应该不会太难;使用原始 SQL 非常简单。

我有一些模型:

class Actor(models.Model):
    last_name = models.CharField(max_length=64)
    specialty = models.ManyToManyField(Specialty, blank=True)
    allergy = models.ManyToManyField(Allergy, blank=True)
    #.... TONS MORE M2Ms HERE ....#

class Character(models.Model):
    actor = models.ForeignKey(Actor)
    name = models.CharField(max_length=64, null=True)

我相信这是正确的建模方式,因为一个演员可以扮演多个角色,但在大多数情况下,情况并非如此,例如:

m = Actor(last_name='Palin')
d = Character(actor=m, name='Dennis')
r = Character(actor=m, name='Right Head')

如果我想显示所有演员的列表,并获取他们所有的 m2m 关系,这很简单:

actors = Actor.objects.all().prefetch_related(specialty, allergy, ...)

有一系列非常复杂的过滤器可以根据用户输入应用于上述查询集,例如actors allergic to peanuts but not allergic to filth

现在,如果我想获得相同的查询集,按不同的角色名称排序,我知道的唯一方法是查询角色,选择相关的演员,并预取相关的 m2ms。

不幸的是,这将需要我重新编写所有过滤器。因为allergy__name='peanuts' 必须变成actor__allergy__name='peanuts'

答案是什么?我如何以DRY最可能的方式提供“按演员排序”和“按角色排序”?

【问题讨论】:

    标签: django many-to-many django-queryset


    【解决方案1】:

    如果你不想重写 Actor 过滤器,那么使用 Actor QS 来约束 Character 过滤器怎么样?示例:

    actors = Actor.objects.filter(allergy__name='peanuts')
    chars = Character.objects.filter(actor__in=actors)
    

    然后您可以随意订购chars

    编辑:我认为您遇到了问题。您希望 Characters 预取与 Actor 相关的其他记录。这是 select_related() ex 的一个很好的用例:

    actors = Actor.objects.filter(allergy__name='peanuts').select_related('character_set').prefetch_related()
    

    这应该会产生所有匹配的 Actors 以及包含在结果集中的相关角色记录,从而允许您对角色名称进行排序,而无需每次都访问数据库。最后一个 prefetch_related() 带来了所有 M2M 记录,假设您也想要这些记录。

    另一个编辑:从这里开始,有很多方法可以重新排序或重新组织结果数据。将其表示为字符记录(带有预加载的 Actor)的一种可能方式可能是:

    from itertools import chain
    by_chars = chain(*[a.character_set.all() for a in actors])
    

    或者,如果它更具可读性/可维护性:

    by_chars = []
    for a in actors:
        for c in a.character_set.all():
            by_chars.append(c)
    

    由于您已预加载所有内容,因此对结果集进行额外的排序和分组不需要额外访问数据库。

    【讨论】:

    • 好吧,这会起作用,但是没有prefetch_related 发生。模板端会有很多数据库活动。考虑 3000 多个演员和 5-6000 个角色。
    • 过滤不依赖于预取。请注意,我没有返回 Actor QS 的 results,而是使用它来过滤 Characters。这只是一次访问数据库。将 chars 作为 QS 排序/过滤完成后,如果您的用例需要,您可以申请 .prefetch_related()
    • 那么我如何显示所有演员,按角色名称排序。其中,在上面的示例中,“Palin”将有两个条目。这会导致很多查询。
    • 不完全:如果我选择所有演员,按演员姓名,我会得到一个,<Actor: Palin>。如果我按角色名称选择所有演员,我想要 2 个,但它将是相同的查询集。我需要返回<Actor: Palin, Char_name: 'Dennis'>, <Actor: Palin, Char_name: 'Right Head'>
    • @RobL 这只是对检索到的数据进行的一些额外分组。添加了几种可能的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 2014-08-07
    • 2016-12-27
    • 2017-09-02
    • 1970-01-01
    相关资源
    最近更新 更多