Python
Django
django-rest-framework

Django REST framework で django-filter を使う

この記事について

Django REST framework に検索機能を楽に追加できるプラグイン「django-filter」の使い方をまとめました。

django-filter とは

少ないコーディングで検索機能を追加するdjangoのプラグインです。
組み込みのFilterSetより柔軟な設定が可能です。

公式ドキュメント
https://django-filter.readthedocs.io/en/master/

Django REST framework に触れていない人は、まずこちらのチュートリアル記事からどうぞ。
Django REST Frameworkを使って爆速でAPIを実装する

サンプルコード

チュートリアルのサンプルコードを省略したものを使います。

パッケージのバージョン
Django (2.0.1)
django-crispy-forms (1.7.0)
django-filter (1.1.0)
djangorestframework (3.7.7)

pip install でよしなに準備します。

project/settings.py
INSTALLED_APPS = [
    ...
    'rest_framework',
    'django_filters', 
    'crispy_forms',
    'sample',
]

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',) 
}
sample/models.py
from django.db import models

# Create your models here.
# テスト結果の記録をイメージ
class Exam(models.Model):

    LEVEL_SET = (
            ('easy', "初級"),
            ('normal', "中級"),
            ('hard', "上級"),
    )

    fullname = models.CharField(max_length=32)
    level = models.CharField(choices=LEVEL_SET, default='easy', max_length=8)
    score = models.IntegerField()
    is_checked =  models.BooleanField()
    created_at = models.DateTimeField(auto_now_add=True)
sample/views.py
# Create your views here.

from django_filters import rest_framework as filters 
from rest_framework import viewsets
from rest_framework import serializers
from .models import Exam


class ExamSerializer(serializers.ModelSerializer):
    class Meta:
        model = Exam
        fields = ('__all__')

class ExamViewSet(viewsets.ModelViewSet):
    queryset = Exam.objects.all()
    serializer_class = ExamSerializer

project/urls.py
from django.conf.urls import url, include
from django.contrib import admin

from rest_framework import routers
from sample.views import ExamViewSet

router = routers.DefaultRouter()
router.register(r'exam', ExamViewSet)

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/', include(router.urls)),
]

Browsable API画面

フィルタ機能が有効になると、ブラウザでWebAPIに直接アクセスしたときに出てくるBrowsable API 画面 に「Filter」ボタンが追加されます。ここをクリックすると検索用ウィンドウからAPIを試すことができます。
※crispy_formsを追加しておくと、ここのフォームがbootstrap対応になります。

文法

フィルタセットの文法

ViewSet(WebAPI)にfilter_fieldsを定義して簡潔に使う方法もありますが、完全一致の検索にしか使えません。
部分一致や範囲を指定するにはFilterSetを継承したフィルタセットを作成し、ViewSetのfilter_classで読みこみます。

sample/views.py
from django_filters import rest_framework as filters 
from rest_framework import viewsets
from rest_framework import serializers
from .models import Exam

# FilterSetを継承したフィルタセット(設定クラス)を作る
class ExamFilter(filters.FilterSet):

    # フィルタの定義
    fullname = filters.CharFilter(name="fullname", lookup_expr='contains')
    score_gt = filters.NumberFilter(lookup_expr='gt')

    class Meta:
        model = Exam
        # フィルタを列挙する。
        # デフォルトの検索方法でいいなら、モデルフィールド名のフィルタを直接定義できる。
        fields = ['fullname', 'score_gt'] 

class ExamViewSet(viewsets.ModelViewSet):
    queryset = Exam.objects.all()
    serializer_class = ExamSerializer
    # フィルタセットの指定
    filter_class = ExamFilter 

フィルタの定義

(公式) https://django-filter.readthedocs.io/en/latest/ref/filters.html#filters

基本文法
フィルタ名= filters.CharFilter(name="fullname", lookup_expr='contains')

・name: モデル内の検索対象フィールド ※デフォルトはフィルタ名
・lookup_expr 検索条件を指定する ※デフォルトはフィルタクラスによって異なる
・method: 独自の検索処理を定義できる(後述)

lookup_exprはdjangoのクエリ構文と同じ表現で検索方法を指定する。

lookup_exprに指定できる値の一覧(一部)

・テキスト

lookup_expr 検索方法
exact 完全一致
contains 部分一致
startswith 前方一致
endswith 後方一致
regex 正規表現

・数値・日時・テキスト(アスキー順)

lookup_expr 検索方法
gt
lt 未満
gte 以上
lte 以下

日時

・時刻の一部に一致する検索が可能

lookup_expr 検索方法
date 日付(YYYY-M-D)
year 年(YYYY)
month 月(M)
day 日(D)
week_day (1-7 日曜が1)
hour 時(H)
minute 分(m)
second 秒(s)

主なフィルタの種類

よくつかいそうなフィルタの定義例。これ以外にも多くの種類がある。詳しくは公式参照。
https://django-filter.readthedocs.io/en/latest/ref/filters.html#filters

CharFilter

テキスト検索

fullname = filters.CharFilter(lookup_expr='contains')

NumberFilter

数値検索

score = filters.NumberFilter(lookup_expr='gt')

BooleanFilter

Bool値検索

is_checked = filters.BooleanFilter()

※パラメータはtrueかfalseを指定

DateTimeFilter

日付検索

created_at = filters.DateTimeFilter(lookup_expr='gt')

パラメータは「YYYY-MM-DD HH24-mm-SS」または「YYYY-MM-DD」で指定

RangeFilter

範囲検索

score_range = filters.RangeFilter(name='score')

フィルタ名に「_0」「_1」がついたパラメータが追加され、それぞれ開始値と終了値を扱う。
片方だけ設定してもOK。

ChoiceFilter

選択肢検索

level = filters.ChoiceFilter(choices=Exam.LEVEL_SET)

値のエイリアスで検索する。choicesの指定にはModelのchoicesの設定を流用する。

MultipleChoiceFilter

level = filters.MultipleChoiceFilter(choices=Exam.LEVEL_SET)

同名のパラメータを複数回使ったOR検索が可能になる

並べ替え

https://django-filter.readthedocs.io/en/latest/ref/filters.html#orderingfilter

OrderingFilterを使って並べ替え(ソート順)の指定ができる。
フォート方法のフィールドはfieldsに(モデルフィールド,パラメータ名)のタプル配列で指定する

order_by = filters.OrderingFilter(
        # tuple-mapping retains order
        fields=(
            ('fullname', 'fullname'),
            ('score', 'score_sort'),
        ),
    )

残念ながら複数のフィールドを使った並べ替えはできないので、やりたい時は下のカスタマイズ法でやる

独自の検索処理を行う方法

フィールドの method 要素を使って独自のフィルター処理を定義できる。
以下は並べ替えた後、指定した数だけ上位件数を取得する例

http://django-filter.readthedocs.io/en/latest/ref/filters.html?highlight=method#method

class ExamFilter(filters.FilterSet):

    def get_top(self, queryset, name, value):
        return queryset.all()[:value]

    order_by = filters.OrderingFilter(
            # tuple-mapping retains order
            fields=(
                ('fullname', 'fullname'),
                ('score', 'score'),
            ),
        )

    top = filters.NumberFilter(method='get_top')

    class Meta:
        model = Exam
        fields = ['order_by','top']