【Django】django-filterで複数キーワードAND・OR検索をする方法。

django-filterを使って、特定フィールド(今回は記事タイトル)に対して複数キーワードで「AND検索」や「OR検索」をしたい。

調べてながら試してみて実際に動作したコードをメモとして残しておきます。

今回フィルタをかける対象URL

全記事データを取得する下記URLに、パラメータを追加してフィルタをかけていきます。

http://127.0.0.1:8000/api/post/
 => 全ての記事を取得

複数キーワードで「AND検索」

記事タイトルに「テスト」「記事」のどちらも含まれる記事を取得したい。

コード

from ..models import Post
from ..serializers import Post
from rest_framework.permissions import AllowAny
from django_filters import rest_framework as filters
from django_filters.rest_framework import DjangoFilterBackend


# AND検索用のカスタムフィルタ
class MultiValueCharFilterAnd(filters.CharFilter):
	def filter(self, qs, value):
		values = value.split(',') or []

		for value in values:
			qs = super(MultiValueCharFilterAnd, self).filter(qs, value)
		return qs


# フィルタ
class PostFilter(filters.FilterSet):
	title = MultiValueCharFilterAnd(field_name='title', lookup_expr='contains')


# ビュー
class PostListView(generics.ListAPIView):
	queryset = Post.objects.all()
	serializer_class = PostListSerializer
	permission_classes = (AllowAny,)
	filter_backends = (DjangoFilterBackend,)
	filter_class = PostFilter  # フィルタを指定

エンドポイント(URL)

http://127.0.0.1:8000/api/post/?title=テスト,記事
 => titleに「テスト」「記事」のどちらも含まれる記事を取得

複数キーワードで「OR検索」

記事タイトルに「テスト」「記事」のいずれか一つでも含まれる記事を取得したい。

コード

from ..models import Post
from ..serializers import Post
from rest_framework.permissions import AllowAny
from django_filters import rest_framework as filters
from django_filters.rest_framework import DjangoFilterBackend
import operator
from functools import reduce
from django.db.models import Q


# OR検索用のカスタムフィルタ
class MultiValueCharFilterOr(filters.CharFilter):

	def filter(self, qs, value):
		values = value.split(',') or []
		expr = reduce(
			operator.or_,
			(Q(**{f'{self.field_name}__{self.lookup_expr}': v}) for v in values)
		)
		return qs.filter(expr)


# フィルタ
class PostFilter(filters.FilterSet):
	title = MultiValueCharFilterOr(field_name='title', lookup_expr='contains')


# ビュー
class PostListView(generics.ListAPIView):
	queryset = Post.objects.all()
	serializer_class = PostListSerializer
	permission_classes = (AllowAny,)
	filter_backends = (DjangoFilterBackend,)
	filter_class = PostFilter  # フィルタを指定

エンドポイント(URL)

http://127.0.0.1:8000/api/post/?title=テスト,記事
 => titleに「テスト」または「記事」が含まれる記事を取得

コメントを残す

*