Filtro Django ManyToMany ()

131

Tengo un modelo:

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

Y necesito construir un filtro a lo largo de las líneas de:

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

Tiene que ser un filtro en Usuario y debe ser un único parámetro de filtro. La razón de esto es que estoy construyendo una cadena de consulta de URL para filtrar la lista de cambios del usuario administrador:http://myserver/admin/auth/user/?zones=3

¡Parece que debería ser simple, pero mi cerebro no está cooperando!

Andy Baker
fuente
8
No estoy seguro de si consigo que la derecha - no es User.objects.filter(zones__id=<id>)ni User.objects.filter(zones__in=<id(s)>)bueno para esto?
Tomasz Zieliński
Eso está bien :) BTW User.objects.filter(zones__in=<id(s)>)probablemente debería serUser.objects.filter(zones__id__in=<id(s)>)
Tomasz Zieliński
21
Solo quería señalar a cualquiera que busque en Google esto, que solo funciona si se establece related_name. zone_set no funcionaría, por ejemplo. Perdí una buena media hora en eso :-)

Respuestas:

155

Simplemente repitiendo lo que dijo Tomasz.

Hay muchos ejemplos de FOO__in=...filtros de estilo en las pruebas de muchos a muchos y de muchos a uno . Aquí hay una sintaxis para su problema específico:

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 sintaxis de subrayado doble (__) se usa por todas partes cuando se trabaja con conjuntos de consultas .

istruble
fuente
Gracias @maxm. Actualizado con un enlace más actual a algunos ejemplos.
Istruble
9
doble guión bajo (argh. 3 horas perdidas con ese)
reabow
¿Puede decir qué hacer si quiero que los usuarios que se encuentran en un conjunto de zonas no sean cualquiera de ellos? Digamos encontrar usuarios que están en zone1, zone3, .. y zone 10
FRR
Mira los ...__inejemplos después # filtering on a few zones, by id. Esos muestran el filtrado para múltiples identificadores / objetos (en este caso). Simplemente pase los identificadores / objetos zone1, zone3 y zone10 que le interesan. O agregue un cuarto si es necesario.
Istruble el
Gracias. Solo estaba filtrando contra un valor único, en lugar de una matriz que contenía el valor único.
zypro
36

Tenga en cuenta que si el usuario puede estar en varias zonas utilizadas en la consulta, es probable que desee agregar .distinct (). De lo contrario, obtendrá un usuario varias veces:

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

Otra forma de hacerlo es a través de la tabla intermedia. Expresaría esto dentro del Django ORM así:

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

sería bueno si no necesitara lo .values('user')especificado, pero Django (versión 3.0.7) parece necesitarlo.

el código anterior terminará generando SQL que se parece a:

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

lo cual es bueno porque no tiene uniones intermedias que puedan hacer que se devuelvan usuarios duplicados

Sam Mason
fuente
Hola Esta no es una respuesta en sí misma. Debe agregar un comentario o editar la respuesta de QB en lugar de agregar una respuesta parcial adicional.
Andy Baker
Sí, si desea editar su respuesta para que esté completa por derecho propio (a menos que tenga suficiente karma para editar la respuesta de QB), entonces esa sería la mejor opción. Idealmente en StackOverflow hay "una respuesta correcta". Por lo general, no funciona tan bien, pero vale la pena apuntar.
Andy Baker
@AndyBaker estuvo de acuerdo! en retrospectiva, la respuesta de QB probablemente debería ser un comentario sobre la respuesta de istruble, mientras que creo que la mía es lo suficientemente distinta como para justificar una respuesta por separado, pero bueno
Sam Mason