Filtre Django ManyToMany ()

131

J'ai un modèle:

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

Et j'ai besoin de construire un filtre sur le modèle de:

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

Il doit s'agir d'un filtre sur l'utilisateur et d'un seul paramètre de filtre. La raison en est que je construis une chaîne de requête URL pour filtrer la liste des modifications de l'utilisateur administrateur:http://myserver/admin/auth/user/?zones=3

Il semble que cela devrait être simple mais mon cerveau ne coopère pas!

Andy Baker
la source
8
Je ne sais pas si je vous comprends bien - n'est-ce pas User.objects.filter(zones__id=<id>)ou User.objects.filter(zones__in=<id(s)>)bon pour cela?
Tomasz Zieliński
C'est ok :) BTW User.objects.filter(zones__in=<id(s)>)devrait probablement êtreUser.objects.filter(zones__id__in=<id(s)>)
Tomasz Zieliński
21
Je voulais juste signaler à tous ceux qui recherchent cela sur Google que cela ne fonctionne que si related_name est défini. zone_set ne fonctionnerait pas, par exemple.

Réponses:

155

Je répète simplement ce que Tomasz a dit.

Il existe de nombreux exemples de FOO__in=...filtres de style dans les tests plusieurs-à-plusieurs et plusieurs-à-un . Voici la syntaxe de votre problème spécifique:

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 syntaxe du double trait de soulignement (__) est utilisée partout lorsque vous travaillez avec des ensembles de requêtes .

istruble
la source
Merci @maxm. Mis à jour avec un lien plus récent vers quelques exemples.
istruble le
9
double tiret bas (argh.3 heures perdues contre celui-là)
recalage le
Pouvez-vous s'il vous plaît dire, que faire si je veux que les utilisateurs qui sont dans un ensemble de zones pas n'importe laquelle d'entre elles? Disons trouver un utilisateur qui se trouve dans la zone1, la zone3, .. et la zone 10
FRR
Regardez les ...__inexemples après # filtering on a few zones, by id. Ceux-ci affichent le filtrage pour plusieurs identifiants / objets (dans ce cas). Passez simplement les identifiants / objets zone1, zone3 et zone10 qui vous intéressent. Ou ajoutez un 4e si nécessaire.
istruble
THX. Je ne filtrais que par rapport à une seule valeur, au lieu d'un tableau contenant la valeur unique.
zypro
36

Notez que si l'utilisateur se trouve dans plusieurs zones utilisées dans la requête, vous souhaiterez probablement ajouter .distinct (). Sinon, vous obtenez un utilisateur plusieurs fois:

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

une autre façon de faire est de passer par le tableau intermédiaire. J'exprimerais ceci dans l'ORM Django comme ceci:

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

ce serait bien s'il n'avait pas besoin du .values('user')spécifié, mais Django (version 3.0.7) semble en avoir besoin.

le code ci-dessus finira par générer du SQL qui ressemble à quelque chose comme:

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

ce qui est bien car il n'a pas de jointures intermédiaires qui pourraient entraîner le retour d'utilisateurs en double

Sam Mason
la source
Hiya. Ce n'est pas une réponse en soi. Vous devriez ajouter un commentaire ou modifier la réponse de QB plutôt que d'ajouter une réponse partielle supplémentaire.
Andy Baker
Ouais - si vous voulez modifier votre réponse pour qu'elle soit complète en soi (à moins que vous n'ayez assez de karma pour modifier la réponse de QB?), Alors ce serait le meilleur pari. Idéalement, sur StackOverflow, il y a "une bonne réponse". Cela ne fonctionne généralement pas aussi bien, mais cela vaut la peine de viser.
Andy Baker le
@AndyBaker était d'accord! rétrospectivement, la réponse de QB devrait probablement être un commentaire sur la réponse d'istruble, alors que je pense que la mienne est suffisamment distincte pour justifier une réponse séparée, mais bon
Sam Mason