Digamos que tenho os seguintes modelos
class Photo(models.Model):
tags = models.ManyToManyField(Tag)
class Tag(models.Model):
name = models.CharField(max_length=50)
Em uma exibição, tenho uma lista com filtros ativos chamados categorias . Quero filtrar objetos de fotos que têm todas as marcas presentes nas categorias .
Eu tentei:
Photo.objects.filter(tags__name__in=categories)
Mas isso corresponde a qualquer item nas categorias, não a todos os itens.
Portanto, se as categorias forem ['feriado', 'verão'], quero fotos com a marca de feriado e verão.
Isso pode ser alcançado?
python
django
filter
django-queryset
Sander van Leeuwen
fonte
fonte
Photo.objects.filter(tags__name='holiday').filter(tags__name='summer')
é o caminho a percorrer. (Este é o mesmo exemplo de Jpic). Cada umfilter
deve adicionar maisJOIN
s à consulta, então você pode adotar uma abordagem de anotação se eles forem muitos.Respostas:
Resumo:
Uma opção é, como sugerido por jpic e sgallen nos comentários, adicionar
.filter()
para cada categoria. Cada adicionalfilter
adiciona mais junções, o que não deve ser um problema para um pequeno conjunto de categorias.Existe a abordagem de agregação . Essa consulta seria mais curta e talvez mais rápida para um grande conjunto de categorias.
Você também tem a opção de usar consultas personalizadas .
Alguns exemplos
Configuração de teste:
Usando a abordagem de filtros em cadeia :
Consulta resultante:
Observe que cada um
filter
adiciona maisJOINS
à consulta.Usando abordagem de anotação :
Consulta resultante:
AND
Q
objetos ed não funcionariam:Consulta resultante:
fonte
t3
, e uma foto tiver as tagst2
et3
. Então, essa foto ainda corresponderá à consulta fornecida.Photo.objects.filter(tags__in=tags)
corresponde a fotos que têm qualquer uma das marcas, não apenas aquelas que têm todas. Alguns daqueles que possuem apenas uma das tags desejadas, podem ter exatamente a quantidade de tags que você está procurando, e alguns daqueles que possuem todas as tags desejadas, também podem ter tags adicionais.Outra abordagem que funciona, embora apenas PostgreSQL, é usar
django.contrib.postgres.fields.ArrayField
:Exemplo copiado de documentos :
ArrayField
tem alguns recursos mais poderosos, como sobreposição e transformações de índice .fonte
Isso também pode ser feito por geração de consulta dinâmica usando Django ORM e alguma mágica do Python :)
A ideia é gerar objetos Q apropriados para cada categoria e então combiná-los usando o operador AND em um QuerySet. Por exemplo, para o seu exemplo, seria igual a
fonte
filter
seria o mesmo que usarand
objetos Q em um filtro ... Erro meu.filter
paraexclude
e usar um operador de negação. Assim:res = Photo.exclude(~reduce(and_, [Q(tags__name=c) for c in categories]))
Eu uso uma pequena função que itera filtros em uma lista para um determinado operador e um nome de coluna:
e esta função pode ser chamada assim:
também funciona com qualquer classe e mais tags na lista; operadores podem ser qualquer um como 'iexact', 'in', 'contains', 'ne', ...
fonte
fonte
Se quisermos fazer isso de forma dinâmica, siga o exemplo:
fonte