BLOGサブスレッドの日常

2022.02.16

djangorestframework で django-filter を使ってみた

torikai

鳥飼です。
django って QuerySet が結構便利で、基本的には別のパッケージで拡充する用事はあまり無いかなーと思うんですが、djangorestframework を使った時に django-filter に触れる機会があったので、かんたんな使い方を紹介したいと思います。

django-filter の何が嬉しいか

よくある検索機能を少ないコーディングで実装できるのが、django-filter の嬉しいところ。
クラスとして分離するので、他の機能から同じ検索処理を呼び出したい、という場合にも再利用がしやすいところも、良いところのひとつです。

準備

適当に model, viewset, serializer を用意。

# models.py
from django.db import models


class Author(models.Model):
    name = models.TextField('投稿者名')
    email = models.TextField('メールアドレス')

    def __str__(self):
        return self.name

class Article(models.Model):
    subject = models.TextField('題名')
    body = models.TextField('本文')
    author = models.ForeignKey(Author, verbose_name='投稿者', on_delete=models.CASCADE)
    created_at = models.DateTimeField('登録日時', auto_now_add=True)

    def __str__(self):
        return f'{self.subject} @{self.author}'
# views.py
from rest_framework import viewsets

from .filters import ArticleFilterSet
from .models import Article
from .serializers import ArticleSerializer


class ArticleViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Article.objects.prefetch_related('author')
    serializer_class = ArticleSerializer
# serializers.py
from rest_framework import serializers

from .models import Author, Article


class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = ('name', )


class ArticleSerializer(serializers.ModelSerializer):
    author = AuthorSerializer()

    class Meta:
        model = Article
        fields = ('subject', 'body', 'author')

django-filter を導入

何はともあれ django-filter をインストール。

pip install django-filter

settingsINSTALLED_APPSREST_FRAMEWORKdjango-filter の設定を追加します。

# settings.py
...
INSTALLED_APPS = [
...
    'django.contrib.staticfiles',
    'django_filters',  # 追加
    'rest_framework',
    'app.blog',
]
...

REST_FRAMEWORK = {
    ...
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',  # 追加
    ]
    ...
}
...

実際に使ってみる

ファイル名や配置場所は何でも良いんですが、今回は views.py などと同じ階層に filters.py を作って、その中にフィルタ設定を書いていきます。

# filters.py
from django_filters import rest_framework as filters
from .models import Article


class ArticleSearchFilterSet(filters.FilterSet):
    subject = filters.CharFilter(field_name='subject', lookup_expr='contains')
    body = filters.CharFilter(field_name='body', lookup_expr='contains')
    author_name = filters.CharFilter(field_name='author', method='filter_author_name')

    class Meta:
        model = Article
        fields = ('subject', 'body', 'author_name')

    def filter_author_name(self, queryset, field_name, value):
        _filters = {'author__name': value, 'author__email__contains': value}
        return queryset.filter(**_filters)

本文や題名は部分一致で検索させたいので、lookup_exprcontains を指定。
ちなみに、lookup_expr のデフォルト値は expr なので、field_name='body__contains' のようには指定できません。

また、もし個別のフィルタリング処理をさせたい場合は、author_name のように method='filter_author_name' と指定して、同名の関数の中に処理を書けばOKです。

viewset を調整

viewset には filter_class を追加して、に先程のクラス(ArticleFilterSet)を指定すれば🙆

# views.py
from .filters import ArticleSearchFilterSet
...
class ArticleViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Article.objects.prefetch_related('author')
    serializer_class = ArticleSearchFilterSet
    filter_class = ArticleFilterSet  # 追加

フィルタの処理をほぼ分離できるので、だいぶスッキリして良い感じですね!
また使う機会があれば使っていきたいと思えるパッケージでした。

この記事を書いた人

torikai