Django REST Framework/DRF 일반

[DRF] 공식 문서 - Filtering 정리

bluebamus 2024. 1. 18. 00:28

 1. Filtering

   1) 정의

      - REST 프레임워크의 일반적인 리스트 뷰의 기본 동작은 모델 매니저의 전체 쿼리셋을 반환하는 것이다. 대부분의 경우 API에서 쿼리셋에 의해 반환되는 항목들을 제한하고 싶을 것이다.

      - GenericAPIView를 상속하는 뷰의 쿼리셋을 필터링하는 가장 간단한 방법은 .get_queryset() 메서드를 오버라이드하는 것이다.

      - 이 메서드를 오버라이드하면 뷰가 반환하는 쿼리셋을 여러 가지 다른 방식으로 커스터마이즈할 수 있다.

 

   2) 현재 사용자에 대한 필터링(Filtering against the current user)

      - 요청을 하는 현재 인증된 사용자와 관련된 결과만 반환되도록 쿼리셋을 필터링하고 싶을 수 있다.

      - 이는 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):
        """
        This view should return a list of all the purchases
        for the currently authenticated user.
        """
        user = self.request.user
        return Purchase.objects.filter(purchaser=user)

 

   3) URL에 대한 필터링(Filtering against the URL)

      - 다른 스타일의 필터링은 URL의 일부에 기반하여 쿼리셋을 제한 방법이다.

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

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

 

      - URL의 사용자 이름 부분으로 필터링된 purchase 쿼리셋을 반환하는 뷰를 작성할 수 있다.

class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
        """
        This view should return a list of all the purchases for
        the user as determined by the username portion of the URL.
        """
        username = self.kwargs['username']
        return Purchase.objects.filter(purchaser__username=username)

 

   4) 쿼리 매개변수에 대한 필터링(Filtering against query parameters)

      - 초기 쿼리셋을 필터링하는 마지막 예시로, URL의 쿼리 매개변수에 기반하여 초기 쿼리셋을 결정하는 방법이 있다.
      - http://example.com/api/purchases?username=denvercoder9와 같은 URL을 처리하기 위해 .get_queryset()을 재정의하고, 사용자 이름 매개변수가 URL에 포함되 있다면 쿼리셋을 필터링할 수 있다.

class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
        """
        Optionally restricts the returned purchases to a given user,
        by filtering against a `username` query parameter in the URL.
        """
        queryset = Purchase.objects.all()
        username = self.request.query_params.get('username')
        if username is not None:
            queryset = queryset.filter(purchaser__username=username)
        return queryset

 

 2. 일반적인 필터링(Generic Filtering)

   - 기본 쿼리셋을 재정의할 수 있는 것뿐만 아니라, REST 프레임워크는 복잡한 검색과 필터를 쉽게 구성할 수 있도록 일반적인 필터링 백엔드를 지원한다.

   - 일반적인 필터들은 브라우저블 API와 관리자 API에서 HTML 컨트롤로 나타날 수도 있다.

 

   1) 필터 백엔드 설정(Setting filter backends)

      - 기본 필터 백엔드는 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]

 

   2) 필터링 및 오브젝트 조회(Filtering and object lookups)

      - 뷰에 필터 백엔드가 설정되어 있는 경우, 이는 목록 뷰를 필터링하는 데 사용되는 것뿐만 아니라, 단일 객체를 반환하기 위해 사용되는 쿼리셋을 필터링하는 데도 사용된다.

      - 예를 들어, 이전 예시에서 ID가 4675인 제품을 가지고 있다면, 다음 URL은 주어진 제품 인스턴스가 필터링 조건을 충족하는지 여부에 따라 해당 객체를 반환하거나 404 응답을 반환할 것이다.

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

 


from myapp.models import Product
from myapp.serializers import ProductSerializer

class ProductListView(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
    filterset_fields = ['category', 'price']
http://example.com/api/products/?category=clothing&price__lte=10.00

 

   3) 초기 QuerySet 오버라이딩(Overriding the initial queryset)

      - 개발자는 오버라이딩된 .get_queryset()와 일반적인 필터링 기능을 동시에 사용할 수 있고, 모든 기능들은 예상대로 잘 동작할 것이다. 예를 들어, 만약 'Product'가 'User'와 'purchase'라는 이름의 다대다 관계를 가지고 있다면, 아래와 같이 뷰를 작성하고자 할 수도 있다.

      - get_queryset 쿼리셋이 먼저 생성되고 이 쿼리셋에 대한 filterset_class가 필터링을 적용한다.

# models.py
purchase = models.ManyToManyField(User, related_name='purchase_set')


# views.py
from django_filters import rest_framework as filters

class ProductFilter(filters.FilterSet):
    max_price = filters.NumberFilter(field_name="price", lookup_expr='lte')

    class Meta:
        model = Product
        fields = ['category', 'max_price']

class PurchasedProductsList(generics.ListAPIView):
    """
    Return a list of all the products that the authenticated
    user has ever purchased, with optional filtering.
    """
    model = Product
    serializer_class = ProductSerializer
    filterset_class = ProductFilter

    def get_queryset(self):
        user = self.request.user
        return user.purchase_set.all()

 

 3. API Guide

   1) 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]

 

      - 만약 당신이 필요로 하는 것이 단순한 동등성에 기반한 필터링(equality-based filtering)이라면, 필터링을 원하는 필드들을 나열하여 뷰나 뷰셋에 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 클래스를 지정할 수 있다. django-filter 문서에서 FilterSets에 대해 더 자세히 알아볼 수 있다. 또한 DRF 통합에 대한 섹션을 읽는 것을 추천한다.

         - django-filter documentation : https://django-filter.readthedocs.io/en/latest/index.html

         - DRF integration : https://django-filter.readthedocs.io/en/latest/guide/rest_framework.html

 

   2) SearchFilter

      - SearchFilter 클래스는 단순한 단일 쿼리 파라미터 기반 검색을 지원하며, Django 관리자의 검색 기능에 기반을 두고 있다.

         - Django admin's search functionality :  https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields

      - 이 기능을 사용하게 되면, 탐색 가능한 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

 

      - 또한 lookup API의 더블 언더스코어 표기법을 사용하여 ForeignKey 또는 ManyToManyField에 대한 관련 조회를 수행할 수 있다.

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

 

      - JSONField와 HStoreField 필드의 경우 동일한 더블 언더스코어 표기법을 사용하여 데이터 구조 내의 중첩된 값에 기반한 필터링을 수행할 수 있다.

search_fields = ['data__breed', 'data__owner__other_pets__0__name']

 

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

 

      - 검색 동작은 search_fields의 필드 이름 앞에 다음 문자 중 하나를 추가함으로써 지정될 수 있다(이는 필드에 __<lookup>을 추가하는 것과 동일하다)

Prefix Lookup  
^ istartswith Starts-with search.
= iexact Exact matches.
$ iregex Regex search.
@ search Full-text search (Currently only supported Django's PostgreSQL backend).
None icontains Contains search (Default).

 

      - 예를 들면 다음과 같다.

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 문서를 참조하자.

         -  https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields

 

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

 

      1. 어떤 필드에 대해 정렬할 수 있는지 지정하기(Specifying which fields may be ordered against)

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

 

      2. 기본 정렬 지정하기(Specifying a default ordering)

         - 뷰에 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 속성은 문자열이거나 문자열의 리스트나 튜플이 될 수 있다.

 

 4. 사용자 정의 일반 필터링(Custom generic filtering)

   - 또한, 자신만의 일반 필터링 백엔드를 제공하거나 다른 개발자들이 사용할 수 있는 설치 가능한 앱을 작성할 수 있다.
   - 이를 위해 BaseFilterBackend를 오버라이드하고 .filter_queryset(self, request, queryset, view) 메소드를 오버라이드하면 된다. 이 메소드는 새로운 필터링된 쿼리셋을 반환해야 한다.
   - 클라이언트가 검색과 필터링을 할 수 있도록 허용하는 것 외에도, 일반 필터 백엔드는 주어진 요청이나 사용자에게 어떤 객체가 보여야 하는지 제한하는데 유용할 수 있다.

 

   1) Example

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

class IsOwnerFilterBackend(filters.BaseFilterBackend):
    """
    Filter that only allows users to see their own objects.
    """
    def filter_queryset(self, request, queryset, view):
        return queryset.filter(owner=request.user)

 

      - 뷰에서 get_queryset()을 오버라이드함으로써 동일한 동작을 구현할 수 있지만, 필터 백엔드를 사용하면 이 제한을 여러 뷰에 더 쉽게 추가하거나 전체 API에 적용할 수 있다.

 

   2) Customizing the interface

      - 일반 필터는 또한 탐색 가능한 API에서 인터페이스를 제공할 수 있다. 이를 위해 필터의 렌더링된 HTML 표현을 반환하는 to_html() 메소드를 구현해야 한다. 이 메소드는 다음과 같은 서명을 가지고 있어야 한다.

      - to_html(self, request, queryset, view)
      - 이 메소드는 렌더링된 HTML 문자열을 반환해야 한다.

 

 5. Third party packages

   1) Django REST 프레임워크 필터 패키지(Django REST framework filters package)

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

         - https://github.com/philipn/django-rest-framework-filters

 

   2) Django REST 프레임워크 전체 단어 검색 필터(Django REST framework full word search filter)

      - djangorestframework-word-filter는 텍스트에서 전체 단어를 검색하거나 정확하게 일치하는 filters.SearchFilter의 대안으로 개발되었다.

         - https://github.com/trollknurr/django-rest-framework-word-search-filter

 

 

   3) Django URL Filter

      - django-url-filter는 인간 친화적인 URL을 통해 데이터를 안전하게 필터링하는 방법을 제공한다. 이는 DRF serializer와 필드와 매우 유사하게 작동하는데, 이들은 중첩될 수 있다는 점에서 filtersets와 filters라고 불린다. 이는 관련 데이터를 쉽게 필터링하는 방법을 제공한다. 또한 이 라이브러리는 일반적인 목적으로 사용될 수 있으므로, Django QuerySets만이 아니라 다른 데이터 소스를 필터링하는데 사용될 수 있다.

         - https://github.com/miki725/django-url-filter

 

   4) drf-url-filters

      - drf-url-filter는 drf ModelViewSet의 Queryset에 필터를 깔끔하고 간단하며 구성 가능한 방식으로 적용하는 간단한 Django 앱이다. 또한 들어오는 쿼리 파라미터와 그 값에 대한 유효성 검사도 지원한다. 아름다운 파이썬 패키지인 Voluptuous는 들어오는 쿼리 파라미터에 대한 유효성 검사에 사용된다. Voluptuous의 가장 좋은 점은 쿼리 파라미터 요구 사항에 따라 자신만의 유효성 검사를 정의할 수 있다는 것이다.

         - https://github.com/manjitkumar/drf-url-filters

 

 - 공식 사이트 문서 : https://www.django-rest-framework.org/api-guide/filtering/#filtering

 

Filtering - Django REST framework

 

www.django-rest-framework.org