Django python rest frameworkとdjango filterを使って、apiのfilteringの実装をする

前回Pagingの対応を行いましたが、filtering・sortなどの機能を実装するために、
django-filterというライブラリを使いたいと思います。

source bin/activateで、仮装環境を作成してから、
pipコマンドなどを使って、django-filterをインストールしましょう。


pip install django-filter
    

settings.pyにfilteringの設定を書き込む

restful01/restful01/settings.pyにdjango-filterの依存関係を記述していきます。

まず、django-filterを認識するようにINSTALLED_APPSにdjango-filtersを追加します。


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # django rest framework
    'rest_framework',
    'newFlowers.apps.NewflowersConfig',
    # Django Filters,
    'django_filters',
]
    

続いて、REST_FRAMEWORKに追記します。


REST_FRAMEWORK = {
  'DEFAULT_PAGINATION_CLASS':
  'flowers.custompagination.LimitOffsetPaginationWithUpperBound',
  'PAGE_SIZE': 2
  'DEFAULT_FILTER_BACKENDS': (
      'django_filters.rest_framework.DjangoFilterBackend',
      'rest_framework.filters.OrderingFilter',
      'rest_framework.filters.SearchFilter',
      ),
}
    

依存ライブラリーの解説をすると。。。

フレームワーク名 機能 公式リファレンス
rest_framework.filters.OrderingFilter apiにorder sortをするためのqueryを提供します http://example.com/api/users?ordering=username
rest_framework.filters.SearchFilter apiにsearchをするためのqueryを提供します http://example.com/api/users?search=russell
django_filters.rest_framework.DjangoFilterBackend django_filterの機能を使うためのパッケージ なし

viewクラスを修正して、apiにfilterを適応する

restful01/newFlowers/views.pyを修正して、apiにfilter機能を加えましょう。
修正をするapiは、FlowerCategoryList, FlowerList, FarmerList,CompetitionListです。

まずは、以下のライブラリをimportします。


from django_filters import rest_framework as filters
from django_filters import AllValuesFilter, DateTimeFilter, NumberFilter
    

FlowerCategoryListにfilterが適用されるように以下のように追記します。


class FlowerCategoryList(generics.ListCreateAPIView):
    queryset = FlowerCategory.objects.all()
    serializer_class = FlowerCategorySerializer
    name = 'flowercategory-list'
    filter_fields = (
        'name',
        )
    search_fields = (
        '^name',
        )
    ordering_fields = (
        'name',
        )
    

各々にカラムnameでフィルターをかけています。
続いて、FlowerListでは複数のカラムでfilterをかけます。


class FlowerList(generics.ListCreateAPIView):
    queryset = Flower.objects.all()
    serializer_class = FlowerSerializer
    name = 'flower-list'
    filter_fields = (
        'name',
        'flower_category',
        'production_date',
        'has_it_competed',
        )
    search_fields = (
        '^name',
        )
    ordering_fields = (
        'name',
        'production_date',
        )
    

filter_fieldsに外部キーであるflower_categoryを入れています。
なので、flower_categoryはIDで検索することができます。

続いて、FarmerList apiにfilterを適用します。


class FarmerList(generics.ListCreateAPIView):
    queryset = Farmer.objects.all()
    serializer_class = FarmerSerializer
    name = 'farmer-list'

    filter_fields = (
        'name',
        'gender',
        'competitions_count',
        )
    search_fields = (
        '^name',
        )
    ordering_fields = (
        'name',
        'competitions_count'
        )
    

custom filterクラスを導入する

続いて、competition apiにdjango-filterのcustom filter機能を適用してみます。
views.pyに以下のようにdjango-filterのfilters.FilterSetクラスを継承して、
CompetitionFilterクラスを作成します。


class CompetitionFilter(filters.FilterSet):
    from_achievement_date = DateTimeFilter(
        field_name='score_achievement_date', lookup_expr='gte')
    to_achievement_date = DateTimeFilter(
        field_name='score_achievement_date', lookup_expr='lte')
    min_score = NumberFilter(
        field_name='score', lookup_expr='gte')
    max_score = NumberFilter(
        field_name='score', lookup_expr='lte')
    flower_name = AllValuesFilter(
        field_name='flower__name')
    farmer_name = AllValuesFilter(
        field_name='farmer__name')

    class Meta:
        model = Competition
        fields = (
            'score',
            'from_achievement_date',
            'to_achievement_date',
            'min_score',
            'max_score',
            # flower__nameからflower_nameに変換される
            'flower_name',
            # farmer__nameからfarmer_nameに変換される
            'farmer_name',
            )
    

DateTimeFilter

DateTimeFieldでフィルタリングしたい場合は、DateTimeFieldを使用します。 また、filed_nameには対象のカラム入れます。

lookup_expr

lookup_exprにはデータの並び順を指定します。 gteはgreater than or equal toの略で、
lteはless than or equal toの略です。

その他の指定方法は公式のドキュメントを参照してください。

NumberFilter

scoreカラムでフィルタリングするために、NumberFilterを使用しています。
DateTimeFilterと同じく、filed_nameにカラムを指定し、lookup_exprに並べ変えフィルターを指定しています。

AllValuesFilter

flower_name,farmer_nameで文字列でフィルタをかけられるようにAllValuesFilterを使用しています。
filed_nameにカラムを指定します。

MetaClass

宣言したフィルターで検索がかけられるように、MetaClassを作成して、
fieldを追加しています。

Filterクラスをapiに適応する

作成したフィルターをCompetitionList Apiに適応するために、
CompetitionListを以下のように修正します。


class CompetitionList(generics.ListCreateAPIView):
    queryset = Competition.objects.all()
    serializer_class = FarmerCompetitionSerializer
    name = 'competition-list'

    filter_class = CompetitionFilter
    ordering_fields = (
        'score',
        'score_achievement_date',
        )
    

filter_classに先ほど作成したCompetitionFilterを指定し、
フィルタの対象となるordering_fieldsに'score', 'score_achievement_date'を指定しています。

Apiを叩いてフィルタリングされているか確認する

apiを叩いて、フィルタリングされるかどうか確認してみます。

flower_categoryのnameに検索をかけてみます。
現状1件しかないので、まず、flower_categoryを1件追加してから、apiを叩きます。


curl -iX POST -H "Content-Type: application/json" -d '{"name":"Rosaceae"}' localhost:8000/flower-categories/

curl -iX GET "localhost:8000/flower-categories/?name=Liliaceae"

HTTP/1.1 200 OK
Date: Sun, 29 Jul 2018 09:17:28 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Content-Type: application/json
Vary: Accept, Cookie
Allow: GET, POST, HEAD, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Length: 311

{"count":1,"next":null,"previous":null,"results":[
{"url":"http://localhost:8000/flower-categories/1","pk":1,"name":"Liliaceae","flowers":["http://localhost:8000/flowers/4","http://localhost:8000/flowers/1","http://localhost:8000/flowers/3","http://localhost:8000/flowers/2","http://localhost:8000/flowers/5"]}]}
    

フィルタがかけられて、name:Liliaceaeのみが返却されていることが確認できます。

続いて、FlowerDetail apiの確認をします。
フィルタに指定した、flower-categoryとhas_it_competedを使い、apiを叩きます。


curl -iX GET "localhost:8000/flowers/?flower_category=1&has_it_competed=False&ordering=-name"

HTTP/1.1 200 OK
Date: Sun, 29 Jul 2018 09:28:47 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Content-Type: application/json
Vary: Accept, Cookie
Allow: GET, POST, HEAD, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Length: 579

{"count":5,"next":"http://localhost:8000/flowers/?flower_category=1&has_it_competed=False&limit=2&offset=2&ordering=-name","previous":null,"results":[{"url":"http://localhost:8000/flowers/5","name":"yellowLily","flower_category":"Liliaceae","production_date":"2018-08-20T02:02:00.716312Z","has_it_competed":false,"inserted_timestamp":"2018-07-22T02:07:34.560213Z"},{"url":"http://localhost:8000/flowers/2","name":"tinyLily","flower_category":"Liliaceae","production_date":"2018-08-20T02:02:00.716312Z","has_it_competed":false,"inserted_timestamp":"2018-07-08T08:37:58.067468Z"}]}(
    

こちらもokです。

カスタムフィルタをかけたapiの挙動の確認

カスタムフィルタを指定したCompetition apiの挙動確認をします。


curl -iX GET "localhost:8000/competitions/?farmer_name=jiji&flower_name=newLily"

HTTP/1.1 200 OK
Date: Sun, 29 Jul 2018 09:32:19 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Content-Type: application/json
Vary: Accept, Cookie
Allow: GET, POST, HEAD, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Length: 206

{"count":1,"next":null,"previous":null,"results":[{"url":"http://localhost:8000/competitions/1","pk":1,"score":60,"score_achievement_date":"2018-10-20T05:03:20.776594Z","farmer":"jiji","flower":"newLily"}]}
    

dbの中身通り、フィルタがかけられています。

続いて、DateTimeFieldでフィルタをかけて、apiを叩いてみます。


curl -iX GET "localhost:8000/competitions/?min_score=61&max_score=100&from_achievement_date=2016-10-18&to_achievement_date=2018-10-29&ordering=-achievement_date"

HTTP/1.1 200 OK
Date: Sun, 29 Jul 2018 09:35:31 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Content-Type: application/json
Vary: Accept, Cookie
Allow: GET, POST, HEAD, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Length: 211

{"count":1,"next":null,"previous":null,"results":[{"url":"http://localhost:8000/competitions/2","pk":2,"score":62,"score_achievement_date":"2018-10-21T06:02:23.776594Z","farmer":"jiji1000","flower":"tinyLily"}]}
    

DateTimeFieldもokです。

こんな感じで、django rest frameworkとdjango-filterを使って、apiにフィルタリングをかけることができました。

初版:2018/7/30

このエントリーをはてなブックマークに追加