Как динамически составить фильтр запроса OR в Django?

104

В примере вы можете увидеть фильтр запросов с множественным ИЛИ:

Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))

Например, это приводит к:

[<Article: Hello>, <Article: Goodbye>, <Article: Hello and goodbye>]

Однако я хочу создать этот фильтр запроса из списка. Как это сделать?

например [1, 2, 3] -> Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))

Джек Ха
источник
1
Похоже, вы спрашивали об этом дважды: stackoverflow.com/questions/852404
Доминик Роджер,
Для этого конкретного варианта использования вы, вероятно, использовали бы Article.objects.filter(pk__in=[1, 2, 3])в современном django, но вопрос все еще актуален, если вы хотите сделать что-то более продвинутое, объединяя объекты Q вместе с помощью OR.
beruic

Ответы:

162

Вы можете связать свои запросы следующим образом:

values = [1,2,3]

# Turn list of values into list of Q objects
queries = [Q(pk=value) for value in values]

# Take one Q object from the list
query = queries.pop()

# Or the Q object with the ones remaining in the list
for item in queries:
    query |= item

# Query the model
Article.objects.filter(query)
Дэйв Уэбб
источник
3
Спасибо! Это было то, что я искал :) Не знал, что ты умеешь | =
Джек Ха
23
Вы также можете инициализировать запрос, используя: query = Q ()
chachan
5
вы можете создавать динамические поля, используя ** {'fieldname': value}: query = [Q (** {'fieldname': value}) для значения в значениях]
rechie
1
Как вы можете составлять необработанные запросы с помощью Django, если вы хотите добавить дополнительные условия, как указано выше?
пользователь
У меня это не сработало, я не знаю почему. запросы не возвращают для меня результатов
Мехран Нури
83

Для построения более сложных запросов также есть возможность использовать встроенные в объект Q () константы Q.OR и Q.AND вместе с методом add () следующим образом:

list = [1, 2, 3]
# it gets a bit more complicated if we want to dynamically build
# OR queries with dynamic/unknown db field keys, let's say with a list
# of db fields that can change like the following
# list_with_strings = ['dbfield1', 'dbfield2', 'dbfield3']

# init our q objects variable to use .add() on it
q_objects = Q(id__in=[])

# loop trough the list and create an OR condition for each item
for item in list:
    q_objects.add(Q(pk=item), Q.OR)
    # for our list_with_strings we can do the following
    # q_objects.add(Q(**{item: 1}), Q.OR)

queryset = Article.objects.filter(q_objects)

# sometimes the following is helpful for debugging (returns the SQL statement)
# print queryset.query
вне
источник
12
Для новичков в этой теме, таких как я, я думаю, что этот ответ следует рассматривать как лучший ответ. Это больше Djangoesque, чем принятый ответ. Спасибо!
theresaanna
5
Я бы поспорил, что использование встроенных операторов OR и AND (| и &) более питонично. q_objects |= Q(pk=item)
Bobort
Отлично! Спасибо!
RL Shyam
1
Стоит отметить, что если listокажется пустым, вы вернете эквивалент Article.objects.all(). Однако легко смягчить ситуацию, вернувшись Article.objects.none()для этого теста.
Wil
2
@Wil вы можете также инициализировать q_objectsс Q(id__in=[]). Он всегда будет терпеть неудачу, если не будет выполнено ИЛИ с чем-либо, и оптимизатор запросов с этим справится.
Джонатан Ричардс,
44

Более короткий способ написать ответ Дэйва Уэбба с использованием функции сокращения Python :

# For Python 3 only
from functools import reduce

values = [1,2,3]

# Turn list of values into one big Q objects  
query = reduce(lambda q,value: q|Q(pk=value), values, Q())  

# Query the model  
Article.objects.filter(query)  
Том Винер
источник
Похоже, "встроенное" сокращение было удалено и заменено на functools.reduce. источник
lsowen
Спасибо @lsowen, исправлено.
Tom
И можно использовать operator.or_вместо лямбды.
eigenein
38
from functools import reduce
from operator import or_
from django.db.models import Q

values = [1, 2, 3]
query = reduce(or_, (Q(pk=x) for x in values))
Игнасио Васкес-Абрамс
источник
Хорошо, но откуда взялось operator?
mpiskore
1
@mpiskore: То же место, что и любой другой модуль Python: вы его импортируете.
Игнасио Васкес-Абрамс
1
веселая. это действительно был мой вопрос: в каком модуле / библиотеке я могу его найти? Google не очень помог.
mpiskore
о, я думал, что это какой-то оператор ORM Django. Как глупо с моей стороны, спасибо!
mpiskore
20

Возможно, лучше использовать оператор sql IN.

Article.objects.filter(id__in=[1, 2, 3])

См. Справку по API queryset .

Если вам действительно нужно делать запросы с динамической логикой, вы можете сделать что-то вроде этого (некрасиво + не проверено):

query = Q(field=1)
for cond in (2, 3):
    query = query | Q(field=cond)
Article.objects.filter(query)
Алекс Васи
источник
1
Вы также можете использоватьquery |= Q(field=cond)
Боборт
8

См. Документы :

>>> Blog.objects.in_bulk([1])
{1: <Blog: Beatles Blog>}
>>> Blog.objects.in_bulk([1, 2])
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>}
>>> Blog.objects.in_bulk([])
{}

Обратите внимание, что этот метод работает только для поиска по первичному ключу, но, похоже, это именно то, что вы пытаетесь сделать.

Итак, что вы хотите:

Article.objects.in_bulk([1, 2, 3])
Доминик Роджер
источник
6

В случае, если мы хотим программно установить, какое поле db мы хотим запрашивать:

import operator
questions = [('question__contains', 'test'), ('question__gt', 23 )]
q_list = [Q(x) for x in questions]
Poll.objects.filter(reduce(operator.or_, q_list))
Zzart
источник
6

Решение , которое используют reduceи or_операторы для фильтра, многократно полей.

from functools import reduce
from operator import or_
from django.db.models import Q

filters = {'field1': [1, 2], 'field2': ['value', 'other_value']}

qs = Article.objects.filter(
   reduce(or_, (Q(**{f'{k}__in': v}) for k, v in filters.items()))
)

ps f- это новый строковый литерал формата. Он был представлен в Python 3.6.

Иван Семочкин
источник
4

Вы можете использовать оператор | = для программного обновления запроса с помощью объектов Q.

Джефф Обер
источник
2
Это где-нибудь задокументировано? Я искал последние 15 минут, и это единственное, что я смог найти.
wobbily_col 09
Как и многое в нашей индустрии, это задокументировано на StackOverflow!
Крис
2

Это для динамического списка пакетов:

pk_list = qs.values_list('pk', flat=True)  # i.e [] or [1, 2, 3]

if len(pk_list) == 0:
    Article.objects.none()

else:
    q = None
    for pk in pk_list:
        if q is None:
            q = Q(pk=pk)
        else:
            q = q | Q(pk=pk)

    Article.objects.filter(q)
Велоди
источник
Вы можете использовать q = Q()вместо q = None, а затем удалить это if q is Noneпредложение - немного менее эффективно, но можно удалить три строки кода. (Пустой Q впоследствии сливается при выполнении запроса.)
Крис,
1

Другой вариант я не знал до недавнего времени - QuerySetтакже отменяет &, |,~ и т.д., операторы. Другие ответы о том, что объекты OR Q являются лучшим решением этого вопроса, но для интереса / аргумента вы можете сделать:

id_list = [1, 2, 3]
q = Article.objects.filter(pk=id_list[0])
for i in id_list[1:]:
    q |= Article.objects.filter(pk=i)

str(q.query)вернет один запрос со всеми фильтрами в WHEREпредложении.

Крис
источник
1

Для цикла:

values = [1, 2, 3]
q = Q(pk__in=[]) # generic "always false" value
for val in values:
    q |= Q(pk=val)
Article.objects.filter(q)

Уменьшить:

from functools import reduce
from operator import or_

values = [1, 2, 3]
q_objects = [Q(pk=val) for val in values]
q = reduce(or_, q_objects, Q(pk__in=[]))
Article.objects.filter(q)

Оба они эквивалентны Article.objects.filter(pk__in=values)

Важно учитывать, что вы хотите, когда valuesпусто. Многие ответы с Q()начальным значением вернут все . Q(pk__in=[])- лучшее начальное значение. Это всегда сбойный объект Q, который хорошо обрабатывается оптимизатором (даже для сложных уравнений).

Article.objects.filter(Q(pk__in=[]))  # doesn't hit DB
Article.objects.filter(Q(pk=None))    # hits DB and returns nothing
Article.objects.none()                # doesn't hit DB
Article.objects.filter(Q())           # returns everything

Если вы хотите вернуть все, когда valuesоно пусто, вы должны И с помощью, ~Q(pk__in=[])чтобы гарантировать, что поведение:

values = []
q = Q()
for val in values:
    q |= Q(pk=val)
Article.objects.filter(q)                     # everything
Article.objects.filter(q | author="Tolkien")  # only Tolkien

q &= ~Q(pk__in=[])
Article.objects.filter(q)                     # everything
Article.objects.filter(q | author="Tolkien")  # everything

Важно помнить, что Q()это ничто , не всегда следующий Q-объект. Любая операция, связанная с этим, просто отбросит его полностью.

Джонатан Ричардс
источник
0

easy ..
from django.db.models import Q import you model args = (Q (visibility = 1) | (Q (visibility = 0) & Q (user = self.user))) #Tuple parameters = {} #dic order = лимит 'create_at' = 10

Models.objects.filter(*args,**parameters).order_by(order)[:limit]
альфонсолаваррия
источник