Bagaimana saya melakukan filter ATAU dalam permintaan Django?

303

Saya ingin dapat membuat daftar item yang ditambahkan oleh pengguna (terdaftar sebagai pencipta) atau item telah disetujui.

Jadi pada dasarnya saya perlu memilih:

item.creator = owner or item.moderated = False

Bagaimana saya melakukan ini di Django? (lebih disukai dengan filter atau queryset).

Mez
sumber

Jawaban:

545

Ada Qobjek yang memungkinkan pencarian yang rumit. Contoh:

from django.db.models import Q

Item.objects.filter(Q(creator=owner) | Q(moderated=False))
Alex Koshelev
sumber
6
bagaimana ini bisa dilakukan secara terprogram? Jadi, misalnya dapat memilikifor f in filters: Item.objects.filter(Q(creator=f1) | Q(creator=f2) | ...)
Alexis
14
@AlexisK Gunakan sesuatu seperti reduce(lambda q, f: q | Q(creator=f), filters, Q())untuk membuat objek Q besar.
Phob
24
@alexis: Anda juga bisa melakukannya Item.objects.filter(creator__in=creators), misalnya.
Kevin London
4
Jika Anda bertanya-tanya (seperti saya) di mana |digunakan sebagai operator OR berasal, itu sebenarnya adalah operator serikat pekerja. Ini juga digunakan (bukan di sini) sebagai bitwise ATAU: stackoverflow.com/questions/5988665/pipe-character-in-python
e100
124

Anda dapat menggunakan | operator untuk menggabungkan queryset secara langsung tanpa memerlukan objek Q:

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

(sunting - Saya awalnya tidak yakin apakah ini menyebabkan permintaan tambahan tetapi @spookylukey menunjukkan bahwa evaluasi queryset yang malas menangani hal itu)

Andy Baker
sumber
4
Untuk mengetahui kueri mana yang dieksekusi berdasarkan permintaan yang diberikan, Anda dapat menggunakan aplikasi Django debug-toolbar. Itu dibuat luar biasa dan menang.
Deniz Dogan
25
lakukan 'dari koneksi impor django.db' dan gunakan 'connection.queries'. Ini membutuhkan DEBUG = Benar. BTW, Anda harus tahu bahwa QuerySets malas dan ini hanya mengenai DB.
spookylukey
1
Bisakah pengecualian digunakan dengan perbandingan yang dinegasikan?
Neob91
2
dapatkah ini menghasilkan duplikat dalam kueryset hasil?
Charles Haro
1
Lebih khusus kumpulan permintaan cenderung untuk memukul DB hanya ketika Anda mencoba untuk mengindeks ke dalamnya, jika tidak, Anda hanya membangun permintaan.
awiebe
41

Perlu dicatat bahwa mungkin untuk menambahkan ekspresi Q.

Sebagai contoh:

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)

Ini berakhir dengan permintaan seperti:

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

Dengan cara ini tidak perlu berurusan dengan atau operator, mengurangi dll.

marxin
sumber
2
Tetapi lebih mudah menulis query |= Q(email='[email protected]')?
Alex78191
26

Anda ingin membuat filter dinamis maka Anda harus menggunakan seperti 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]) setara dengan

Q(brand=brands[0]) | Q(brand=brands[1]) | Q(brand=brands[2]) | .....
Abhishek Chauhan
sumber
6
Jawaban sempurna untuk saya! Untuk python3, lakukan from functools import reducesebelumnya.
Dharmit
1
Mengapa tidak menggunakan operator.or_bukan lambda x, y: x | y?
Alex78191
20

Mirip dengan answerera yang lebih tua, tetapi sedikit lebih sederhana, tanpa lambda:

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

Untuk memfilter kedua kondisi ini menggunakan OR:

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

Untuk mendapatkan hasil yang sama secara terprogram:

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

(Dipecah dalam dua baris di sini, untuk kejelasan)

operatorada di perpustakaan standar: import operator
Dari docstring:

or_ (a, b) - Sama dengan | b.

Untuk Python3, reducebukan builtin lagi tetapi masih di perpustakaan standar:from functools import reduce


PS

Jangan lupa pastikan list_of_Qtidak kosong - reduce()akan tersedak daftar kosong, perlu setidaknya satu elemen.

frnhr
sumber