Фильтр Django() на поле родственной модели

Я думаю, что мне не хватает чего-то очень элементарного и фундаментального в том, как должен работать метод 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()

Ответ 1

Оказывается, есть две части. Во-первых, это отдельный() метод, для которого в документе говорится:

По умолчанию QuerySet не удаляет повторяющиеся строки. На практике, это редко бывает проблемой, поскольку простые запросы, такие как Blog.objects.all() не вводит возможность дублирования результата строк. Однако, если ваш запрос охватывает несколько таблиц, его можно получать повторяющиеся результаты при оценке QuerySet. То, когда youd используйте distinct().

Следующие выходы "2", как ожидалось:

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

Однако это не помогает с более сложным примером, который я дал, используя annotate(). Оказывается, это пример известной проблемы: https://code.djangoproject.com/ticket/10060.