Django关联模型字段的filter()方法

24

我觉得我对Django的filter()方法应该如何工作缺少了一些基本的和根本性的东西。

使用以下模型:

class Collection(models.Model): 
    pass

class Item(models.Model):
    flag = models.BooleanField()
    collection =  models.ForeignKey(Collection)

并使用在问题底部调用populate()函数提供的数据,在./manage.py shell中尝试执行以下操作:

len(Collection.objects.filter(item__flag=True))

我的期望是输出"2",即至少有一个Item的flag为True的Collection数量。这个期望基于文档https://docs.djangoproject.com/en/1.5/topics/db/queries/#lookups-that-span-relationships中的一个例子:"该示例检索所有具有名称为'Beatles Blog'的Blog的Entry对象"。

然而,上述调用实际上打印了"6",这是拥有flag=True的Item记录数量。虽然实际返回的是Collection对象,但似乎它对应于每个具有flag=True的相应Item记录一次返回相同的Collection对象。可以通过以下方式确认:

queryset = Collection.objects.filter(item__flag=True)
queryset[0] == queryset[1]

输出 True。这是正确的行为吗?如果是,背后的原理是什么?如果确实如此,文档可能被解释为严格正确,但它遗漏了一个事实,即每个对象可以被多次返回。

下面是一个相关的示例,似乎表现出非常惊人的(或者只是纯粹错误的)行为。在一个自定义模型管理器添加 exclude() 调用,并且调用者随后添加 filter() 的情况下,它会让我陷入困境:

from django.db.models import Count    
[coll.count for coll in Collection.objects.filter(item__flag=True).annotate(count=Count("item"))]
[coll.count for coll in Collection.objects.exclude(item=None).filter(item__flag=True).annotate(count=Count("item"))]

第一个案例打印出了"[2,4]",但第二个案例却打印出了"[8,16]"!!!

填充函数:

def populate():
    Collection.objects.all().delete()

    collection = Collection()
    collection.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()

    collection = Collection()
    collection.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()
    item = Item(collection=collection, flag=True)
    item.save()

    collection = Collection()
    collection.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
    item = Item(collection=collection, flag=False)
    item.save()
1个回答

24

原来这有两个部分。首先是distinct()方法,文档中说:

默认情况下,QuerySet不会消除重复的行。实际上,这很少是一个问题,因为像Blog.objects.all()这样的简单查询不会引入重复的结果行的可能性。但是,如果您的查询涵盖多个表,则在评估QuerySet时可能会出现重复的结果。那就是你要使用distinct()的时候。

以下输出了预期的“2”:

len(Collection.objects.filter(item__flag=True).distinct())

然而,这并不能解决我所提供的更复杂的示例,使用annotate()。事实证明,这是一个已知问题的实例:https://code.djangoproject.com/ticket/10060


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