はじめに
これはこのページの日本語訳です。
フィルタリング
この章はDjangoのドキュメントの引用から始まります。
Manager から渡される QuerySet は、最初はデータベースにあるすべてのオブジェクトが入っている。そのため、そのオブジェクトのうち必要なものだけの部分集合を取得する必要がある。 — Django ドキュメント
Django REST framework の ListView は、デフォルトでは全ての QuerySet を返します。しかし、普通は、QuerySet のうち特定のものだけ返したいはずです。
QuerySet をフィルタしたい時、最も簡単なのは、 GenericAPIView を継承して get_queryset() メソッドをオーバーライド(上書き)することです。
このメソッドを上書きすることで、各 View で、様々な条件の QuerySet を返すことができます。
ログインユーザーを使ったフィルタリング
ログインしているユーザーに関連する QuerySet だけを返したいことがあります。
このような時は、 request.user
が使えます。
例:
from myapp.models import Purchase from myapp.serializers import PurchaseSerializer from rest_framework import generics class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): """ この View は、ログインしているユーザーの すべての支払履歴(Purchese)を返します """ user = self.request.user return Purchase.objects.filter(purchaser=user)
URLによるフィルタリング
他にも、URLを使って QuerySet をフィルタリングする方法もあります。
例えば、以下のようにURLが設定されているとします。
url('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),
このとき、URLに含まれるusernameを使って、Purchaseをフィルタリングするには以下のように書きます。
class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): """ この View はURLに含まれるusernameを持つuserの すべてのPurchaseを返します。 """ username = self.kwargs['username'] return Purchase.objects.filter(purchaser__username=username)
Query parameter を使ったフィルタリング
最後に、URLの Query parameter を使ってフィルタリングされた QuerySet を返す例です。
get_queryset()
メソッドを上書きし、 http://example.com/api/purchases?username=denvercoder9
のようなURLの、username
を使い、QuerySetをフィルタリングします。
class PurchaseList(generics.ListAPIView): serializer_class = PurchaseSerializer def get_queryset(self): """ username パラメータによって与えられたユーザーの Purchase だけを返すようにする """ queryset = Purchase.objects.all() username = self.request.query_params.get('username', None) if username is not None: queryset = queryset.filter(purchaser__username=username) return queryset
ジェネリックを使ったフィルタリング
get_queryset
を上書きしなくても、REST framework にはフィルタを作れる機能があります。これをジェネリックバックエンドフィルタと呼びます。*1
これを使うと、APIの結果をフィルタできるだけでなく、ブラウザでAPIを叩けるREST frameworksの機能「Browsable API」のUI上にも表示されます。
フィルタを設定する
下記のように DEFAULT_FILTER_BACKENDS
を指定すると、全ての View に対して、DjangoFilterBackend というフィルタが適用されます。(DjangoFilterBackend については後に仕様などが出てきます)
REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] }
個別のViewに設定したい場合は、GenericAPIView
クラスを継承した View に filter_backends プロパティを設定してください。
import django_filters.rest_framework from django.contrib.auth.models import User from myapp.serializers import UserSerializer from rest_framework import generics class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
フィルタの注意点
フィルタが View に適用されているとき、リストを返す場合だけでなく、単体のオブジェクトを返す時にもそのフィルタが適用されるので注意してください。
例えば、下記のように、id=4675 のオブジェクトを取得したとき、category や max_price に 4675 のオブジェクトが適合しない場合は、404 NotFound を返します。
http://example.com/api/products/4675/?category=clothing&max_price=10.00
get_queryset を上書き + FilterBackend
get_queryset()
を上書きする方法と、 フィルタクラスを使う方法を両方合わせる事もできます。例えば、 Product
と User
が purchase
という多対多の関係を持っている時、このようにできます。下記の例だと、filterset_class というものを指定しつつ、get_queryset を上書きしています。
class PurchasedProductsList(generics.ListAPIView): model = Product serializer_class = ProductSerializer filterset_class = ProductFilter def get_queryset(self): user = self.request.user return user.purchase_set.all()
REST framework の詳細な仕様
DjangoFilterBackend
django-filter
ライブラリには DjangoFilterBackend
クラスがあり、これが REST framework で使えます。
DjangoFilterBackend
を使うには、django-filter をインストールし、 django_filters
を Django の INSTELLED_APPS
に追加してください。
pip install django-filter
DjangoFilterBackend
を設定に追加するのを忘れないようにしてください。
REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] }
あるいは、個別のViewやViewSetに DjangoFilterBackend を追加してください。
from django_filters.rest_framework import DjangoFilterBackend class UserListView(generics.ListAPIView): ... filter_backends = [DjangoFilterBackend]
filterset_fields
を設定すれば、そのフィールドに対してのみ、完全一致でのフィルタが可能になります。
class ProductList(generics.ListAPIView): queryset = Product.objects.all() serializer_class = ProductSerializer filter_backends = [DjangoFilterBackend] filterset_fields = ['category', 'in_stock']
これにより、自動的にフィルタが使えるようになります。
http://example.com/api/products?category=clothing&in_stock=True
もっと高度なフィルタリングをしたい場合は、特殊な FilterSet クラスを実装する必要があります。django-filter のドキュメントの FilterSet を参照してください。DRF integration の節も合わせて読むとよいでしょう。
SearchFilter
SearchFilter
クラスは、キーワード検索を可能にします。
SearchFilter は、search_fields
に指定されたフィールドを対象に検索します。search_fields には、 CharFields
や TextField
などの、文字のフィールドのみが指定可能です。
from rest_framework import filters class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [filters.SearchFilter] search_fields = ['username', 'email']
このようにすると、以下のような検索ができます。
http://example.com/api/users?search=russell
アンダースコア2つで ForeignKey
や ManyToManyField
の検索も可能です。
search_fields = ['username', 'email', 'profile__profession']
デフォルトだと、検索は大文字小文字を区別せず、部分一致の検索になります。キーワードはカンマかスペースで区切ることで複数指定することもできます。複数のキーワードを指定した場合は、全てのキーワードを含むオブジェクトのみが返ってきます。
search_fields
パラメータには、以下の記法が使えます。
^
前方一致=
完全一致@
全文検索(MySQLをバックエンドに使っている場合のみ使えます)$
正規表現による検索
例:
search_fields = ['=username', '=email']
デフォルトでは、検索の時のクエリパラメータの名前は search
ですが、設定の SEARCH_PARAM
で変更できます。
リクエスト内容によって検索対象のフィールドを変更したいなど、特殊なことをしたい場合は、 SearchFilter
を継承して、 get_search_fields()
を上書きしてください。下記のコードは、title_only
パラメータを指定された場合はタイトルのみを検索対象にするクラスです。
from rest_framework import filters class CustomSearchFilter(filters.SearchFilter): def get_search_fields(self, view, request): if request.query_params.get('title_only'): return ['title'] return super(CustomSearchFilter, self).get_search_fields(view, request)
もっと詳しく知りたい場合は Django のドキュメントを参照してください。
OrderingFilter
OrderingFilter
クラスは簡単に結果を並べ替えることできます。
デフォルトだと、クエリパラメータ名は ordering
になりますが、 ORDERING_PARAM
で設定できます。
例えば、usernameで並べ替えたい場合はこうなります。
http://example.com/api/users?ordering=username
逆順で並べたい場合は、フィールド名の最初に -
をつけます。
http://example.com/api/users?ordering=-username
複数のフィールドで並べ替えたい場合はこのようにします。
http://example.com/api/users?ordering=account,username
並べ替えできるフィールドを限定する
並べ替えできるフィールドは限定することをおすすめします。 ordering_fields
で限定できます。
class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [filters.OrderingFilter] ordering_fields = ['username', 'email']
これをしないと、passwordのような秘匿情報を並べ替えフィードに指定することができてしまい、データを推測されてしまう可能性があります。
ただし、ordering_fields を指定しない場合は、serializer_class で指定されたシリアライザで取得可能な値でのみ並べ替え可能になります。
秘匿情報を含まないので、シリアライザに関わらずすべてのフィールドを並べ替え可能にしたい場合は、__all__
を指定します。
class BookingsListView(generics.ListAPIView): queryset = Booking.objects.all() serializer_class = BookingSerializer filter_backends = [filters.OrderingFilter] ordering_fields = '__all__'
デフォルトの並び順を指定する
View に ordering
プロパティを指定すると、デフォルトの並び順がそれになります。
class UserListView(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer filter_backends = [filters.OrderingFilter] ordering_fields = ['username', 'email'] ordering = ['username']
フィルタのジェネリックを使ってフィルタをカスタマイズする
フィルタのジェネリックを使うことで、フィルタをカスタマイズできたり、他の開発者が作ったフィルタを使うことができます。
BaseFilterBackend
を継承して、 .filter_queryset(self, request, queryset, view)
を上書きします。このメソッドは、フィルタリング後の QuerySet を返すようにしてください。
例
リクエストしたユーザーが作成したオブジェクトだけ返したい場合はこうします。
class IsOwnerFilterBackend(filters.BaseFilterBackend): """ 自分のオブジェクトのみ返す """ def filter_queryset(self, request, queryset, view): return queryset.filter(owner=request.user)
View の get_queryset()
を上書きすることで同様の実装が可能ですが、 FilterBackend を使うと、簡単に様々なViewに使いまわせたり、API全体に適用させたりできます。
Browsable API インタフェースのカスタマイズ
このジェネリックは Browsable API にも有効です。to_html()
メソッドを実装することで、HTML表示にも対応できます。to_html を以下の形式に従って実装してください。
to_html(self, request, queryset, view)
このメソッドは HTML の string を返してください。
ページネーションとスキーマについて
get_schema_fields()
メソッドを実装することで、必要なフィールドだけ返すことができます。
get_schema_fields(self, view)
このメソッドは、 coreapi.Field
のリストを返してください。
サードパーティーのパッケージ
Django REST framework filters パッケージ
関係を横断したフィルタなど、更に複雑なフィルタを簡単に実装できます。
Django REST framework full word search filter
filters.SearchFilter
よりさらに高度な検索をするためのパッケージです。
Django URL Filter
人間に読みやすいURLで、かつ安全にフィルタリングをするためのパッケージです。ネストできるなど、DRFシリアライザと似たような動きをします。関係を含むようなデータのフィルタリングを簡単に実装できます。
drf-url-filters
*1:ジェネリック=一般的な、バックエンド=サーバーサイドでフィルタリングされる、的な意味合いだと思います。