Django ManyToMany filter ()


131

Ho un modello:

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

E ho bisogno di costruire un filtro sulla falsariga di:

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

Deve essere un filtro per l'utente e deve essere un singolo parametro di filtro. La ragione di ciò è che sto costruendo una stringa di query URL per filtrare l'elenco delle modifiche dell'utente amministratore:http://myserver/admin/auth/user/?zones=3

Sembra che dovrebbe essere semplice ma il mio cervello non collabora!


8
Non sono sicuro se ho ragione - non è User.objects.filter(zones__id=<id>)o User.objects.filter(zones__in=<id(s)>)buono per questo?
Tomasz Zieliński,

Va bene :) BTW User.objects.filter(zones__in=<id(s)>)dovrebbe probabilmente essereUser.objects.filter(zones__id__in=<id(s)>)
Tomasz Zieliński

21
Volevo solo far notare a chiunque google questo, che funziona solo se è impostato related_name. zone_set non funzionerebbe, per esempio. Ho sprecato una buona mezz'ora in questo :-)

Risposte:


155

Riaffermando quello che ha detto Tomasz.

Ci sono molti esempi di FOO__in=...filtri di stile nei test molti-a-molti e molti-a-uno . Ecco la sintassi per il tuo problema specifico:

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

La sintassi del doppio trattino basso (__) viene utilizzata ovunque quando si lavora con i queryset .


Grazie @maxm. Aggiornato con un link più attuale ad alcuni esempi.
istruble

9
doppio trattino basso (argh. 3 ore perse per quello)
reabow

Puoi dirmi cosa devo fare se voglio che gli utenti che si trovano in una serie di zone non solo una di esse? Diciamo di trovare l'utente che si trova in zona1, zona3, .. e zona 10
FRR

Guarda gli ...__inesempi dopo # filtering on a few zones, by id. Questi mostrano il filtro per più id / oggetti (in questo caso). Basta inserire gli id ​​/ oggetti zone1, zone3 e zone10 a cui tieni. Oppure aggiungi un quarto se necessario.
istruble

Grazie. Stavo solo filtrando in base a un singolo valore, anziché a un array contenente il singolo valore.
zypro

36

Si noti che se l'utente può trovarsi in più zone utilizzate nella query, è possibile che si desideri aggiungere .distinct (). Altrimenti ottieni un utente più volte:

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

1

un altro modo per farlo è passare attraverso la tabella intermedia. Lo esprimerei nell'ORM di Django in questo modo:

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

sarebbe bello se non fosse necessario il .values('user')specificato, ma Django (versione 3.0.7) sembra averne bisogno.

il codice precedente finirà per generare SQL che assomiglia a qualcosa del tipo:

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

che è bello perché non ha join intermedi che potrebbero causare la restituzione di utenti duplicati


Hiya. Questa non è una risposta in sé. È necessario aggiungere un commento o modificare la risposta di QB anziché aggiungere una risposta parziale aggiuntiva.
Andy Baker,

Sì, se vuoi modificare la tua risposta in modo che sia completa di per sé (a meno che tu non abbia abbastanza karma per modificare la risposta di QB?), Quella sarebbe la scommessa migliore. Idealmente su StackOverflow c'è "una risposta corretta". Di solito non funziona così bene, ma vale la pena puntare.
Andy Baker,

@AndyBaker ha accettato! a posteriori, la risposta di QB dovrebbe probabilmente essere un commento sulla risposta dell'istruble, mentre penso che la mia sia abbastanza distinta da giustificare una risposta separata, ma ah bene
Sam Mason
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.