Django REST framework_GenericAPIView

nikevapormax·2022년 9월 14일
0

TIL

목록 보기
96/116

공식문서

GenericAPIView

정의

  • GenericAPIView는 REST framework의 APIView 클래스를 확장하여 standard list 및 detail views에 일반적으로 필요한 동작을 추가
  • GenericAPIView에 하나 혹은 그 이상의 mixin class와 결합하여 빌드

예시

  • 일반적으로 generic views를 사용할 때, 다음과 같이 view를 오버라이딩해 사용하게 되며 몇몇 class attribute를 세팅하게 된다.
from django.contrib.auth.models import User
from rest_framework.generics import ListCreateAPIView
from rest_framework.permissions import IsAdminUser

from myapp.serializers import UserSerializer


class UserList(ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [IsAdminUser]
  • 좀 더 복잡한 상황들을 위해서 다양한 메서드를 오버라이딩해 사용할 수도 있다.
from django.contrib.auth.models import User
from rest_framework.generics import ListCreateAPIView
from rest_framework.permissions import IsAdminUser

from myapp.serializers import UserSerializer


class UserList(ListCreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [IsAdminUser]

    def list(self, request):
        # Note the use of `get_queryset()` instead of `self.queryset`
        queryset = self.get_queryset()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)
  • 단순한 케이스를 다루는 경우라면, 아래와 같이 .as_view() 메서드를 활용해 URLconf에 아래와 같이 필요한 부분을 포함해 작성하면 된다.
path('users/', ListCreateAPIView.as_view(queryset=User.objects.all(), serializer_class=UserSerializer), name='user-list')

속성

하단의 속성들은 view의 일반적인 동작을 컨트롤하는데 사용된다.

기본세팅

queryset

  • 해당 view에서 objects를 반환하기 위해 사용한다.
  • 일반적으로, queryset을 세팅하거나 get_queryset() method를 오버라이딩해 사용해야 한다.
  • 만약 queryset을 사용하는 대신 get_queryset() method를 오버라이딩해 사용하게 된다면
    • queryset은 한 번만 불러와진다.
    • 모든 후속 requests에 해당 결과가 캐시되어 사용되게 된다.
      • 원 데이터에 접근하는 시간보다 캐시에 접근하는 시간이 작은 경우, 시간을 절약할 수 있음

serializer_class

  • validating과 input의 역직렬화에 사용되고, output을 직렬화하는데 사용된다.
  • 일반적으로, serializer_class를 세팅하거나 get_serializer_class() method를 오버라이딩해야 한다.

lookup_field

  • object의 개별 모델 인스턴스를 조회하는데 사용된다.
  • 디폴트값으로 pk를 사용한다.
  • hyperlinked APIs를 사용한다면, custom value 사용 시 API views와 serializer classes 모두 lookup field를 세팅해야 한다.

lookup_url_kwarg

  • 객체 조회에 사용되는 URL keyword argument이다.
  • URL conf는 해당 값에 대응하는 keyword argument를 포함해야 한다.
  • 해당 값을 설정하지 않는다면 디폴트값으로 lookup_field를 사용하게 된다.

pagination

pagination_class

  • 페이징 처리가 된 리스트 형태의 결과를 얻고자 할 때 사용해야 한다.
  • 'rest_framework.pagination.PageNumberPagination'DEFAULT_PAGINATION_CLASS 세팅의 디폴트값으로 사용하며, pagination_class와 동일한 값으로 사용된다.
  • pagination_class=None으로 세팅 시 해당 뷰에서는 페이징에 대한 결과를 확인할 수 없다.

Filtering

filter_backends

  • filter backend class들은 queryset들을 필터링하는데 사용된다.
  • DEFAULT_FILTER_BACKENDS 세팅의 같은 값으로 사용된다.

Methods

기본 메서드

get_queryset(self)

  • list view에서 queryset을 반환할 때 사용된다.
  • detail view의 lookup의 기본값으로 사용된다.
  • 기본적인 반환값은 queryset 속성으로 명세화된 queryset이다.
  • self.queryset에 직접 접근하기보다 get_queryset을 항상 사용해야 한다.
    • 결과가 한 번만 평가되고 후속 request들을 위해 캐싱된다.
def get_queryset(self):
    user = self.request.user
    return user.accounts.all()
  • 요청을 하는 사용자와 관련된 queryset 반환과 같은 동적 동작을 제공하기 위해 오버라이딩될 수 있다.
  • 만약 generic view가 orm 관계를 가질 경우, serializer_class 사용 시 n+1 problem을 야기할 수 있다.

get_object(self)

  • detail view에서 object instance를 반환할 때 사용한다.
  • 기본 queryset을 필터링하기 위해 lookup_field 파라미터를 사용하는 것이 기본값이다.
  • 하나 이상의 URL kwarg를 사용해 좀 더 복잡한 동작을 제공하기 위해 오버라이딩될 수 있다.
def get_object(self):
    queryset = self.get_queryset()
    filter = {}
    for field in self.multiple_lookup_fields:
        filter[field] = self.kwargs[field]

    obj = get_object_or_404(queryset, **filter)
    self.check_object_permissions(self.request, obj)
    return obj
  • 만약 API가 아무 object level permission들을 포함하고 있지 않을 시, 선택적으로 self.check_object_permissions을 포함시킬 수 있다.
  • 그리고 간단하게 get_object_or_404 lookup을 사용해 객체를 반환시킬 수 있다.

filter_queryset(self, queryset)

  • queryset을 주고 filter backends에서 사용중인 필터를 통해 새로운 queryset을 반환할 수 있다.
def filter_queryset(self, queryset):
    filter_backends = [CategoryFilter]

    if 'geo_route' in self.request.query_params:
        filter_backends = [GeoRouteFilter, CategoryFilter]
    elif 'geo_point' in self.request.query_params:
        filter_backends = [GeoPointFilter, CategoryFilter]

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

    return queryset

get_serializer_class(self)

  • serializer에 사용될 class를 반환한다.
  • serializer_class 속성을 반환하는 것을 기본값으로 한다.
  • 읽기와 쓰기 동작에 서로 다른 serializer를 사용하고 있거나 사용자의 타입에 따라 서로 다른 serializer들을 사용하고 있다면, 다양한 동작을 제공하기 위해 오버라이딩할 수 있다.
def get_serializer_class(self):
    if self.request.user.is_staff:
        return FullAccountSerializer
    return BasicAccountSerializer

save and deletion hooks

  • 아래의 메서드들은 mixin class들에 의해 제공되며, 객제의 저장과 삭제 행위를 위한 쉬운 오버라이딩을 제공한다.
  • 아래의 hook들은 request 안에 내재된 attribute들을 세팅할 때 부분적으로 유용하다.
    • 하지만 request data의 한 부분은 아니다.

perform_create(self, serializer)

  • 새로운 object instance를 저장할 때 CreateModelMixin을 사용한다.
  • request user 또는 URL kwarg를 기반으로 개체에 속성을 설정할 수 있다.
def perform_create(self, serializer):
    serializer.save(user=self.request.user)
  • ValidationError()를 사용해 추가적인 validation을 사용하는 데 사용할 수 있다. 해당 로직은 데이터베이스에 데이터를 저장하는 시점에 유용할 수 있다.
def perform_create(self, serializer):
    queryset = SignupRequest.objects.filter(user=self.request.user)
    if queryset.exists():
        raise ValidationError('You have already signed up')
    serializer.save(user=self.request.user)

perform_update(self, serializer)

  • 존재하는 object instance를 저장할 때 UpdateModelMixin을 사용한다.
def perform_update(self, serializer):
    instance = serializer.save()
    send_email_confirmation(user=self.request.user, modified=instance)
  • 확인 이메일을 보내거나 업데이트를 기록하는 것과 같이 objcet를 저장하기 전이나 후에 발생하는 동작을 추가하는 데 특히 유용하다.

perform_destroy(self, instance)

  • object instance를 삭제할 때 DestroyModelMixin을 사용한다.

Other methods

  • GenericAPIView를 사용하여 custom view를 작성하는 경우 호출해야 할 수도 있지만 일반적으로 다음 메소드를 오버라이딩할 필요가 없습니다.

get_serializer_context(self)

  • serializer에 제공해야 하는 추가 컨텍스트가 포함된 사전을 반환한다.
  • 기본적으로 request, view, format 키를 포함한다.

get_serailzer(self, instance=None, data=None, many=False, partial=False)

  • serialzer instance를 반환한다.

get_paginated_response(self, data)

  • 페이징된 Response object를 반환한다.

paginated_queryset(self, queryset)

  • 만약 필요하다면 queryset을 페이징한다.
  • page objcet를 반환하거나, 만약 pagination이 구성되지 않은 view라면 None 값을 반환한다.

filter_queryset(self, queryset)

  • queryset을 주고 filter backends에서 사용중인 필터를 통해 새로운 queryset을 반환할 수 있다.

Customizing the generic views

  • generic view들을 사용하면서 조금의 커스터마이징된 행위들을 추가할 수 있다.

Creating custom mixins

  • 예를 들어, URL conf에 포함되어 있는 여러 개의 필드들을 사용해 object들을 검색하는 기능이 필요하다면 아래와 같이 mixin class를 생성해 사용할 수 있다.
class MultipleFieldLookupMixin:
    """
    Apply this mixin to any view or viewset to get multiple field filtering
    based on a `lookup_fields` attribute, instead of the default single field filtering.
    """
    def get_object(self):
        queryset = self.get_queryset()             # Get the base queryset
        queryset = self.filter_queryset(queryset)  # Apply any filter backends
        filter = {}
        for field in self.lookup_fields:
            if self.kwargs[field]: # Ignore empty fields.
                filter[field] = self.kwargs[field]
        obj = get_object_or_404(queryset, **filter)  # Lookup the object
        self.check_object_permissions(self.request, obj)
        return obj
  • 위에서 생성한 minxin class를 아래와 같이 view 또는 viewser에 추가해 커스텀을 할 수 있다.
class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_fields = ['account', 'username']

Creating custom base classes

  • 여러 view에서 mixin을 사용하는 경우, 한 단계 더 나아가 프로젝트 전체에서 사용할 수 있는 자신만의 기본 view들의 세트를 만들 수 있다.
class BaseRetrieveView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
    pass

class BaseRetrieveUpdateDestroyView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView):
    pass
  • 위와 같이 새로운 커스텀 클래스를 생성하는 것은 커스텀된 행위가 전체 프로젝트 안에서 많이 사용되게 될 때 아주 좋은 옵션이 될 수 있다.
profile
https://github.com/nikevapormax

0개의 댓글