#django-filterとは
検索条件を短いコードで書くことができます!!!
クエリパラメータのキーにモデルのフィールド名を入れて、バリュー値で検索が可能になります。
※この記事では、Django Rest Frameworkの使用と初期設定は終わっている想定です!!!!!!!
#modelとseiralizerを定義
サンプルになりますが、このようなモデルとシリアライザを定義しておきます。
class Book(models.Model):
"""Bookモデル ※一部省略"""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
sub_title = models.CharField(max_length=128)
price = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True)
#
author = models.ForeignKey(Author, on_delete=models.PROTECT, blank=True, null=True)
publisher = models.ForeignKey(Publisher, on_delete=models.PROTECT)
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
#基本的な書き方
まず、django-filterの書き方はこのようになります。
検索条件などで絞り込まずに一覧の取得を行ってみます。
from django_filters import rest_framework as filters
class FilterBook(filters.FilterSet):
""" BookモデルのFilter """
class Meta:
model = Book
fields = '__all__'
class ListBook(APIView):
""" BookモデルのAPIView """
def get(self, request):
filterset = FilterBook(request.query_params, queryset=Book.objects.all())
serializer = BookSerializer(instance=filterset.qs, many=True)
return Response(serializer.data)
FilterSetを継承したクラスを作成して、そこでモデルを指定します。
DjangoのFormやSerializerに似ていますね。
そしてこの作成したListBookAPIのURL
/api/v1/book/list/
(省いています。)
にブラウザからリクエストすると、このようなデータがレスポンスされます。
もちろんデータはサンプルです笑
#検索条件を指定する
それでは検索条件を指定してリクエストする。
先ほどリクエストしたURLは/api/v1/book/list/
でしたが、そこのクエリパラメータを指定します。
/api/v1/book/list/?title=test
に変更してリクエストするとレスポンスが変わります。
今回のレスポンスは何も返却されませんでした。
このtitle=test
というのは、Bookモデルのtitleがtestに一致するオブジェクトを返します。
その他にもtitle
をsub_title
に変更して、/api/v1/book/list/?sub_title=test
に
リクエストをしてもレスポンスは何にも返ってきません。
ただし、クエリパラメータをBookモデルに存在しないフィールドを指定すると
絞り込みができず、レスポンスにデータが返ってきます。
#検索条件を変更する
先ほどは、title
やsub_title
で絞り込んで
データを抽出しました。
ただ今回は、title
は検索条件でいいけど、sub_title
は検索条件として要らない!
なんてことがあるかと思います。
そんな時は、Filterクラスのfields
で、該当のみに指定することができます。
class FilterBook(filters.FilterSet):
class Meta:
model = Book
fields = ['title']
このようにすることで、先ほどはsub_title
で絞り込みできていましたが、title
でしか絞り込みができなくなりました。
#検索方法をカスタマイズする
先ほどfieldsを__all__
で指定した場合だと、デフォルトの検索方法になってしまい
数値の検索や、部分一致などの検索方法が使えませんでした。
(例えば、/api/v1/book/list/?price=test
にリクエストしても、絞り込みはされません。)
そこで、検索方法をカスタマイズしていきます。
class FilterBook(filters.FilterSet):
# UUID
id = filters.UUIDFilter()
# 部分一致
title = filters.CharFilter(lookup_expr='icontains')
# 金額を調べる
price = filters.NumberFilter()
price__gt = filters.NumberFilter(field_name='price', lookup_expr='gt')
price__lt = filters.NumberFilter(field_name='price', lookup_expr='lt')
class Meta:
model = Book
fields = []
カスタマイズの内容です。
filters.UUIDFilter()
は、UUIDへの対応です。IDで検索かけても引っかかるようになりました。
filters.CharFilter(lookup_expr='icontains')
で
部分一致にしました。このlookup_expr
の引数は、Djangoのfield lookups
と同じ指定ができます。
filters.NumberFilter(field_name='price', lookup_expr='gt')
は数値の対応と範囲を指定しています。
field_name
は、モデルの対象フィールドを指定します。
通常はフィルターのフィールド名とモデルのフィールド名で一致していれば、指定しなくても問題ありません。
それではカスタマイズ後にリクエストをします。
api/v1/book/list/?title=sql
や
api/v1/book/list/?price_gt=3200&price_lt=4000
にリクエストしても、データがレスポンスされるようになりました。
#リレーションにも対応する
ForeignKeyで結びついたモデルも検索条件にしたい場合は、
field_name
に指定します。
指定方法はdjangoのlookup式で対応できます。
class FilterBook(filters.FilterSet):
author_last_name = filters.CharFilter(field_name='author__last_name',lookup_expr='icontains')
#デフォルトのフィルターをカスタマイズする
デフォルトのフィルターでは、CharFilterが完全一致だったりでフィールドごとにカスタマイズする必要がありました。
フィールドごとにカスタマイズするのではなく、CharFilterのデフォルトをカスタマイズしようということです。
class FilterBook(filters.FilterSet):
class Meta:
model = Book
fields = ['title']
filter_overrides = {
models.CharField: {
'filter_class': filters.CharFilter,
'extra': lambda f: {
'lookup_expr': 'icontains',
},
},
}
class Meta:
にfilter_overrrides
とデフォルトの設定にしたい内容を記載します。
今回これで、モデルにCharFiled
を使っているフィールドは、デフォルトで全て部分一致になりました。
#独自のカスタマイズをする
絞りこむ際に独自の検索を行うことができます。
ハイフンを取り除き、部分一致にする例を作成しました。
class FilterBook(filters.FilterSet):
title = filters.CharFilter(method='title_custom_filter')
class Meta:
model = Book
fields = []
@staticmethod
def title_custom_filter(queryset, name, value):
# nameはフィールド名前
# valueは検索する値
# name__icontainsが部分一致
lookup = '__'.join([name, 'icontains'])
# ハイフンを消す
replaced_value = value.replace('-', '')
return queryset.filter(**{
lookup: replaced_value,
})
CharFilter
の引数にメソッドの名前を文字列で渡すと、そのメソッドが検索時に実行されます。
name
にフィールド名、value
検索のパラメータが渡されます。
なのでそこを変更すれば、自由に検索条件が作成できる分けです。
今回では、例えば
/api/v1/book/list/?title=g
と同じように
/api/v1/book/list/?title=g---
にリクエストを送信してもレスポンスがくるようになりました。
この他の詳しい情報は、
ぜひ公式ドキュメントを
ご確認いただければとおもいます!!!!!!!!!!!!!!!!!