Djangoのフォーム制御は一癖も二癖もあり、細かい制御をしていくとなるとけっこう煩雑になります。そのたびに分岐したりするのはすごく面倒なので、最短で複数のキーワードに対応できる方法がないか調べてみたところそれを実現しているページがあり、実装してみてこれはかなり使えると思い、更に簡略化したものを記事にしてみました。
記事の要件定義はMstBooksという書籍マスターから該当する作品(book_nm)をキーワード検索したい場合です。
from django.db.models import Q
from functools import reduce
from operator import and_
def(request):
#中略
if form.is_valid():
keywords = [''] if form.cleaned_data['keyword'] is '' else form.cleaned_data['keyword'].split()
result = MstBooks.objects.filter(
reduce(and_,[Q(book_nm__icontains=q) for q in keywords]),
).order_by("id")
おわりです
解説
流石にこれでは記事としてあんまりなので、解説を加えます。テンプレートにはname=keywordとなったテキストボックスがあり、ここにフリーワードが入力されます。
そして、Pythonの三項演算子の公式に従って、文字が複数入っている、または単一文字列の場合はsplit()制御によってオブジェクト化されます。splitは引数を何も入れない場合\sが基準となるので、複数ワードが入っている場合はきちんと文字が分けられ、単一ワードの場合は入力文字一つがオブジェクト化されます。対して無記入の場合は空の値を代入しておきます。こうやっておかないと、検索条件のreduceでエラーが発生します。
TypeError: Reduce() of empty sequence with no initial value
クエリセットの式
そして、クエリ検索条件にセットしている式はこのようになっています。
reduce(and_,[Q(book_nm__icontains=q) for q in keywords])
まずQオブジェクトでないと条件式をループできません。そしてPythonのループ式を使って、検索キーワードを一つずつicontainsに与えています。icontainsはlike演算子と同じ働きをするQオブジェクト上のメソッドです。
またreduceはループ処理を再帰的に実行してくれるものなので、どんどんと計算式を追記してくれます。その際、結合条件としているのがand_なので、これによってand文を次々と追記してくれるわけです。検索条件が存在しない場合は空オブジェクトが渡されます。
また、こうやっておけば、フォームを追加して検索条件を足したい場合でもすぐに対応できます。
検証
クエリに対し、queryプロパティを付与するとSQLを出力できます。
print(result.query)
SELECT `app_mstbooks`.`id`, `app_mstbooks`.`book_nm`,
`app_mstbooks`.`book_yomi`,`app_mstbooks`.`auth_no`, `app_mstbooks`.`published`,
`app_mstbooks`.`publisher`, `app_mstbooks`.`files`, `app_mstbooks`.`jitai`,
`app_mstbooks`.`kana` FROM `app_mstbooks`
WHERE (`app_mstbooks`.`book_nm` LIKE %の% AND `app_mstbooks`.`book_nm` LIKE %に% ))
ORDER BY `app_mstbooks`.`id` ASC
このようにすればテキストボックスに「の に」と入力すると「~の~」、「~に~」が含まれる書籍タイトルが検索されます。
- 城の崎にて
- 恩讐の彼方に
- 死の影の下に
※参考までに、未入力の場合はこのようになります。
app_mstbooks`.`book_nm` LIKE %%