2013-06-16 6 views
11

Ich glaube, ich bin fehlt etwas sehr einfach und grundlegend wie Djangos filter() Methode arbeiten soll.Django filter() auf dem Feld von verwandten Modell

Verwendung der folgenden Modelle:

class Collection(models.Model): 
    pass 

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

und mit Daten versorgt, indem die Populate() -Funktion an der Unterseite der Frage zu stellen, versuchen, die folgenden in ./manage.py Shell ausführt:

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

war meine Erwartung, dass diese „2“ drucken würde, was die Anzahl der Kollektionen ist, dass mindestens ein Element mit Flag haben = True. Diese Erwartung wurde bei https://docs.djangoproject.com/en/1.5/topics/db/queries/#lookups-that-span-relationships auf der Dokumentation basiert, die ein Beispiel hat sagen: „In diesem Beispiel werden alle Eingabeobjekte mit einem Blog, dessen Name‚Beatles Blog‘“.

jedoch oberhalb der Anruf druckt tatsächlich „6“, die die Anzahl der Artikeldatensätzen ist, die Flagge haben = True. Die tatsächlichen Objekte, die zurückgegeben werden, sind jedoch Auflistungsobjekte. Es scheint, dass es das gleiche Collection-Objekt mehrmals zurückgibt, einmal für jeden entsprechenden Item-Datensatz mit Flag = True. Dies kann bestätigt werden durch:

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

die True druckt.

Ist das das richtige Verhalten? Wenn ja, was ist der Grund? Wenn es das ist, was erwartet wird, könnte die Dokumentation als streng korrekt interpretiert werden, aber es lässt sich nicht sagen, dass jedes Objekt mehrfach zurückgegeben werden kann.

Hier ist ein ähnliches Beispiel, das sehr überraschend zu sein scheint (oder falsch nur plain) Verhalten. Es fing mich in einem Fall, wo ein auszuschließen sind() Aufruf von einem Manager benutzerdefinierte Modell hinzugefügt wurde und der Anrufer wurde dann das Hinzufügen eines Filters():

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

ersten Fall druckt „[2,4]“ , aber der zweite druckt "[8,16]" !!!

Populate Funktion:

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

Antwort

12

Es stellt sich heraus gibt es zwei Teile dazu. Zuerst ist die distinct() -Methode, für die das Dokument sagt:

Standardmäßig wird ein QuerySet doppelte Zeilen nicht löschen. In der Praxis ist dies selten ein Problem, weil einfache Abfragen wie Blog.objects.all() nicht die Möglichkeit des doppelten Ergebnisses Reihen einzuführen. Wenn sich Ihre Abfrage jedoch über mehrere Tabellen erstreckt, ist es möglich, doppelte Ergebnisse zu erhalten, wenn ein QuerySet ausgewertet wird. Das ist, wenn Sie distinct() verwenden würden.

Die folgenden Ausgänge "2" wie erwartet:

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

Dies ist jedoch mit dem komplexeren Beispiel gab ich nicht hilft, mit Anmerkungen versehen mit(). Es stellt sich heraus, dass dies ein Beispiel für ein bekanntes Problem ist: https://code.djangoproject.com/ticket/10060.

Verwandte Themen