ตัวกรอง Django ManyToMany ()


131

ฉันมีโมเดล:

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

และฉันต้องสร้างตัวกรองตามแนวของ:

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

ต้องเป็นตัวกรองผู้ใช้และต้องเป็นพารามิเตอร์ตัวกรองเดียว เหตุผลนี้คือฉันกำลังสร้างสตริงการสืบค้น URL เพื่อกรองรายการเปลี่ยนผู้ใช้ของผู้ดูแลระบบ:http://myserver/admin/auth/user/?zones=3

ดูเหมือนว่ามันจะเรียบง่าย แต่สมองของฉันไม่ให้ความร่วมมือ!


8
ฉันไม่แน่ใจว่าฉันทำให้คุณถูกต้องUser.objects.filter(zones__id=<id>)หรือไม่User.objects.filter(zones__in=<id(s)>)ดีสำหรับสิ่งนี้?
Tomasz Zieliński

ไม่เป็นไร :) BTW User.objects.filter(zones__in=<id(s)>)น่าจะเป็นUser.objects.filter(zones__id__in=<id(s)>)
Tomasz Zieliński

22
แค่อยากจะชี้ให้ทุกคนที่ใช้ Googling สิ่งนี้ว่าจะใช้ได้ก็ต่อเมื่อมีการตั้งค่า related_name zone_set ใช้ไม่ได้เช่น เสียเวลาไปครึ่งชั่วโมงดี :-)

คำตอบ:


156

เพียงแค่ทบทวนสิ่งที่ Tomasz พูด

มีหลายตัวอย่างของการมีFOO__in=...ฟิลเตอร์สไตล์ในหลายต่อหลายคนและหลายต่อหนึ่งการทดสอบ นี่คือไวยากรณ์สำหรับปัญหาเฉพาะของคุณ:

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

ขีดคู่ (__) ไวยากรณ์ถูกนำมาใช้ทั่วทุกสถานที่เมื่อทำงานกับชุดข้อความ


ขอบคุณ @maxm. อัปเดตด้วยลิงก์ที่เป็นปัจจุบันมากขึ้นไปยังตัวอย่างบางส่วน
istruble

9
ขีดล่างคู่ (ประมาณ 3 ชั่วโมงที่เสียไป)
reabow

ช่วยบอกหน่อยได้ไหมว่าฉันจะทำอย่างไรถ้าฉันต้องการให้ผู้ใช้ที่อยู่ในโซนต่างๆไม่ใช่แค่คนใดคนหนึ่ง ให้บอกว่าค้นหาผู้ใช้ที่อยู่ในโซน 1, โซน
FRR

ดูที่ตัวอย่างหลังจากที่...__in # filtering on a few zones, by idแสดงการกรองรหัส / วัตถุหลายรายการ (ในกรณีนี้) เพียงแค่ส่งรหัส / วัตถุ zone1, zone3 และ zone10 ที่คุณสนใจ หรือเพิ่มตัวที่ 4 หากจำเป็น
istruble

ขอบคุณ. ฉันกรองเฉพาะค่าเดียวแทนที่จะเป็นอาร์เรย์ที่มีค่าเดียว
zypro

36

โปรดทราบว่าหากผู้ใช้อาจอยู่ในหลายโซนที่ใช้ในแบบสอบถามคุณอาจต้องการเพิ่ม. distinct () มิฉะนั้นคุณจะได้รับผู้ใช้หนึ่งคนหลายครั้ง:

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

1

อีกวิธีในการทำเช่นนี้คือการผ่านตารางกลาง ฉันจะแสดงสิ่งนี้ภายใน Django ORM ดังนี้:

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

คงจะดีไม่น้อยหากไม่ต้องการสิ่งที่.values('user')ระบุไว้ แต่ Django (เวอร์ชัน 3.0.7) ดูเหมือนจะต้องการ

โค้ดด้านบนจะสิ้นสุดการสร้าง SQL ที่มีลักษณะดังนี้:

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

ซึ่งดีเพราะไม่มีการรวมระดับกลางที่อาจทำให้ผู้ใช้ซ้ำถูกส่งคืน


Hiya นี่ไม่ใช่คำตอบในตัวเอง คุณควรเพิ่มความคิดเห็นหรือแก้ไขคำตอบของ QB แทนที่จะเพิ่มคำตอบเพิ่มเติมบางส่วน
Andy Baker

ใช่ - ถ้าคุณต้องการแก้ไขคำตอบของคุณเพื่อให้มันสมบูรณ์ตามสิทธิ์ของตัวเอง (เว้นแต่คุณจะมีกรรมเพียงพอที่จะแก้ไขคำตอบของ QB?) นั่นจะเป็นทางออกที่ดีที่สุด ตามหลักการแล้วใน StackOverflow จะมี "คำตอบที่ถูกต้อง" มันมักจะไม่ได้ออกมาอย่างเรียบร้อย แต่ก็คุ้มค่าที่จะตั้งเป้าไว้
Andy Baker

@AndyBaker เห็นด้วย! ในการย้อนกลับคำตอบของ QB น่าจะเป็นความคิดเห็นเกี่ยวกับคำตอบของ istruble ในขณะที่ฉันคิดว่าของฉันมีความแตกต่างพอที่จะรับประกันคำตอบแยกต่างหาก แต่ก็ดี
Sam Mason
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.