20
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

django-filterの使い方を理解する

Last updated at Posted at 2020-10-21

#django-filterとは
検索条件を短いコードで書くことができます!!!

クエリパラメータのキーにモデルのフィールド名を入れて、バリュー値で検索が可能になります。

django-filter 公式ドキュメント

※この記事では、Django Rest Frameworkの使用と初期設定は終わっている想定です!!!!!!!

#modelとseiralizerを定義
サンプルになりますが、このようなモデルとシリアライザを定義しておきます。

models.py
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)
serializer.py
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

#基本的な書き方
まず、django-filterの書き方はこのようになります。
検索条件などで絞り込まずに一覧の取得を行ってみます。

サンプル_view.py
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/(省いています。)
にブラウザからリクエストすると、このようなデータがレスポンスされます。
もちろんデータはサンプルです笑

スクリーンショット 2020-10-21 19.38.42.png

#検索条件を指定する
それでは検索条件を指定してリクエストする。
先ほどリクエストしたURLは/api/v1/book/list/でしたが、そこのクエリパラメータを指定します。
/api/v1/book/list/?title=testに変更してリクエストするとレスポンスが変わります。

スクリーンショット 2020-10-21 19.48.30.png

今回のレスポンスは何も返却されませんでした。
このtitle=testというのは、Bookモデルのtitleがtestに一致するオブジェクトを返します。
その他にもtitlesub_titleに変更して、/api/v1/book/list/?sub_title=test
リクエストをしてもレスポンスは何にも返ってきません。

ただし、クエリパラメータをBookモデルに存在しないフィールドを指定すると
絞り込みができず、レスポンスにデータが返ってきます。

#検索条件を変更する

先ほどは、titlesub_titleで絞り込んで
データを抽出しました。

ただ今回は、titleは検索条件でいいけど、sub_titleは検索条件として要らない!
なんてことがあるかと思います。
そんな時は、Filterクラスのfieldsで、該当のみに指定することができます。

views.py
class FilterBook(filters.FilterSet):
    class Meta:
        model = Book
        fields = ['title']

このようにすることで、先ほどはsub_titleで絞り込みできていましたが、titleでしか絞り込みができなくなりました。

#検索方法をカスタマイズする
先ほどfieldsを__all__で指定した場合だと、デフォルトの検索方法になってしまい
数値の検索や、部分一致などの検索方法が使えませんでした。
(例えば、/api/v1/book/list/?price=testにリクエストしても、絞り込みはされません。)

そこで、検索方法をカスタマイズしていきます。

views.py
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
にリクエストしても、データがレスポンスされるようになりました。

スクリーンショット 2020-10-21 20.39.53.png

#リレーションにも対応する
ForeignKeyで結びついたモデルも検索条件にしたい場合は、
field_nameに指定します。
指定方法はdjangoのlookup式で対応できます。

views.py
class FilterBook(filters.FilterSet):
    author_last_name = filters.CharFilter(field_name='author__last_name',lookup_expr='icontains')

#デフォルトのフィルターをカスタマイズする
デフォルトのフィルターでは、CharFilterが完全一致だったりでフィールドごとにカスタマイズする必要がありました。
フィールドごとにカスタマイズするのではなく、CharFilterのデフォルトをカスタマイズしようということです。

views.py
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を使っているフィールドは、デフォルトで全て部分一致になりました。

#独自のカスタマイズをする
絞りこむ際に独自の検索を行うことができます。
ハイフンを取り除き、部分一致にする例を作成しました。

views.py
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---
にリクエストを送信してもレスポンスがくるようになりました。

スクリーンショット 2020-10-22 1.38.34.png


この他の詳しい情報は、
ぜひ公式ドキュメント
ご確認いただければとおもいます!!!!!!!!!!!!!!!!!

20
21
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?