Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Django REST framework で django-filter を使う

More than 3 years have passed since last update.

この記事について

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検索が可能になる

並べ替え

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

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

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

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

フィールドの 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']
okoppe8
札幌でシステムエンジニアをしています。 「Djangoで業務システム作る時に必要な機能をあらかじめ用意する」というテーマでやってます。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away