2013-06-16 2 views
11

Я думаю, что мне не хватает чего-то очень элементарного и фундаментального в том, как должен работать метод filter() Django.Django filter() на поле родственной модели

с использованием следующих моделей:

class Collection(models.Model): 
    pass 

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

и данными, полученными от вызова функции Заселите() в нижней части вопроса, Попробуйте выполнить следующие действия в ./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.

Это правильное поведение? Если да, то в чем причина? Если это то, что ожидается, документация может быть истолкована как строго правильная, но она не позволяет утверждать, что каждый объект может быть возвращен несколько раз.

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

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]" !!!

Populate функция:

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() 

ответ

12

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

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

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

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

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

Смежные вопросы