Як зробити АБО фільтр у запиті Джанго?


303

Я хочу мати можливість перелічити елементи, які або додав користувач (вони вказані як творець), або елемент був затверджений.

Тому в основному мені потрібно вибрати:

item.creator = owner or item.moderated = False

Як би я це зробив у Джанго? (бажано з фільтром або набором запитів).

Відповіді:


544

Є Qоб'єкти, які дозволяють здійснювати складні пошуки. Приклад:

from django.db.models import Q

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

6
як це можна було зробити програмно? Так, наприклад, мати змогуfor f in filters: Item.objects.filter(Q(creator=f1) | Q(creator=f2) | ...)
Олексій

14
@AlexisK Використовуйте щось на кшталт reduce(lambda q, f: q | Q(creator=f), filters, Q())створення великого Q-об’єкта.
Phob

24
@alexis: ви також можете зробити Item.objects.filter(creator__in=creators), наприклад.
Кевін Лондон

4
Якщо вам цікаво (як я), звідки |використовується оператор АБО, це насправді встановлений оператор об'єднання. Він також використовується (чи не тут) , як побітовое АБО: stackoverflow.com/questions/5988665/pipe-character-in-python
e100

124

Ви можете використовувати | оператор об'єднувати набори запитів безпосередньо, не потребуючи Q-об'єктів:

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

(редагувати - я спочатку був не впевнений, чи це спричинило додатковий запит, але @spookylukey вказав, що про це дбає ледача оцінка запитів)


4
Щоб дізнатися, які запити виконуються за заданим запитом, ви можете скористатися програмою налагодження Django на панелі інструментів. Він зроблений з приголомшливого і виграшного.
Деніз Доган

25
зробіть "з django.db імпортного з'єднання" та використовуйте "connection.queries". Для цього потрібно DEBUG = True. До речі, ви повинні знати, що QuerySets ледачий, і це потрапляє в БД лише один раз.
spookylukey

1
Чи можна виключити використання при запереченні порівнянь?
Neob91

2
Чи може це призвести до дублікатів у наборі запитів?
Чарльз Харо

1
Більш конкретно, набори запитів мають тенденцію вражати БД лише тоді, коли ви намагаєтесь індексувати їх, інакше ви просто будуєте запит.
awiebe

41

Варто зазначити, що можна додати вирази Q.

Наприклад:

from django.db.models import Q

query = Q(first_name='mark')
query.add(Q(email='mark@test.com'), Q.OR)
query.add(Q(last_name='doe'), Q.AND)

queryset = User.objects.filter(query)

Це закінчується запитом на зразок:

(first_name = 'mark' or email = 'mark@test.com') and last_name = 'doe'

Таким чином , немає необхідності мати справу з або операторами, скоротити'S і т.д.


2
Але простіше писати query |= Q(email='mark@test.com')?
Alex78191

26

Ви хочете зробити фільтр динамічним, тоді вам доведеться використовувати Lambda, як

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]) еквівалентно

Q(brand=brands[0]) | Q(brand=brands[1]) | Q(brand=brands[2]) | .....

6
Ідеальна відповідь для мене! Для python3 зробіть це from functools import reduceзаздалегідь.
Дхарміт

1
Чому б не використовувати operator.or_замість lambda x, y: x | y?
Alex78191

20

Подібний до старшої ансвери, але трохи простіший, без лямбда:

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

Щоб фільтрувати ці два умови, використовуючи OR:

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

Щоб отримати однаковий результат програмно:

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

(тут для чіткості розбито на два рядки)

operatorзнаходиться в стандартній бібліотеці: import operator
З docstring:

or_ (a, b) - Те саме, що | б.

Для Python3 reduceвже не вбудований, але все ще знаходиться у стандартній бібліотеці:from functools import reduce


PS

Не забудьте переконатися, що list_of_Qвін не порожній - він reduce()буде задихатися у порожньому списку, йому потрібен хоча б один елемент.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.