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!


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 .


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

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


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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.