Як динамічно скласти фільтр запитів АБО у 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])в сучасних джанго, але питання все-таки актуальне, якщо ви хочете зробити щось трохи більш досконале, об'єктуючи об'єкти Q разом.
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}: queries = [Q (** {'namename field:' value}) для значень у значеннях]
досягайте

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
Для новачків цієї теми, як я, я вважаю, що цю відповідь слід розглядати як верхню відповідь. Це більше Джангоеска, ніж прийнята відповідь. Дякую!
Терезаанна

5
Я б заперечував, що більш пітонічно використовувати вбудовані оператори OR і AND (| і &). q_objects |= Q(pk=item)
Бобборт

Ідеально! Дякую!
Р.Л.Шям

1
Варто зазначити, що якщо listтрапиться порожнє, ви повернете еквівалент Article.objects.all(). Легко пом'якшити, повернувшись Article.objects.none()до цього тесту.
Віл

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, виправлено.
Том Вінер

І можна використовувати 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

о, я подумав, що це якийсь оператор Django ORM. Як глупо мені, дякую!
mpiskore

20

Можливо, краще використовувати оператор sql IN.

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

Дивіться посилання на запит набору .

Якщо вам справді потрібно робити запити з динамічною логікою, ви можете зробити щось подібне (некрасиво + не перевірено):

query = Q(field=1)
for cond in (2, 3):
    query = query | Q(field=cond)
Article.objects.filter(query)

1
Ви також можете скористатисяquery |= Q(field=cond)
Bobort

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

У випадку, якщо ми хочемо програмно встановити те, яке поле, яке ми хочемо запитувати:

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

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

Як і в нашій галузі, це документально підтверджено на 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також перекриває &, |, ~і т.д., оператори. Інші відповіді, що АБО об'єкти 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

легко ..
з імпорту django.db.models Q імпортуєш ти аргументи = (Q (видимість = 1) | (Q (видимість = 0) і Q (користувач = self.user))) # параметри параметри = {} #dic замовлення = 'create_at' межа = 10

Models.objects.filter(*args,**parameters).order_by(order)[:limit]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.