Di Django, bagaimana cara memfilter QuerySet dengan pencarian bidang dinamis?

161

Diberi kelas:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=20)

Apakah mungkin, dan jika demikian caranya, memiliki QuerySet yang memfilter berdasarkan argumen dinamis? Sebagai contoh:

 # Instead of:
 Person.objects.filter(name__startswith='B')
 # ... and:
 Person.objects.filter(name__endswith='B')

 # ... is there some way, given:
 filter_by = '{0}__{1}'.format('name', 'startswith')
 filter_value = 'B'

 # ... that you can run the equivalent of this?
 Person.objects.filter(filter_by=filter_value)
 # ... which will throw an exception, since `filter_by` is not
 # an attribute of `Person`.
Brian M. Hunt
sumber

Jawaban:

311

Ekspansi argumen Python dapat digunakan untuk memecahkan masalah ini:

kwargs = {
    '{0}__{1}'.format('name', 'startswith'): 'A',
    '{0}__{1}'.format('name', 'endswith'): 'Z'
}

Person.objects.filter(**kwargs)

Ini adalah idiom Python yang sangat umum dan berguna.

Daniel Naab
sumber
6
Hanya kepala-upcha cepat: pastikan string dalam kwarg adalah tipe str bukan unicode, kalau tidak filter () akan menggerutu.
Steve Jalim
1
@santiagobasulto Ini juga disebut sebagai parameter packing / unpacking, dan variasi daripadanya.
Daniel Naab
7
bagus, bagus, dan bagus !
Oscar Mederos
5
@DanielNaab tetapi ini hanya akan berfungsi pada kwargs yang bekerja pada DAN penyaringan kondisi, alternatif apa pun untuk kondisi ATAU.
Prateek099
3
@prateek Anda selalu dapat menggunakan objek Q: stackoverflow.com/questions/13076822/...
deecodameeko
6

Contoh sederhana:

Dalam aplikasi survei Django, saya ingin daftar pilih HTML yang menunjukkan pengguna terdaftar. Tetapi karena kami memiliki 5.000 pengguna terdaftar, saya membutuhkan cara untuk memfilter daftar itu berdasarkan kriteria kueri (seperti hanya orang yang menyelesaikan lokakarya tertentu). Agar elemen survei dapat digunakan kembali, saya perlu orang yang membuat pertanyaan survei untuk dapat melampirkan kriteria tersebut untuk pertanyaan itu (tidak ingin menyulitkan kueri kode ke dalam aplikasi).

Solusi yang saya buat bukanlah 100% user friendly (membutuhkan bantuan dari orang teknologi untuk membuat kueri) tetapi itu memecahkan masalah. Saat membuat pertanyaan, editor dapat memasukkan kamus ke dalam bidang khusus, misalnya:

{'is_staff':True,'last_name__startswith':'A',}

String itu disimpan dalam database. Dalam kode tampilan, ia kembali sebagai self.question.custom_query. Nilai itu adalah string yang terlihat seperti kamus. Kami mengubahnya kembali menjadi kamus nyata dengan eval () dan kemudian memasukkannya ke queryset dengan ** kwargs:

kwargs = eval(self.question.custom_query)
user_list = User.objects.filter(**kwargs).order_by("last_name")   
shacker
sumber
Saya bertanya-tanya apa yang diperlukan untuk membuat ModelField / FormField / WidgetField kustom yang menerapkan perilaku untuk memungkinkan pengguna, di sisi GUI, pada dasarnya "membangun" kueri, tidak pernah melihat teks yang sebenarnya, tetapi menggunakan antarmuka untuk lakukan itu. Kedengarannya seperti proyek yang rapi ...
T. Stone
1
T. Stone - Saya membayangkan akan mudah untuk membuat alat semacam itu dengan cara yang sederhana jika model yang membutuhkan kueri sederhana, tetapi sangat sulit dilakukan dengan cara menyeluruh yang memaparkan semua opsi yang mungkin, terutama jika modelnya adalah kompleks.
shacker
5
-1 memanggil eval()impor pengguna adalah ide yang buruk, bahkan jika Anda sepenuhnya mempercayai pengguna Anda. Bidang JSON akan menjadi ide yang lebih baik di sini.
John Carter
5

Django.db.models.Q persis seperti yang Anda inginkan dengan cara Django.

Brent81
sumber
7
Bisakah Anda (atau seseorang) memberikan contoh cara menggunakan objek Q dalam menggunakan nama bidang dinamis?
jackdbernier
3
Ini sama seperti dalam jawaban Daniel Naab. Satu-satunya perbedaan adalah bahwa Anda meneruskan argumen ke konstruktor objek Q. Q(**filters), jika Anda ingin membangun objek Q secara dinamis, Anda dapat menempatkannya dalam daftar dan menggunakan .filter(*q_objects), atau menggunakan operator bitwise untuk menggabungkan objek Q.
Will S
5
Jawaban ini harus benar-benar termasuk contoh menggunakan Q untuk menyelesaikan masalah OP.
pdoherty926
-2

Bentuk pencarian yang sangat kompleks biasanya menunjukkan bahwa model yang lebih sederhana sedang mencoba menggali jalan keluarnya.

Bagaimana, tepatnya, apa yang Anda harapkan untuk mendapatkan nilai untuk nama kolom dan operasi? Di mana Anda mendapatkan nilai-nilai 'name'suatu 'startswith'?

 filter_by = '%s__%s' % ('name', 'startswith')
  1. Bentuk "pencarian"? Anda akan - apa? - pilih nama dari daftar nama? Pilih operasi dari daftar operasi? Sementara terbuka, kebanyakan orang menemukan ini membingungkan dan sulit digunakan.

    Berapa kolom yang memiliki filter seperti itu? 6? 12? 18?

    • Beberapa? Daftar pilih yang rumit tidak masuk akal. Beberapa bidang dan beberapa jika-pernyataan masuk akal.
    • Jumlah yang besar? Model Anda tidak terdengar benar. Kedengarannya seperti "bidang" sebenarnya merupakan kunci untuk baris di tabel lain, bukan kolom.
  2. Tombol filter khusus. Tunggu ... Begitulah cara kerja admin Django. Filter khusus diubah menjadi tombol. Dan analisis yang sama seperti di atas berlaku. Beberapa filter masuk akal. Sejumlah besar filter biasanya berarti jenis pelanggaran bentuk normal pertama.

Banyak bidang serupa sering berarti seharusnya ada lebih banyak baris dan lebih sedikit bidang.

S.Lott
sumber
9
Dengan hormat, lancang untuk membuat rekomendasi tanpa mengetahui apa pun tentang desain. Untuk "cukup mengimplementasikan" aplikasi ini akan menghasilkan fungsi astronomi (> 200 aplikasi ^ 21 foo) untuk memenuhi persyaratan. Anda membaca maksud dan tujuan menjadi contoh; kamu seharusnya tidak. :)
Brian M. Hunt
2
Saya bertemu banyak orang yang merasa bahwa masalah mereka akan sepele untuk dipecahkan jika hanya hal-hal yang (a) lebih umum dan (b) bekerja seperti yang mereka bayangkan. Dengan begitu terletak frustrasi tanpa akhir karena segala sesuatu tidak seperti yang mereka bayangkan. Saya telah melihat terlalu banyak kegagalan berasal dari "memperbaiki kerangka".
S.Lott
2
Segala sesuatunya berjalan sesuai yang diharapkan dan diinginkan berdasarkan respons Daniel. Pertanyaan saya adalah tentang sintaks, bukan desain. Jika saya punya waktu untuk menulis desain, saya akan melakukannya. Saya yakin masukan Anda akan sangat membantu, tetapi itu bukan pilihan praktis.
Brian M. Hunt
8
S.Lott, jawaban Anda bahkan tidak menjawab pertanyaan ini dari jarak jauh. Jika Anda tidak tahu jawaban, silakan tinggalkan pertanyaan sendiri. Jangan merespons dengan saran desain yang tidak diminta ketika Anda benar-benar tidak memiliki pengetahuan tentang desain!
slypete
2
@slypete: Jika perubahan pada desain menghilangkan masalah, maka masalahnya terpecahkan. Melanjutkan sepanjang jalur berdasarkan desain yang buruk lebih mahal dan kompleks daripada yang diperlukan. Memecahkan masalah penyebab utama lebih baik daripada memecahkan masalah lain yang berasal dari keputusan desain yang buruk. Maaf Anda tidak menyukai analisis akar-penyebab. Tetapi ketika sesuatu sangat sulit, biasanya berarti Anda mencoba hal yang salah untuk memulai.
S.Lott