Django ManyToMany Filter ()

131

Ich habe ein Modell:

class Zone(models.Model):
    name = models.CharField(max_length=128)
    users = models.ManyToManyField(User, related_name='zones', null=True, blank=True)

Und ich muss einen Filter nach dem Vorbild von:

u = User.objects.filter(...zones contains a particular zone...)

Es muss ein Filter für den Benutzer sein und es muss ein einzelner Filterparameter sein. Der Grund dafür ist, dass ich einen URL-Querystring erstelle, um die Änderungsliste der Administratorbenutzer zu filtern:http://myserver/admin/auth/user/?zones=3

Es scheint einfach zu sein, aber mein Gehirn kooperiert nicht!

Andy Baker
quelle
8
Ich bin mir nicht sicher, ob ich dich richtig verstehe - ist das nicht User.objects.filter(zones__id=<id>)oder nicht User.objects.filter(zones__in=<id(s)>)gut dafür?
Tomasz Zieliński
Das ist User.objects.filter(zones__in=<id(s)>)User.objects.filter(zones__id__in=<id(s)>)
Tomasz Zieliński
21
Ich wollte nur darauf hinweisen, dass es nur funktioniert, wenn related_name gesetzt ist. zone_set würde zum Beispiel nicht funktionieren. Ich habe eine gute halbe Stunde damit

Antworten:

155

Ich wiederhole nur, was Tomasz gesagt hat.

Es gibt viele Beispiele für FOO__in=...Stilfilter in den Viele-zu-Viele- und Viele-zu-Eins- Tests. Hier ist die Syntax für Ihr spezifisches Problem:

users_in_1zone = User.objects.filter(zones__id=<id1>)
# same thing but using in
users_in_1zone = User.objects.filter(zones__in=[<id1>])

# filtering on a few zones, by id
users_in_zones = User.objects.filter(zones__in=[<id1>, <id2>, <id3>])
# and by zone object (object gets converted to pk under the covers)
users_in_zones = User.objects.filter(zones__in=[zone1, zone2, zone3])

Bei der Arbeit mit Abfragesätzen wird überall die doppelte Unterstrichsyntax (__) verwendet .

istruble
quelle
Danke @maxm. Aktualisiert mit einem aktuelleren Link zu einigen Beispielen.
istruble
9
doppelter Unterstrich (argh. 3 Stunden verloren zu diesem)
reabow
Können Sie bitte sagen, was zu tun ist, wenn ich möchte, dass die Benutzer, die sich in einer Reihe von Zonen befinden, nicht nur eine von ihnen sind? Nehmen wir an, Sie finden Benutzer, die sich in Zone1, Zone3, .. und Zone 10 befinden
FRR
Schauen Sie sich die ...__inBeispiele danach an # filtering on a few zones, by id. Diese zeigen die Filterung nach mehreren IDs / Objekten (in diesem Fall). Geben Sie einfach die IDs / Objekte für Zone1, Zone3 und Zone10 ein, die Sie interessieren. Oder fügen Sie bei Bedarf eine vierte hinzu.
istruble
Vielen Dank. Ich habe nur nach einem einzelnen Wert gefiltert, anstatt nach einem Array, das den einzelnen Wert enthält.
Zypro
36

Beachten Sie, dass Sie möglicherweise .distinct () hinzufügen möchten, wenn sich der Benutzer in mehreren in der Abfrage verwendeten Zonen befindet. Andernfalls erhalten Sie einen Benutzer mehrmals:

users_in_zones = User.objects.filter(zones__in=[zone1, zone2, zone3]).distinct()
QB.
quelle
1

Eine andere Möglichkeit, dies zu tun, besteht darin, die Zwischentabelle durchzugehen. Ich würde dies im Django ORM folgendermaßen ausdrücken:

UserZone = User.zones.through

# for a single zone
users_in_zone = User.objects.filter(
  id__in=UserZone.objects.filter(zone=zone1).values('user'))

# for multiple zones
users_in_zones = User.objects.filter(
  id__in=UserZone.objects.filter(zone__in=[zone1, zone2, zone3]).values('user'))

Es wäre schön, wenn es das .values('user')angegebene nicht benötigen würde , aber Django (Version 3.0.7) scheint es zu brauchen.

Der obige Code generiert am Ende SQL, das ungefähr so ​​aussieht:

SELECT * FROM users WHERE id IN (SELECT user_id FROM userzones WHERE zone_id IN (1,2,3))

Das ist schön, weil es keine Zwischenverknüpfungen gibt, die dazu führen könnten, dass doppelte Benutzer zurückgegeben werden

Sam Mason
quelle
Hiya. Dies ist an sich keine Antwort. Sie sollten einen Kommentar hinzufügen oder die Antwort von QB bearbeiten, anstatt eine zusätzliche Teilantwort hinzuzufügen.
Andy Baker
Ja - wenn Sie Ihre Antwort so bearbeiten möchten, dass sie für sich genommen vollständig ist (es sei denn, Sie haben genug Karma, um die Antwort von QB zu bearbeiten?), Dann wäre das die beste Wahl. Idealerweise gibt es bei StackOverflow "eine richtige Antwort". Normalerweise funktioniert es nicht ganz so gut, aber es lohnt sich, darauf zu zielen.
Andy Baker
@ AndyBaker stimmte zu! im nachhinein sollte die Antwort von QB wahrscheinlich ein Kommentar zu der Antwort von istruble sein, während ich denke, dass meine Antwort eindeutig genug ist, um eine separate Antwort zu rechtfertigen, aber na ja
Sam Mason