Filtro Django ManyToMany ()

131

Eu tenho um modelo:

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

E preciso criar um filtro ao longo das linhas de:

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

Ele deve ser um filtro no usuário e um único parâmetro de filtro. A razão para isso é que estou construindo uma string de consulta de URL para filtrar a lista de alterações do usuário administrador:http://myserver/admin/auth/user/?zones=3

Parece que deveria ser simples, mas meu cérebro não está cooperando!

Andy Baker
fonte
8
Eu não tenho certeza se eu te direita - não é User.objects.filter(zones__id=<id>)ou User.objects.filter(zones__in=<id(s)>)bom para isso?
Tomasz Zieliński
Está tudo bem :) BTW User.objects.filter(zones__in=<id(s)>)provavelmente deveria serUser.objects.filter(zones__id__in=<id(s)>)
Tomasz Zieliński
21
Só queria apontar para alguém pesquisando isso no Google, que só funciona se o related_name estiver definido. zone_set não funcionaria, por exemplo. Desperdiçado uma boa meia hora em que :-)

Respostas:

155

Apenas reafirmando o que Tomasz disse.

Existem muitos exemplos de FOO__in=...filtros de estilo nos testes muitos para muitos e muitos para um . Aqui está a sintaxe para seu 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])

A sintaxe de sublinhado duplo (__) é usada em todo o lugar ao trabalhar com conjuntos de consultas .

istruble
fonte
Obrigado @maxm. Atualizado com um link mais atual para alguns exemplos.
Istruble
9
sublinhado duplo (perda de 3 horas para aquele)
reabow 14/01
Você pode dizer o que fazer se eu quiser que os usuários que estão em um conjunto de zonas não sejam apenas um deles? Deixa o usuário digamos achado que estão em zone1, Zone3, .. e zona 10
FRR
Veja os ...__inexemplos depois # filtering on a few zones, by id. Aqueles mostram a filtragem de vários IDs / objetos (neste caso). Basta passar os IDs / objetos zone1, zone3 e zone10 com os quais você se preocupa. Ou adicione um quarto, se necessário.
istruble
THX. Eu estava apenas filtrando contra um único valor, em vez de uma matriz contendo o valor único.
zypro
36

Observe que, se o usuário estiver em várias zonas usadas na consulta, provavelmente você deseja adicionar .distinct (). Caso contrário, você obterá um usuário várias vezes:

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

Outra maneira de fazer isso é percorrer a tabela intermediária. Eu expressaria isso dentro do Django ORM assim:

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

seria bom se não precisasse do .values('user')especificado, mas o Django (versão 3.0.7) parece precisar.

o código acima acabará gerando SQL parecido com:

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

o que é legal porque não possui junções intermediárias que podem causar o retorno de usuários duplicados

Sam Mason
fonte
Hiya. Esta não é uma resposta em si. Você deve adicionar um comentário ou editar a resposta do QB em vez de adicionar uma resposta parcial adicional.
Andy Baker
Sim - se você deseja editar sua resposta para que ela seja completa por si só (a menos que você tenha karma suficiente para editar a resposta de QB?), Essa seria a melhor aposta. Idealmente, no StackOverflow, há "uma resposta correta". Geralmente não funciona tão bem, mas vale a pena procurar.
Andy Baker
@AndyBaker concordou! em retrospecto, a resposta de QB deve provavelmente ser um comentário sobre a resposta de istruble, enquanto eu acho que a minha é distinta o suficiente para justificar uma resposta separada, mas ah bem
Sam Mason