Como eu faço um filtro OR em uma consulta Django?

303

Quero poder listar os itens que um usuário adicionou (eles são listados como criador) ou o item foi aprovado.

Então, eu basicamente preciso selecionar:

item.creator = owner or item.moderated = False

Como eu faria isso no Django? (de preferência com um filtro ou conjunto de consultas).

Mez
fonte

Respostas:

545

Existem Qobjetos que permitem pesquisas complexas. Exemplo:

from django.db.models import Q

Item.objects.filter(Q(creator=owner) | Q(moderated=False))
Alex Koshelev
fonte
6
como isso pode ser feito programaticamente? Então, por exemplo, seja capaz de ter #for f in filters: Item.objects.filter(Q(creator=f1) | Q(creator=f2) | ...)
Alexis
14
@AlexisK Use algo parecido reduce(lambda q, f: q | Q(creator=f), filters, Q())para criar o grande objeto Q.
Phob
24
@exex: você também pode fazer Item.objects.filter(creator__in=creators), por exemplo.
Kevin London
4
Se você está se perguntando (como eu) de onde |vem sendo usado como operador OR, na verdade é o operador de união definida. Também é usado (não aqui) como bit a bit OU: stackoverflow.com/questions/5988665/pipe-character-in-python
e100
124

Você pode usar o | operador para combinar conjuntos de consultas diretamente sem a necessidade de objetos Q:

result = Item.objects.filter(item.creator = owner) | Item.objects.filter(item.moderated = False)

(editar - eu inicialmente não tinha certeza se isso causava uma consulta extra, mas @spookylukey apontou que a avaliação lenta do conjunto de consultas cuida disso)

Andy Baker
fonte
4
Para descobrir quais consultas são executadas em uma determinada solicitação, você pode usar o aplicativo Django da barra de ferramentas de depuração. É incrível e ganha.
Deniz Dogan
25
faça 'from django.db import connection' e use 'connection.queries'. Isso requer DEBUG = True. BTW, você deve saber que QuerySets são preguiçosos e isso atinge o banco de dados apenas uma vez.
Spookylukey
1
A exclusão poderia ser usada com comparações negadas?
Neob91
2
isso pode resultar em duplicatas no conjunto de consultas do resultado?
Charles Haro
1
Mais especificamente, os conjuntos de consultas tendem a atingir o banco de dados apenas quando você tenta indexá-los; caso contrário, você está apenas criando uma consulta.
21818 awiebe
41

Vale ressaltar que é possível adicionar expressões Q.

Por exemplo:

from django.db.models import Q

query = Q(first_name='mark')
query.add(Q(email='[email protected]'), Q.OR)
query.add(Q(last_name='doe'), Q.AND)

queryset = User.objects.filter(query)

Isso acaba com uma consulta como:

(first_name = 'mark' or email = '[email protected]') and last_name = 'doe'

Desta forma, não há necessidade de lidar com ou operadores, reduzir etc.

marxin
fonte
2
Mas é mais fácil escrever query |= Q(email='[email protected]')?
Alex78191
26

Você quer dinamizar o filtro, então precisa usar o Lambda como

from django.db.models import Q

brands = ['ABC','DEF' , 'GHI']

queryset = Product.objects.filter(reduce(lambda x, y: x | y, [Q(brand=item) for item in brands]))

reduce(lambda x, y: x | y, [Q(brand=item) for item in brands]) é equivalente a

Q(brand=brands[0]) | Q(brand=brands[1]) | Q(brand=brands[2]) | .....
Abhishek Chauhan
fonte
6
Resposta perfeita para mim! Para python3, faça from functools import reduceantes.
Dharmit
1
Por que não usar em operator.or_vez de lambda x, y: x | y?
Alex78191
20

Semelhante à resposta anterior, mas um pouco mais simples, sem o lambda:

filter_kwargs = {
    'field_a': 123,
    'field_b__in': (3, 4, 5, ),
}

Para filtrar essas duas condições usando OR:

Item.objects.filter(Q(field_a=123) | Q(field_b__in=(3, 4, 5, ))

Para obter o mesmo resultado programaticamente:

list_of_Q = [Q(**{key: val}) for key, val in filter_kwargs.items()]
Item.objects.filter(reduce(operator.or_, list_of_Q))

(dividido em duas linhas aqui, para maior clareza)

operatorestá na biblioteca padrão: import operator
De docstring:

or_ (a, b) - o mesmo que a | b.

Para Python3, reducenão é mais um built-in, mas ainda está na biblioteca padrão:from functools import reduce


PS

Não se esqueça de certificar- list_of_Qse de que não está vazio - reduce()irá sufocar na lista vazia, ele precisa de pelo menos um elemento.

frnhr
fonte