[DRF] Filtering

강민성·2024년 9월 27일

DRF API Guide

목록 보기
17/28

필터링(Filtering)

Manager가 제공하는 기본 QuerySet은 데이터베이스 테이블의 모든 객체를 설명합니다. 그러나 보통은 전체 객체 집합 중 일부만 선택해야 합니다.
Django 문서

REST 프레임워크의 제네릭 리스트 뷰의 기본 동작은 모델 매니저에 대한 전체 queryset을 반환하는 것입니다. 종종 API에서 queryset이 반환하는 항목을 제한하고 싶을 때가 있습니다.

GenericAPIView를 상속받은 뷰에서 queryset을 필터링하는 가장 간단한 방법은 .get_queryset() 메서드를 재정의하는 것입니다.

이 메서드를 재정의하면 뷰에서 반환하는 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):
        """
        이 뷰는 현재 인증된 사용자의 모든 구매 목록을 반환해야 합니다.
        """
        user = self.request.user
        return Purchase.objects.filter(purchaser=user)

URL에 대한 필터링

다른 필터링 방식으로는 URL의 일부를 기반으로 queryset을 제한하는 방법이 있습니다.

예를 들어, URL 설정에 다음과 같은 항목이 포함되어 있다면:

re_path('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),

URL의 username 부분을 기반으로 필터링된 구매 queryset을 반환하는 뷰를 작성할 수 있습니다:

class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
        """
        이 뷰는 URL의 username 부분에 따라 해당 사용자의 모든 구매 목록을 반환해야 합니다.
        """
        username = self.kwargs['username']
        return Purchase.objects.filter(purchaser__username=username)

쿼리 파라미터에 대한 필터링

초기 queryset을 필터링하는 또 다른 예시는 URL의 쿼리 파라미터를 기반으로 초기 queryset을 결정하는 것입니다.

.get_queryset()을 재정의하여 http://example.com/api/purchases?username=denvercoder9과 같은 URL을 처리하고, username 파라미터가 URL에 포함된 경우에만 queryset을 필터링할 수 있습니다:

class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
        """
        URL의 `username` 쿼리 파라미터를 기준으로 반환된 구매 목록을 특정 사용자로 제한할 수 있습니다.
        """
        queryset = Purchase.objects.all()
        username = self.request.query_params.get('username')
        if username is not None:
            queryset = queryset.filter(purchaser__username=username)
        return queryset

Generic 필터링

기본 queryset을 재정의할 수 있을 뿐만 아니라, REST 프레임워크는 복잡한 검색과 필터를 쉽게 구성할 수 있는 Generic 필터링 백엔드를 지원합니다.

Generic 필터는 탐색 가능한 API와 관리자 API에서 HTML 컨트롤로 표시될 수도 있습니다.

필터 백엔드 설정

Generic 필터 백엔드는 DEFAULT_FILTER_BACKENDS 설정을 사용하여 전역적으로 설정할 수 있습니다. 예를 들어:

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}

또한, GenericAPIView 기반 뷰를 사용하여 뷰 또는 뷰셋별로 필터 백엔드를 설정할 수도 있습니다.

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]

필터링과 객체 조회

뷰에 필터 백엔드가 설정된 경우, 리스트 뷰뿐만 아니라 단일 객체를 반환하는 queryset에도 필터가 적용된다는 점을 유의하세요.

예를 들어, 이전 예시와 제품 ID가 4675인 제품이 있을 때, 아래 URL은 해당 제품 인스턴스가 필터링 조건을 충족하는지에 따라 해당 객체를 반환하거나 404 응답을 반환합니다:

http://example.com/api/products/4675/?category=clothing&max_price=10.00

초기 queryset 재정의

.get_queryset()을 재정의하면서 동시에 일반 필터링을 사용할 수 있으며, 모든 것이 예상대로 작동합니다. 예를 들어, ProductUserpurchase라는 다대다 관계를 가지고 있다면, 아래와 같은 뷰를 작성할 수 있습니다:

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()

API 가이드

DjangoFilterBackend

django-filter 라이브러리는 REST 프레임워크에서 매우 사용자 정의 가능한 필터링을 지원하는 DjangoFilterBackend 클래스를 포함합니다.

DjangoFilterBackend를 사용하려면 먼저 django-filter를 설치해야 합니다.

pip install django-filter

그런 다음, django_filters를 Django의 INSTALLED_APPS에 추가합니다:

INSTALLED_APPS = [
    ...
    'django_filters',
    ...
]

이제 필터 백엔드를 설정에 추가하거나:

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}

혹은 개별 뷰(View)나 뷰셋(ViewSet)에 필터 백엔드를 추가해야 합니다.

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']

이렇게 하면 주어진 필드에 대해 자동으로 FilterSet 클래스가 생성되며, 아래와 같은 요청을 보낼 수 있습니다:

http://example.com/api/products?category=clothing&in_stock=True

더 복잡한 필터링이 필요하다면 뷰에서 사용할 FilterSet 클래스를 지정할 수 있습니다. FilterSet에 대한 자세한 내용은 django-filter 문서에서 확인할 수 있으며, DRF와의 통합에 대한 섹션도 읽는 것을 권장합니다.

SearchFilter

SearchFilter 클래스는 간단한 단일 쿼리 파라미터를 기반으로 한 검색을 지원하며, 이는 Django 관리자 페이지의 검색 기능을 기반으로 합니다.

사용 중일 때, 탐색 가능한 API는 SearchFilter 컨트롤을 포함합니다:

  • 검색 필터

SearchFilter 클래스는 뷰에 search_fields 속성이 설정된 경우에만 적용됩니다. search_fields 속성은 모델에서 텍스트 유형 필드(예: CharField 또는 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

또한, 외래 키(ForeignKey)나 다대다 관계(ManyToManyField)를 lookup API의 더블 언더스코어 표기법을 사용하여 관련된 조회를 수행할 수 있습니다:

search_fields = ['username', 'email', 'profile__profession']

JSONFieldHStoreField 필드의 경우, 동일한 더블 언더스코어 표기법을 사용하여 데이터 구조 내의 중첩된 값을 기반으로 필터링할 수 있습니다:

search_fields = ['data__breed', 'data__owner__other_pets__0__name']

기본적으로 검색은 대소문자를 구분하지 않고 부분 일치를 사용합니다. 검색 파라미터는 여러 검색어를 포함할 수 있으며, 이는 공백 또는 쉼표로 구분해야 합니다. 여러 검색어가 사용된 경우, 제공된 모든 검색어가 일치할 때에만 객체가 목록에 반환됩니다. 검색어는 공백이 있는 따옴표로 묶인 구문을 포함할 수 있으며, 각 구문은 단일 검색어로 간주됩니다.

검색 동작은 search_fields 내의 필드 이름 앞에 아래 문자를 접두어로 추가하여 지정할 수 있습니다. (이는 필드에 __<lookup>을 추가하는 것과 동일합니다):

접두어조회 유형설명
^istartswith시작 문자열 검색
=iexact정확한 일치 검색
$iregex정규식 검색
@search전체 텍스트 검색 (현재는 Django의 PostgreSQL 백엔드에서만 지원됨)
없음icontains포함 검색 (기본값)

예를 들어:

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().get_search_fields(view, request)

자세한 내용은 Django 문서를 참조하십시오.

OrderingFilter

OrderingFilter 클래스는 간단한 쿼리 파라미터를 통해 결과의 정렬을 제어하는 기능을 지원합니다.

  • 정렬 필터

기본적으로 쿼리 파라미터의 이름은 ordering이지만, ORDERING_PARAM 설정을 통해 이를 재정의할 수 있습니다.

예를 들어, 사용자 이름별로 사용자 목록을 정렬하려면:

http://example.com/api/users?ordering=username

필드 이름 앞에 '-'를 접두어로 추가하여 역순 정렬을 지정할 수도 있습니다:

http://example.com/api/users?ordering=-username

또한, 여러 정렬 기준을 지정할 수도 있습니다:

http://example.com/api/users?ordering=account,username

정렬할 수 있는 필드 지정하기

정렬 필터에서 API가 허용해야 할 필드를 명시적으로 지정하는 것이 좋습니다. 이는 ordering_fields 속성을 뷰에 설정함으로써 할 수 있습니다:

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['username', 'email']

이 방법을 통해 비밀번호 해시 필드나 다른 민감한 데이터와 같은 예기치 않은 데이터 누출을 방지할 수 있습니다.

뷰에 ordering_fields 속성을 지정하지 않으면 필터 클래스는 serializer_class 속성에 지정된 serializer에서 읽을 수 있는 모든 필드를 기본값으로 허용합니다.

뷰에서 사용하는 쿼리셋에 민감한 데이터가 포함되지 않는다고 확신할 수 있다면, 특별한 값 __all__을 사용하여 뷰에서 모델 필드나 쿼리셋 집계에 대한 정렬을 허용하도록 명시적으로 지정할 수도 있습니다.

class BookingsListView(generics.ListAPIView):
    queryset = Booking.objects.all()
    serializer_class = BookingSerializer
    filter_backends = [filters.OrderingFilter]
    ordering_fields = '__all__'

기본 정렬 지정하기

뷰에 ordering 속성을 설정하면 이를 기본 정렬로 사용합니다.

일반적으로는 초기 쿼리셋에서 order_by를 설정하여 이 동작을 제어하지만, 뷰에서 ordering 파라미터를 사용하는 것은 자동으로 템플릿에 컨텍스트로 전달될 수 있는 방식으로 정렬을 지정할 수 있게 해줍니다. 이를 통해 결과를 정렬하는 데 사용되는 열 머리글을 자동으로 다르게 렌더링할 수 있습니다.

class UserListView(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    filter_backends = [filters.OrderingFilter]
    ordering_fields = ['username', 'email']
    ordering = ['username']

ordering 속성은 문자열 또는 문자열의 리스트/튜플일 수 있습니다.

커스텀 Generic 필터링

자체적으로 Generic 필터링 백엔드를 제공하거나, 다른 개발자들이 사용할 수 있는 설치 가능한 앱을 작성할 수도 있습니다.

이렇게 하려면 BaseFilterBackend를 재정의하고 .filter_queryset(self, request, queryset, view) 메서드를 재정의하세요. 이 메서드는 새로운 필터링된 쿼리셋을 반환해야 합니다.

클라이언트가 검색 및 필터링을 수행할 수 있도록 할 뿐만 아니라, Generic 필터 백엔드는 특정 요청이나 사용자에게 표시되어야 할 객체를 제한하는 데도 유용할 수 있습니다.

예시

예를 들어, 사용자가 자신이 생성한 객체만 볼 수 있도록 제한해야 할 경우를 생각해볼 수 있습니다.

class IsOwnerFilterBackend(filters.BaseFilterBackend):
    """
    사용자가 자신의 객체만 볼 수 있도록 필터링합니다.
    """
    def filter_queryset(self, request, queryset, view):
        return queryset.filter(owner=request.user)

위와 같은 동작은 뷰에서 get_queryset()을 재정의하여도 동일하게 구현할 수 있지만, 필터 백엔드를 사용하면 여러 뷰에 더 쉽게 이 제한을 추가할 수 있고, 전체 API에서 이 제한을 적용할 수 있습니다.

커스텀 인터페이스

Generic 필터는 브라우저에서 사용할 수 있는 API에 인터페이스를 제공할 수도 있습니다. 이를 위해 to_html() 메서드를 구현해야 하며, 필터의 HTML 표현을 렌더링하여 반환해야 합니다. 이 메서드는 다음과 같은 시그니처를 가져야 합니다:

to_html(self, request, queryset, view)

이 메서드는 렌더링된 HTML 문자열을 반환해야 합니다.

외부 패키지

다음 외부 패키지는 추가적인 필터 구현을 지원합니다.

  • Django REST framework filters 패키지
    django-rest-framework-filters 패키지는 DjangoFilterBackend 클래스와 함께 작동하며, 관계를 통한 필터를 쉽게 생성하거나 주어진 필드에 대해 여러 필터 조회 유형을 생성할 수 있습니다.

  • Django REST framework full word search filter
    djangorestframework-word-filter는 텍스트에서 전체 단어를 검색하거나 정확한 일치를 검색하는 filters.SearchFilter의 대안으로 개발되었습니다.

  • Django URL Filter
    django-url-filter는 사람이 이해하기 쉬운 URL을 통해 데이터를 안전하게 필터링할 수 있는 방법을 제공합니다. 이 라이브러리는 필터셋과 필터라고 불리는 객체가 중첩될 수 있다는 점에서 DRF의 직렬화기(Serializer) 및 필드와 매우 유사하게 작동합니다. 이로 인해 관련 데이터를 쉽게 필터링할 수 있습니다. 또한 이 라이브러리는 일반 용도 라이브러리이므로 Django QuerySet뿐만 아니라 다른 데이터 소스를 필터링하는 데에도 사용할 수 있습니다.

  • drf-url-filters
    drf-url-filter는 DRF의 ModelViewSet의 Queryset에 필터를 적용하는 깔끔하고 간단하며 구성 가능한 방식의 Django 앱입니다. 또한 쿼리 파라미터와 그 값에 대한 유효성 검사를 지원합니다. 유효성 검사에는 세련된 파이썬 패키지인 Voluptuous가 사용되며, 이는 들어오는 쿼리 파라미터에 대한 유효성 검사를 수행합니다. Voluptuous의 가장 좋은 점은 쿼리 파라미터 요구사항에 따라 자체 유효성 검사를 정의할 수 있다는 것입니다.

Reference

DRF API Guide - Filtering

profile
Back-end Junior Developer

0개의 댓글