Я думаю, что мне не хватает чего-то очень элементарного и фундаментального в том, как должен работать метод Django filter().
Используя следующие модели:
class Collection(models.Model):
pass
class Item(models.Model):
flag = models.BooleanField()
collection = models.ForeignKey(Collection)
и с данными, вызванными вызовом функции populate() в нижней части вопроса, попробуйте выполнить следующее в оболочке. /manage.py:
len(Collection.objects.filter(item__flag=True))
Мое предположение состояло в том, что это напечатало бы "2", то есть количество коллекций, у которых есть хотя бы один элемент с флагом = True. Это ожидание было основано на документации на https://docs.djangoproject.com/en/1.5/topics/db/queries/#lookups-that-span-relationships, в которой приведен пример: "В этом примере извлекаются все объекты Entry с блоком, чье имя" Beatles Blog "".
Тем не менее, вызов выше на самом деле печатает "6", это число записей элементов, которые имеют флаг = True. Фактически возвращенные объекты - объекты Collection. Кажется, что он возвращает один и тот же объект Collection несколько раз, один раз для каждой записи элемента с флагом = True. Это можно подтвердить:
queryset = Collection.objects.filter(item__flag=True)
queryset[0] == queryset[1]
который печатает True.
Это правильное поведение? Если да, то в чем причина? Если это то, что ожидается, документация может быть истолкована как строго правильная, но она не позволяет сказать, что каждый объект может быть возвращен несколько раз.
Вот пример, который кажется очень неожиданным (или просто неправильным). Это меня поймало в случае, когда пользовательский диспетчер модели добавлял вызов exclude(), а вызывающий затем добавлял фильтр():
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()