通过不同的反向FK对Django QuerySet进行排序

时间:2015-03-23 14:29:24

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

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

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

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

答案是什么?我如何以 DRY 方式提供“按演员排序”和“按字符排序”?

1 个答案:

答案 0 :(得分:0)

如果您不想重写Actor过滤器,那么如何使用Actor QS来约束Character过滤器呢?例如:

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

然后您可以按照自己的意愿订购chars

编辑:我认为,我发现了您遇到的问题。您希望使用与Actor相关的其他记录预取字符。这是select_related()ex:

的一个很好的用例
actors = Actor.objects.filter(allergy__name='peanuts').select_related('character_set').prefetch_related()

这应该会产生所有匹配的Actors以及结果集中包含的相关Character记录,允许您对Character name进行排序,而不是每次都访问数据库。最后prefetch_related()会带来所有M2M记录,假设您也需要这些记录。

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

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)

由于您已经预先加载了所有内容,因此结果集的其他排序和分组不应该需要额外的数据库访问。