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