Filtr Django ManyToMany ()

141

Mam modelkę:

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

I muszę zbudować filtr w następujący sposób:

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

Musi to być filtr użytkownika i musi to być pojedynczy parametr filtru. Powodem tego jest to, że konstruuję zapytanie dotyczące adresu URL, aby filtrować listę zmian użytkownika administratora:http://myserver/admin/auth/user/?zones=3

Wydaje się, że to powinno być proste, ale mój mózg nie współpracuje!

Andy Baker
źródło
8
Nie jestem pewien, czy ja ci rację - nie jest User.objects.filter(zones__id=<id>)ani User.objects.filter(zones__in=<id(s)>)dobry do tego?
Tomasz Zieliński
W porządku :) Przy okazji User.objects.filter(zones__in=<id(s)>)prawdopodobnie powinno byćUser.objects.filter(zones__id__in=<id(s)>)
Tomasz Zieliński
24
Chciałem tylko wskazać każdemu, kto to szuka w Google, że działa to tylko wtedy, gdy ustawiono related_name. Na przykład zone_set nie zadziała. Zmarnowałem na to dobre pół godziny :-)

Odpowiedzi:

166

Powtarzam tylko to, co powiedział Tomasz.

Istnieje wiele przykładów FOO__in=...filtrów stylów w testach wiele do wielu i wiele do jednego . Oto składnia dla twojego konkretnego problemu:

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

Składnia podwójnego podkreślenia (__) jest używana w każdym miejscu podczas pracy z zestawami zapytań .

istruble
źródło
Dzięki @maxm. Zaktualizowano o bardziej aktualne łącze do kilku przykładów.
istruble
10
podwójne podkreślenie (ok. 3 godziny stracone do tego)
reabow
Czy możesz powiedzieć, co zrobić, jeśli chcę, aby użytkownicy znajdowali się w zestawie stref, a nie tylko w jednej z nich? Powiedzmy, że znajdź użytkownika, który jest w strefie 1, strefie 3, .. i strefie 10
FRR
Spójrz na ...__inponiższe przykłady # filtering on a few zones, by id. Te pokazują filtrowanie dla wielu identyfikatorów / obiektów (w tym przypadku). Po prostu podaj identyfikatory / obiekty zone1, zone3 i zone10, na których Ci zależy. Lub dodaj 4. w razie potrzeby.
istruble
Dzięki. Filtrowałem tylko względem pojedynczej wartości zamiast tablicy zawierającej tę pojedynczą wartość.
zypro
39

Zauważ, że jeśli użytkownik może znajdować się w wielu strefach używanych w zapytaniu, prawdopodobnie będziesz chciał dodać .distinct(). W przeciwnym razie jednego użytkownika otrzymujesz wielokrotnie:

users_in_zones = User.objects.filter(zones__in=[zone1, zone2, zone3]).distinct()
QB.
źródło
3

innym sposobem jest przejście przez tabelę pośrednią. Wyraziłbym to w Django ORM w ten sposób:

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

byłoby miło, gdyby nie wymagał .values('user')określonego, ale wydaje się, że Django (wersja 3.0.7) tego potrzebuje.

powyższy kod zakończy generowanie kodu SQL, który wygląda mniej więcej tak:

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

co jest miłe, ponieważ nie ma żadnych pośrednich złączeń, które mogłyby spowodować zwrócenie zduplikowanych użytkowników

Sam Mason
źródło
Cześć. To nie jest odpowiedź sama w sobie. Powinieneś raczej dodać komentarz lub edytować odpowiedź QB niż dodawać dodatkową odpowiedź częściową.
Andy Baker
Tak - jeśli chcesz edytować swoją odpowiedź, aby była kompletna sama w sobie (chyba że masz wystarczająco dużo karmy, aby edytować odpowiedź QB?), To byłby najlepszy wybór. Idealnie w StackOverflow jest „jedna poprawna odpowiedź”. Zwykle nie wychodzi to tak dobrze, ale warto do tego dążyć.
Andy Baker
@AndyBaker zgodził się! z perspektywy czasu odpowiedź QB powinna prawdopodobnie być komentarzem do odpowiedzi istruble, podczas gdy myślę, że moja jest wystarczająco wyraźna, aby uzasadnić oddzielną odpowiedź, ale cóż
Sam Mason