티스토리 뷰

검색 기능을 통해 사용자가 원하는 필드의 값으로

특정 모델객체를 찾을 수 있는 기능을 구현하려고 합니다.

그렇다면 Backend에서는 사용자가 입력한 값을 받아 값에 맞는 모델 객체를 찾아가는 과정이 필요합니다.

class ProductAPI(APIView, LargeResultsSetPagination):

    def get(self, request):
        product = get_object_or_404(Product, price=request.GET["price"], id=request.GET["id"])
        serializer = ProductSerializer(product)
        response = Response(serializer.data, status=status.HTTP_200_OK)
        return response
        

--------------------------------------------------------------------------------

GET /products/?id=9&price=17000

HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 9,
    "name": "교촌파채소이살살",
    "price": 17000,
    "expression": "새콤달콤한 소이소스와 담백한 살살치킨에 신선한 채소를 곁들인 촉촉하여 바삭한 맛"
}

간단하게 get_object_or_404함수나 모델.objects.filter를 사용하여 필터링 로직을 구현할 수 있지만

위 로직은 사용자가 price쿼리만 입력하고 id쿼리는 입력하지 않았을 때

키 에러가 발생하기 때문에(꼭 모든 쿼리를 다 받아야 함) 그에 따라 많은 조건 분기들이 필요하게 됩니다.

(Ex. [id]쿼리만 입력했을 때, [price]쿼리만 입력했을 때, [id, price]둘 다 입력했을 때)

 

만약 쿼리의 개수가 적다면 조건 분기에 대한 문제점이 없지만

쿼리의 개수가 많아지면 조건 분기 또한 엄청나게 많아지기 때문에 비효율적인 코드가 될 수 있습니다.

 

이러한 문제점들의 대한 해결책으로 DRF가 기본적으로 제공하는 Filtering클래스들이 있습니다.

오늘은 DRF제공하는 DjangoFilterBackend를 통하여 Filtering로직을 구현해 보겠습니다.

 

 

 


DRF Filtering

 

DRF가 제공하는 Filtering클래스들을 사용하려면

일단 먼저 패키지를 설치해야 합니다.

(venv) -> pip3 install django-filter

설치를 완료하고 settings.py INSTALLED_APPS부분에도 추가했다면 준비완료입니다.

먼저 GenericAPIView로 Filtering을 구현해볼텐데

APIView가 아닌 GenericAPIView나 Viewset을 상속하면 Filtering에 대한 구현이 간단합니다.

from rest_framework import filters

class ProductList(generics.ListAPIView):
    queryset = Product.objects
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['id', 'price']


or


class ProductList(generics.ListAPIView):
    queryset = Product.objects
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = ProductFilter

filter_backends값으로 자신이 사용할 Filtering Class를 입력하고(DRF가 제공하는)

filterset_fields값으로 필터링할 모델의 필드들을 입력하거나

filterset_class값으로 자신이 만든 filter클래스를 입력하기만 하면 됩니다.

GET /productss/?id=9
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "next": null,
    "previous": null,
    "results": [
        {
            "id": 9,
            "name": "교촌파채소이살살",
            "price": 17000,
            "expression": "새콤달콤한 소이소스와 담백한 살살치킨에 신선한 채소를 곁들인 촉촉하여 바삭한 맛"
        }
    ]
}

이전과는 다르게 쿼리로 id값만 전달해도 잘 동작하는 걸 확인할 수 있습니다.

 

고작 패키지 하나 설치하고 코드 두 줄 작성했을 뿐인데 필터링이 잘 동작합니다.

이러한 마법같은 부분들에 대해 조금 파헤쳐 본다면

class ListAPIView(mixins.ListModelMixin,
                  GenericAPIView):
    """
    Concrete view for listing a queryset.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

GenericAPIView를 상속한 클래스들은 기본적으로 self.filter_queryset(self.get_queryset())과정을 거치게 됩니다.

해당 메서드들은 GenericAPIView에 구현되어 있는데 먼저 self.filter_queryset함수 파라미터의 값으로 전달되는

self.get_queryset()메서드부터 봐보

    def get_queryset(self):
        """
        Get the list of items for this view.
        This must be an iterable, and may be a queryset.
        Defaults to using `self.queryset`.
        This method should always be used rather than accessing `self.queryset`
        directly, as `self.queryset` gets evaluated only once, and those results
        are cached for all subsequent requests.
        You may want to override this if you need to provide different
        querysets depending on the incoming request.
        (Eg. return a list of items that is specific to the user)
        """
        assert self.queryset is not None, (
            "'%s' should either include a `queryset` attribute, "
            "or override the `get_queryset()` method."
            % self.__class__.__name__
        )

        queryset = self.queryset
        if isinstance(queryset, QuerySet):
            # Ensure queryset is re-evaluated on each request.
            queryset = queryset.all()
        return queryset

가장 먼저 self.queryset을 통하여 해당 View의 쿼리셋값이 None인지 확인합니다.

(None이면 AssertionError가 발생)

참고로 GenericAPIView에서는 self.queryset기본값이 None으로 설정되어 있기 때문에

해당 뷰에서 사용할 쿼리셋 클래스로 바꿔줘야 합니다.

class ProductList(generics.ListAPIView):
    queryset = Product.objects
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['id', 'price']

그렇기 때문에 위에서 ProductList뷰를 구현할 때

queryset값으로 해당 모델.objects를 사용하여 사용할 모델의 쿼리셋값으로 초기화해줬던 것입니다.

 

그리고 이제 GenericAPIView의 filter_queryset함수를 보면

def filter_queryset(self, queryset):
    """
    Given a queryset, filter it with whichever filter backend is in use.
    You are unlikely to want to override this method, although you may need
    to call it either from a list view, or from a custom `get_object`
    method if you want to apply the configured filtering backend to the
    default queryset.
    """
    for backend in list(self.filter_backends):
        queryset = backend().filter_queryset(self.request, queryset, self)
    return queryset

self.filter_backends값을 반복하게 되는데

filter_backends값은 GenericAPIView에서 기본값으로

settings.py에서 DEFAULT_FILTER_BACKENDS값을 가져오게 설정되어 있습니다.

(Pagination과 동일합니다.)

filter_backends = api_settings.DEFAULT_FILTER_BACKENDS

그렇기 때문에 settings.py에 해당 코드를 입력하면

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

GenericAPIView를 상속하는 모든 뷰 클래스는 결론적으로 filter_backends값으로

DjangoFilterBackend클래스를 가지게 됩니다.

 

그리고 self.filter_backends를 리스트형태로 값을 반복하기 때문에

여러 filtering클래스를 적용할 수 있습니다.

class ProductList(generics.ListAPIView):
    queryset = Product.objects
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend, OrderingFilter]
    filterset_fields = ['id', 'price']
    ordering_fields = ['id']


-------------------------------------------------------------------
GET /productss/?price=20000&ordering=-id


HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

[
    {
        "id": 10,
        "name": "교촌허니콤보",
        "price": 20000,
        "expression": "달콤한 허니소스와 바삭한 날개, 봉, 다리가 만나 촉촉함과 달콤함을 더욱 느낄 수 있는 부위별 치킨"
    },
    {
        "id": 8,
        "name": "교촌 레드순살(R)",
        "price": 20000,
        "expression": "부드럽고 바삭한 정육 순살에 청양 홍고추의 매콤함이 맛있게 어우러진 순살치킨 (정육)"
    },
    {
        "id": 5,
        "name": "크런치 순살크래커",
        "price": 20000,
        "expression": "황금올리브 닭다리살에 오레가노 풍미와 감칠맛을 부여하고, 빵가루 크럼을 입혀 바삭하게 튀겨낸 크래커 형태의 순살 치킨"
    },
    {
        "id": 2,
        "name": "황금올리브치킨",
        "price": 20000,
        "expression": "황금빛 파우더의 바삭함과 육즙 가득 퍼지는 부드러운 속살이 환상적!"
    }
]

위 예시는 DjangoFilterBackend외에도 OrderingFilter(데이터 정렬)도 사용하여

price값이 20000원인 데이터에 -id로 정렬한 뒤(id값으로 내림차순)Response된 데이터를 확인할 수 있습니다.

 

다음 코드를 봐보면

for backend in list(self.filter_backends):
    queryset = backend().filter_queryset(self.request, queryset, self)
return queryset

queryset = backend().filter_queryset(self,request, queryset, self)

해당 backend클래스의 객체를 생성하면서 해당 클래스의 filter_querset메서드를 사용합니다.

(Ex. DjangoFilterBackend().filter_queryset(...))

def filter_queryset(self, request, queryset, view):
    filterset = self.get_filterset(request, queryset, view)
    if filterset is None:
        return queryset

    if not filterset.is_valid() and self.raise_exception:
        raise utils.translate_validation(filterset.errors)
    return filterset.qs

DjangoFilterBackend의 filter_queryset메서드는 일단 먼저 get_filterset메서드를 호출하는데

def get_filterset(self, request, queryset, view):
    filterset_class = self.get_filterset_class(view, queryset)
    if filterset_class is None:
        return None

    kwargs = self.get_filterset_kwargs(request, queryset, view)
    return filterset_class(**kwargs)

해당 함수에서는 get_filterset_class를 통해서 filterset_class값을 가지고

get_filterset_kwargs를 통해 filterset_class의 객체를 생성할 때 보낼 인자들을 설정합니다.

 

일단 먼저 get_filterset_class함수부터 보면

def get_filterset_class(self, view, queryset=None):
    """
    Return the `FilterSet` class used to filter the queryset.
    """
    filterset_class = getattr(view, "filterset_class", None)
    filterset_fields = getattr(view, "filterset_fields", None)

    if filterset_class:
        filterset_model = filterset_class._meta.model

        # FilterSets do not need to specify a Meta class
        if filterset_model and queryset is not None:
            assert issubclass(
                queryset.model, filterset_model
            ), "FilterSet model %s does not match queryset model %s" % (
                filterset_model,
                queryset.model,
            )

        return filterset_class

    if filterset_fields and queryset is not None:
        MetaBase = getattr(self.filterset_base, "Meta", object)

        class AutoFilterSet(self.filterset_base):
            class Meta(MetaBase):
                model = queryset.model
                fields = filterset_fields

        return AutoFilterSet

    return None

getattr을 통해 해당 객체의 filterset_class속성값과 filterset_fields속성값을 가져옵니다.

from rest_framework import filters

class ProductList(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['id', 'price']


or


class ProductList(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = ProductFilter

필자가 처음에 filterset_fieldsfilterset_class 둘 중 하나만 입력하면 Filtering이 적용된다고 했는데

결론적으로 get_filterset_class함수에서 해당값을 가져오게 됩니다.

if filterset_class:
    filterset_model = filterset_class._meta.model

    # FilterSets do not need to specify a Meta class
    if filterset_model and queryset is not None:
        assert issubclass(
            queryset.model, filterset_model
        ), "FilterSet model %s does not match queryset model %s" % (
            filterset_model,
            queryset.model,
        )

    return filterset_class

if filterset_fields and queryset is not None:
    MetaBase = getattr(self.filterset_base, "Meta", object)

    class AutoFilterSet(self.filterset_base):
        class Meta(MetaBase):
            model = queryset.model
            fields = filterset_fields

    return AutoFilterSet

return None

사실상 filterset_fieldsfilterset_class사이에 큰 차이는 없습니다.

filterset_class가 더 우선적이고 위 코드에서 보면 알 수 있듯이 filterset_fields를 사용하여도

결론적으로는 클래스를 동적으로 만들어 적용하는 방식입니다.

(사용자가 만든 filterset_class[보통 FilterSet클래스]나 filterset_fields방식을 통해 동적으로 만드는 AutoFilterSet클래스는

똑같이 FilterSet클래스를 사용하기 때문에 BaseFilterSet클래스를 상속받습니다.)

class DjangoFilterBackend:
    filterset_base = filterset.FilterSet

filterset_class부분에 있는 assert부분은 해당 FilterSet클래스에서 사용하는 모델과

쿼리에 해당하는 모델이 동일한지 확인하는 과정입니다.

def get_filterset(self, request, queryset, view):
    filterset_class = self.get_filterset_class(view, queryset)
    if filterset_class is None:
        return None

    kwargs = self.get_filterset_kwargs(request, queryset, view)
    return filterset_class(**kwargs)

다음으로 kwargs부분인 self.get_filterset_kwargs함수를 봐보겠습니다.

def get_filterset_kwargs(self, request, queryset, view):
    return {
        "data": request.query_params,
        "queryset": queryset,
        "request": request,
    }

해당 함수는 간단합니다.

사실 변수명에서도 어떤 역할인지 유추할 수 있듯이

해당 함수는 Filtering에 사용할 데이터를 key, value값으로 리턴하는 형식입니다.

(**kwargs, filterset_class의 객체를 생성할 때 보낼 데이터들 [__init__])

 

이제 마지막 과정인 filterset_class(**kwargs) --> 사용자가 설정한 filterset_class(**kwargs) or AutoFilterSet(**kwargs)

결국은 사용자가 만든 filterset_classfilterset_fields는 FilterSet클래스를 통해 만들기 때문에

FilterSet클래스 소스코드를 봐보겠습니다.

class FilterSet(BaseFilterSet, metaclass=FilterSetMetaclass):
    pass

필자가 위에서도 설명했듯이

FilterSet방식(filterset_class)도 결국은 BaseFilterSet을 상속하기 때문에

사실상 핵심은 BaseFilterSet클래스에 있습니다.

class BaseFilterSet:
    FILTER_DEFAULTS = FILTER_FOR_DBFIELD_DEFAULTS

    def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
        if queryset is None:
            queryset = self._meta.model._default_manager.all()
        model = queryset.model

        self.is_bound = data is not None
        self.data = data or {}
        self.queryset = queryset
        self.request = request
        self.form_prefix = prefix

        self.filters = copy.deepcopy(self.base_filters)

        # propagate the model and filterset to the filters
        for filter_ in self.filters.values():
            filter_.model = model
            filter_.parent =

filterset_class(**kwargs) --> FilterSet(**kwargs) --> BaseFilterSet(**kwargs)

사실상 메타클래스를 제외하고는 BaseFilterSet객체를 생성하여 **kwargs값을 통해

해당 객체의 어트리뷰트 값을 설정합니다.

def filter_queryset(self, request, queryset, view):
    filterset = self.get_filterset(request, queryset, view)
    if filterset is None:
        return queryset

    if not filterset.is_valid() and self.raise_exception:
        raise utils.translate_validation(filterset.errors)
    return filterset.qs

DjangoFilterBackend의 filter_queryset의 리턴 부분은

filterset.qs로 해당 객체의 qs속성값을 리턴하는데

결국은 BaseFilterSet클래스의 @property데코레이터 getter를 통해 qs프로퍼티에 접근할 수 있습니다.

@property
def qs(self):
    if not hasattr(self, "_qs"):
        qs = self.queryset.all()
        if self.is_bound:
            # ensure form validation before filtering
            self.errors
            qs = self.filter_queryset(qs)
        self._qs = qs
    return self._qs

qs에는 self.filter_queryset()함수가 있는 걸 확인할 수 있습니다.

def filter_queryset(self, queryset):
    """
    Filter the queryset with the underlying form's `cleaned_data`. You must
    call `is_valid()` or `errors` before calling this method.
    This method should be overridden if additional filtering needs to be
    applied to the queryset before it is cached.
    """
    for name, value in self.form.cleaned_data.items():
        queryset = self.filters[name].filter(queryset, value)
        assert isinstance(
            queryset, models.QuerySet
        ), "Expected '%s.%s' to return a QuerySet, but got a %s instead." % (
            type(self).__name__,
            name,
            type(queryset).__name__,
        )
    return queryset

결국은 BaseFilterSet클래스의 filter_queryset을 통해 실질적인 Filtering하는 과정을 가지게 됩니다.

 

이러한 복잡한 과정을 통하기 때문에 우리가 위에서 봤던 것처럼

GenericAPIView나 ViewSet을 사용하는 클래스에서는

from rest_framework import filters

class ProductList(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['id', 'price']


or


class ProductList(generics.ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = ProductFilter

이렇게 단 세줄의 코드로 Filtering을 구현할 수 있습니다.

(serializer는 필수지만 Filtering과는 관련이 없기 때문에 제외했습니다.)

 

그렇다면 APIView에서는 DRF Filtering클래스를 사용하여 Filtering을 구현하려면

어떻게 해야 할까요?

 

 

 


APIView에서 DjangoFilterBackend사용해 보기

 

위에서 봤던 소스코드들을 생각하면 APIView에서 구현하는 방법도 간단합니다.

APIView는 이때까지 봤던 GenericAPIView와는 다르게 Filtering과 관련된 코드는 없습니다.

그렇기 때문에 GenericAPIVeiw에서 사용하는 Filtering관련된 코드들만 추가하면 똑같이

APIView에서도 동작하게 만들 수 있습니다.

class ListAPIView(mixins.ListModelMixin,
                  GenericAPIView):
    """
    Concrete view for listing a queryset.
    """
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

class ListModelMixin:
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

 

처음에 필자가 GenericAPIView를 상속한 ListAPIView에서는

queryset = self.filter_queryset(self.get_queryset())이라는 코드 한 줄로

이때까지 우리가 봤던 과정을 거치면서 필터링한다는 걸 알 수 있었습니다.

 

그렇기 때문에 우리도 이런 코드를 APIView에 추가하기만 하면 됩니다.

class ProductList(CustomAPIView, LargeResultsSetPagination):

  def get(self, request):
    queryset = self.filter_queryset(self.get_queryset())
    result_page = self.paginate_queryset(queryset, request)
    if result_page is not None:
        serializer = ProductSerializer(result_page, many=True, context={'request':request})
        return self.get_paginated_response(serializer.data)
    serializer = ProductSerializer(queryset, many=True)
    response = Response(serializer.data, status=status.HTTP_200_OK)
    return response

하지만 생각해보면 기존 APIView에서는 filter_queryset, get_queryset이라는 메서드는 존재하지 않기 때문에

동작하지 않습니다.

 

그렇기 때문에 APIView를 커스터마이징하여 filter_querset, get_queryset메서드를 GenericAPIView에서

가져오기만 하면 됩니다.

class CustomAPIView(APIView):
  filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
  queryset = Product.objects
  
  def get_queryset(self):
    """
    Get the list of items for this view.
    This must be an iterable, and may be a queryset.
    Defaults to using `self.queryset`.
    This method should always be used rather than accessing `self.queryset`
    directly, as `self.queryset` gets evaluated only once, and those results
    are cached for all subsequent requests.
    You may want to override this if you need to provide different
    querysets depending on the incoming request.
    (Eg. return a list of items that is specific to the user)
    """
    assert self.queryset is not None, (
        "'%s' should either include a `queryset` attribute, "
        "or override the `get_queryset()` method."
        % self.__class__.__name__
    )

    queryset = self.queryset
    if isinstance(queryset, QuerySet):
        # Ensure queryset is re-evaluated on each request.
        queryset = queryset.all()
    return queryset
  
  def filter_queryset(self, queryset):
    """
    Given a queryset, filter it with whichever filter backend is in use.
    You are unlikely to want to override this method, although you may need
    to call it either from a list view, or from a custom `get_object`
    method if you want to apply the configured filtering backend to the
    default queryset.
    """
    for backend in list(self.filter_backends):
        queryset = backend().filter_queryset(self.request, queryset, self)
    return queryset

CustomAPIView클래스를 만들어 기존 APIView를 상속받고

get_queryset과 filter_queryset을 그대로 가져와 기존 APIView클래스를 커스터마이징 했습니다.

(사실상 0.3GenericAPIView라고 볼 수 있습니다.)

 

그리고 해당 클래스의 어트리뷰트 값으로

filter_backends와 queryset값을 설정해 줬습니다.

(get_queryset, filter_queryset메서드를 사용하려면 필요한 값들)

 

이제 해당 CustomAPIView를 상속받아

class ProductList(CustomAPIView, LargeResultsSetPagination):
  filter_backends = (DjangoFilterBackend,)
  filterset_class = ProductFilter
  
  def get(self, request):
    queryset = self.filter_queryset(self.get_queryset())
    result_page = self.paginate_queryset(queryset, request)
    if result_page is not None:
        serializer = ProductSerializer(result_page, many=True, context={'request':request})
        return self.get_paginated_response(serializer.data)
    serializer = ProductSerializer(queryset, many=True)
    response = Response(serializer.data, status=status.HTTP_200_OK)
    return response

우리가 GenericAPIView에서 했듯이 똑같은 과정을 거치면 됩니다.

filter_backends값 설정(필수 x, settings.py에 값이 있다면),

filterset_class or filterset_fields값 초기화

 

이제 테스트해보면

GET /productss/?price=24000

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

[
    {
        "id": 1,
        "name": "자메이카 소떡만나치킨",
        "price": 24000,
        "expression": "BBQ자메이카 저크소스와 신선육, 소떡소떡을 조합하여 풍미를 올린 전국민이 최애하는 바베큐치킨"
    }
]

똑같이 잘 동작하는 걸 확인할 수 있습니다.

 

사실상 필자가 한 APIView커스터마이징은 GenericAPIView와 똑같이 동작하게 만들었을 뿐입니다.

GenericAPIView는 APIView를 상속받아 기능들을 더 추가한 것인데

필자의 CustomAPIView는 해당 기능들을 다 추가한 게 아닌 일부를 추가한 클래스에 불과합니다.

 

이럴 거면 그냥 GenericAPIView나 ViewSet을 통해 구현하면 되는 거 아닐까?

 

사실 GenericAPIView나 ViewSet을 통해 구현하면 코드도 짧고 사용방법만 알면 되지만

필자가 프로젝트를 진행하면서 느꼈던 부분이

사용하는 방법만 아는 상태면 무언가 나의 코드에 문제점이 발생했을 때 해결법에 대해 찾기 어려웠습니다.

 

이러한 부분들은 결국은 내가 사용방법만 알지 내부코드에 대한 동작 이해도가 부족해서 발생한 문제라고 생각하기 때문에

이런 Filtering이 어떤 과정을 통해 동작하는지 직접 소스코드를 통해 알아봤습니다.

(덤으로 잘 짜여진 객체지향코드에 대해서도 조금 알아간 것 같습니다.)

 

만약 실력이 출중하신 분들이면 Filtering을 그대로 GenericAPIView에서 가져오는 게 아닌

사용자가 직접 설정해서 구현할 수 있을 거라고 생각합니다.

(하지만 기존에 있는 것도 충분하다고 생각합니다.)

 

DRF Filtering API Guide

 

Filtering - Django REST framework

 

www.django-rest-framework.org

GenericAPIView

 

GitHub - encode/django-rest-framework: Web APIs for Django. 🎸

Web APIs for Django. 🎸. Contribute to encode/django-rest-framework development by creating an account on GitHub.

github.com

DjangoFilterBackend

 

GitHub - carltongibson/django-filter: A generic system for filtering Django QuerySets based on user selections

A generic system for filtering Django QuerySets based on user selections - GitHub - carltongibson/django-filter: A generic system for filtering Django QuerySets based on user selections

github.com

FilterSet

 

GitHub - carltongibson/django-filter: A generic system for filtering Django QuerySets based on user selections

A generic system for filtering Django QuerySets based on user selections - GitHub - carltongibson/django-filter: A generic system for filtering Django QuerySets based on user selections

github.com

 

댓글
공지사항