Django查询集中如何过滤反向外键?

91

我有以下Django模型:

class Make:
   name = models.CharField(max_length=200)

class MakeContent:
   make = models.ForeignKey(Make)
   published = models.BooleanField()

我想知道是否有可能(不用直接编写SQL),生成一个包含所有Make和它们相关的MakeContent,且其中published = True的查询集。


你能具体说明一下你的问题吗? - endre
5个回答

75

是的,我认为您想要

make = Make.objects.get(pk=1)
make.make_content_set.filter(published=True)

或者也许

make_ids = MakeContent.objects.filter(published=True).values_list('make_id', flat=True)
makes = Make.objects.filter(id__in=make_ids)

1
你的第一个代码片段不起作用。它只获取了一个制造商的所有MakeContents,而需要获取所有制造商的MakeContents。_set适用于单个对象,但不适用于查询集。 - knite
添加 flat = True 的意义在哪里?根据定义,这些 ID 已经是唯一的,再次确保其唯一性可能需要额外的计算。 - pintoch
2
pintoch,flat=True并没有提供任何与唯一性相关的内容。它只会在仅请求单个字段时返回单个值,而不是元组。 - Rob
我相信在较新版本的Django中(我使用的是2.2),make.make_content_set应该改为make.makecontent_set - Sam Creamer

53

我知道这是一个很老的问题,但我还是要回答。因为我认为我的答案可以帮到其他人。我对模型进行了一些修改,具体如下。我使用的是Django 1.8。

class Make(models.Model):
    name = models.CharField(max_length=200)

class MakeContent(models.Model):
    make = models.ForeignKey(Make, related_name='makecontent')
    published = models.BooleanField()

我使用了以下查询集。

Make.objects.filter(makecontent__published=True)

为避免重复结果,您应该使用 distinct()

Make.objects.filter(makecontent__published=True).distinct()
我希望它能有所帮助。

8
当有多个指向同一制造商(Make)的“MakeContent”时,这将复制该制造商的条目。类似于select from make right join makecontent on make.id=makecontent.make_id,其中在“MakeContent”中有多行具有相同的 make_id - Shadi
8
你可以使用Make.objects.filter(makecontent__published=True).distinct()。 - user1012513

20

Django不支持反向外键查找的select_related()方法,因此在不离开Python的情况下,你所能做的最好的事情就是进行两个数据库查询。第一个查询是获取包含MakeContentspublished = True的所有Makes,第二个查询是获取所有MakeContents,其中published = True。然后你必须循环遍历并按照需要排列数据。这是一篇关于如何做到这一点的好文章:

http://blog.roseman.org.uk/2010/01/11/django-patterns-part-2-efficient-reverse-lookups/


3
请参考prefetch_related()方法,以使您提到的两个查询得到优化。 - B Robster

19

让我将Spike的文字答案翻译成代码,以供将来的查看者参考。请注意,每个'Make'都可以有零到多个'MakeContent'。

如果问者的意思是查询具有至少一个已发布的'MakeContent'的'Make',那么Jason Christa的第二个片段回答了这个问题。

该片段相当于

makes = Make.objects.select_related().filter(makecontent__published=True).distinct()

但是,如果提问者想要查询所有已发布的'MakeContent'并且与'Make'相匹配,则跟随上述的'makes'。

import operator
make_ids = [m.id for m in makes if 
    reduce(operator.and_, [c.published for c in m.makecontent_set.all()] ) 
]
makes_query = Make.objects.filter(id__in=make_ids)

包含所需的查询。


我认为只有这个答案符合问题。 - lengxuehx
这就是我想要的,但我想知道选择所有然后distinct()是否会有性能损失。 - MiDaa

3
再说一遍,不清楚问题是什么,但如果要求所有相关的MakeContent对象都必须已发布,则可以这样实现:
Make.objects.exclude(MakeContent_set__published=False)

如果其中至少有一个(就像其他答案中所述):

Make.objects.filter(MakeContent_set__published=True)

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