Django ManyToMany filter ()

131

Saya punya model:

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

Dan saya perlu membuat filter di sepanjang baris:

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

Itu harus menjadi filter pada Pengguna dan itu harus menjadi parameter filter tunggal. Alasannya adalah karena saya membuat querystring URL untuk memfilter daftar pengguna admin:http://myserver/admin/auth/user/?zones=3

Sepertinya itu harus sederhana tetapi otakku tidak mau bekerja sama!

Andy Baker
sumber
8
Saya tidak yakin apakah saya membuat Anda benar - tidak User.objects.filter(zones__id=<id>)atau tidak User.objects.filter(zones__in=<id(s)>)baik untuk ini?
Tomasz Zieliński
Tidak apa-apa :) BTW User.objects.filter(zones__in=<id(s)>)mungkin seharusnyaUser.objects.filter(zones__id__in=<id(s)>)
Tomasz Zieliński
21
Hanya ingin menunjukkan kepada siapa pun di Google tentang ini, bahwa itu hanya berfungsi jika related_name disetel. zone_set tidak akan berfungsi, misalnya. Membuang waktu setengah jam untuk hal itu :-)

Jawaban:

155

Hanya mengulangi apa yang dikatakan Tomasz.

Ada banyak contoh FOO__in=...filter gaya dalam pengujian banyak-ke-banyak dan banyak-ke-satu . Berikut ini sintaks untuk masalah spesifik Anda:

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

Sintaks garis bawah ganda (__) digunakan di semua tempat ketika bekerja dengan kueri .

ramah
sumber
Terima kasih @maxm. Diperbarui dengan tautan terkini ke beberapa contoh.
istri
9
dobel garis bawah (argh. 3 jam hilang dari yang itu)
reabow
Bisakah Anda katakan, apa yang harus dilakukan jika saya ingin pengguna yang berada dalam satu set zona bukan sembarang dari mereka? Katakanlah temukan pengguna yang ada di zone1, zone3, .. dan zone 10
FRR
Lihatlah ...__incontoh setelahnya # filtering on a few zones, by id. Itu menunjukkan penyaringan untuk beberapa id / objek (dalam hal ini). Cukup kirimkan id / objek zone1, zone3, dan zone10 yang Anda pedulikan. Atau tambahkan 4 jika perlu.
istri
Terima kasih. Saya hanya memfilter terhadap nilai tunggal, bukan array yang berisi nilai tunggal.
zypro
36

Perhatikan bahwa jika pengguna mungkin berada di beberapa zona yang digunakan dalam kueri, Anda mungkin ingin menambahkan .distinct (). Kalau tidak, Anda mendapatkan satu pengguna beberapa kali:

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

Cara lain untuk melakukan ini adalah dengan melalui tabel perantara. Saya akan menyatakan ini dalam Django ORM seperti ini:

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

akan lebih baik jika tidak membutuhkan yang .values('user')ditentukan, tetapi Django (versi 3.0.7) tampaknya membutuhkannya.

kode di atas akan menghasilkan SQL yang terlihat seperti:

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

yang bagus karena tidak memiliki gabungan antara yang dapat menyebabkan pengguna duplikat dikembalikan

Sam Mason
sumber
Hai Ini bukan jawaban dalam dirinya sendiri. Anda harus menambahkan komentar atau mengedit jawaban QB daripada menambahkan jawaban parsial tambahan.
Andy Baker
Ya - jika Anda ingin mengedit jawaban Anda jadi sudah lengkap dengan haknya sendiri (kecuali jika Anda punya cukup karma untuk mengedit jawaban QB?) Maka itu akan menjadi taruhan terbaik. Idealnya di StackOverflow ada "satu jawaban yang benar". Biasanya tidak berhasil dengan cukup rapi tetapi layak untuk dibidik.
Andy Baker
@AndyBaker setuju! dalam retrospeksi jawaban QB mungkin harus menjadi komentar pada jawaban istruble, sementara saya pikir jawaban saya cukup berbeda untuk menjamin jawaban yang terpisah, tetapi ah well
Sam Mason