[DRF] Generic Views

강민성·2024년 9월 18일

DRF API Guide

목록 보기
4/28

제너릭 뷰(Generic views)

Django의 제너릭 뷰는 일반적인 사용 패턴을 위한 단축키로 개발되었습니다. 이 뷰는 뷰 개발에서 자주 사용되는 특정 관용구와 패턴을 추상화하여, 데이터를 위한 일반적인 뷰를 반복하지 않고도 빠르게 작성할 수 있도록 돕습니다.
— Django Documentation

클래스 기반 뷰의 주요 이점 중 하나는 재사용 가능한 동작을 조합할 수 있다는 점입니다. REST 프레임워크는 이러한 점을 활용하여 자주 사용되는 패턴을 제공하는 여러 사전 구축된 뷰를 제공합니다.

REST 프레임워크가 제공하는 제너릭 뷰는 데이터베이스 모델과 밀접하게 연결된 API 뷰를 빠르게 구축할 수 있도록 해줍니다.

제너릭 뷰가 API의 요구를 충족시키지 못할 경우, 일반 APIView 클래스를 사용하거나, 제너릭 뷰에서 사용된 믹스인(mixin)과 기본 클래스를 재사용하여 자체적으로 재사용 가능한 제너릭 뷰 세트를 구성할 수 있습니다.


예시
일반적으로 제너릭 뷰를 사용할 때, 뷰를 오버라이드하고 여러 클래스 속성을 설정합니다.

from django.contrib.auth.models import User
from myapp.serializers import UserSerializer
from rest_framework import generics
from rest_framework.permissions import IsAdminUser

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

보다 복잡한 경우, 뷰 클래스의 다양한 메서드를 오버라이드할 수도 있습니다. 예를 들어:

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

    def list(self, request):
        # `self.queryset` 대신 `get_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')

API 참조

GenericAPIView
이 클래스는 REST 프레임워크의 APIView 클래스를 확장하며, 표준 목록 및 상세 뷰에 필요한 일반적인 동작을 추가합니다.

제공되는 구체적인 제너릭 뷰 각각은 GenericAPIView를 하나 이상의 믹스인 클래스와 결합하여 구성됩니다.


속성
기본 설정:

다음 속성들은 기본적인 뷰 동작을 제어합니다.

  • queryset: 이 뷰에서 객체를 반환하는 데 사용되는 쿼리셋. 일반적으로 이 속성을 설정하거나, get_queryset() 메서드를 오버라이드해야 합니다. 뷰 메서드를 오버라이드하는 경우, self.queryset에 직접 접근하는 대신 get_queryset()을 호출하는 것이 중요합니다. queryset은 한 번만 평가되고, 그 결과는 이후의 모든 요청에 대해 캐시됩니다.
  • serializer_class: 입력값을 검증하고 역직렬화(deserialize)하며, 출력값을 직렬화(serialize)하는 데 사용되는 직렬화 클래스. 일반적으로 이 속성을 설정하거나, get_serializer_class() 메서드를 오버라이드해야 합니다.
  • lookup_field: 개별 모델 인스턴스를 조회할 때 사용되는 모델 필드. 기본값은 'pk'입니다. 하이퍼링크 API를 사용하는 경우, 사용자 정의 값을 사용하려면 API 뷰와 직렬화 클래스 모두에서 lookup_field를 설정해야 합니다.
  • lookup_url_kwarg: 객체 조회에 사용되는 URL 키워드 인수. URLconf는 이 값에 해당하는 키워드 인수를 포함해야 합니다. 설정되지 않으면 lookup_field와 동일한 값이 기본값으로 사용됩니다.

페이징:

다음 속성들은 목록 뷰에서 페이징을 제어하는 데 사용됩니다.

  • pagination_class: 목록 결과를 페이징할 때 사용되는 페이징 클래스. 기본값은 'rest_framework.pagination.PageNumberPagination'입니다. pagination_class=None으로 설정하면 이 뷰에서 페이징이 비활성화됩니다.

필터링:

  • filter_backends: 쿼리셋을 필터링하는 데 사용되는 필터 백엔드 클래스 목록. 기본값은 DEFAULT_FILTER_BACKENDS 설정 값과 동일합니다.

메서드

기본 메서드

get_queryset(self)
리스트 뷰에 사용될 쿼리셋을 반환하며, 상세 뷰에서 조회 시 사용될 기본 쿼리셋으로도 사용됩니다. 기본적으로 queryset 속성에 지정된 쿼리셋을 반환합니다.

이 메서드는 self.queryset에 직접 접근하는 대신 항상 사용해야 합니다. self.queryset은 한 번만 평가되고 이후 모든 요청에 대해 그 결과가 캐시되기 때문입니다.

요청하는 사용자에 따라 동적인 동작을 제공하기 위해 이 메서드를 재정의할 수 있습니다.

예:

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

참고: Generic View에서 사용하는 serializer_class가 ORM 관계를 맺고 있는 경우, n+1 문제를 야기할 수 있습니다. 이 메서드에서 select_relatedprefetch_related를 사용하여 쿼리셋을 최적화할 수 있습니다. n+1 문제와 관련된 자세한 내용은 Django 문서를 참고하세요.


get_object(self)
상세 뷰에 사용될 객체 인스턴스를 반환합니다. 기본적으로 lookup_field 매개변수를 사용하여 기본 쿼리셋을 필터링합니다.

여러 URL 매개변수를 기반으로 한 복잡한 동작을 제공하기 위해 재정의할 수 있습니다.

예:

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에 객체 수준의 권한이 포함되어 있지 않은 경우, self.check_object_permissions를 제외하고 get_object_or_404 조회에서 반환된 객체를 단순히 반환할 수 있습니다.


filter_queryset(self, 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 속성을 반환합니다.

읽기와 쓰기 작업에 대해 다른 직렬화기를 사용하거나, 사용자 유형에 따라 다른 직렬화기를 제공하는 등 동적 동작을 제공하기 위해 재정의할 수 있습니다.

예:

def get_serializer_class(self):
    if self.request.user.is_staff:
        return FullAccountSerializer
    return BasicAccountSerializer

저장 및 삭제 후크:

다음 메서드는 믹스인 클래스에 의해 제공되며, 객체 저장 또는 삭제 동작을 쉽게 재정의할 수 있습니다.

  • perform_create(self, serializer): 새 객체 인스턴스를 저장할 때 호출됩니다.
  • perform_update(self, serializer): 기존 객체 인스턴스를 저장할 때 호출됩니다.
  • perform_destroy(self, instance): 객체 인스턴스를 삭제할 때 호출됩니다.

이러한 후크는 요청 데이터의 일부가 아닌 요청에서 암묵적으로 설정된 속성을 설정할 때 유용합니다. 예를 들어, 요청 사용자나 URL의 키워드 인수에 따라 객체의 속성을 설정할 수 있습니다.

def perform_create(self, serializer):
    serializer.save(user=self.request.user)

이러한 재정의 지점은 또한 객체를 저장하기 전이나 후에 동작을 추가할 때 유용합니다. 예를 들어, 확인 이메일을 발송하거나 업데이트를 기록할 수 있습니다.

def perform_update(self, serializer):
    instance = serializer.save()
    send_email_confirmation(user=self.request.user, modified=instance)

또한 ValidationError()를 발생시켜 추가적인 유효성 검사를 제공할 수 있습니다. 예를 들어, 데이터베이스 저장 시점에서 유효성 검사 로직이 필요한 경우 유용할 수 있습니다.

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)

기타 메서드

일반적으로 다음 메서드를 재정의할 필요는 없지만, GenericAPIView를 사용해 커스텀 뷰를 작성하는 경우 이 메서드를 호출해야 할 수 있습니다.

  • get_serializer_context(self): 직렬화기에 제공될 추가 컨텍스트를 포함하는 사전을 반환합니다. 기본적으로 'request', 'view' 및 'format' 키를 포함합니다.
  • get_serializer(self, instance=None, data=None, many=False, partial=False): serializer 인스턴스를 반환합니다.
  • get_paginated_response(self, data): 페이징 처리된 응답(Response) 객체를 반환합니다.
  • paginate_queryset(self, queryset): 필요한 경우 쿼리셋을 페이징 처리합니다. 페이지 객체를 반환하거나, 페이징이 이 뷰에서 구성되지 않은 경우 None을 반환합니다.
  • filter_queryset(self, queryset): 주어진 쿼리셋을 필터링하여 새로운 쿼리셋을 반환합니다.

믹스인(Mixins)

믹스인 클래스는 기본적인 뷰 동작을 제공하는 액션들을 포함하고 있습니다. 이 믹스인 클래스들은 직접 핸들러 메서드(예: .get() 또는 .post())를 정의하는 대신, 액션 메서드를 제공합니다. 이러한 방식은 동작을 더 유연하게 구성할 수 있게 합니다.

믹스인 클래스는 rest_framework.mixins에서 가져올 수 있습니다.

ListModelMixin
.list(request, *args, **kwargs) 메서드를 제공하여 쿼리셋을 리스트 형식으로 구현합니다.

만약 쿼리셋이 채워져 있으면, 200 OK 응답과 함께 쿼리셋을 직렬화한 표현을 응답 본문으로 반환합니다. 응답 데이터는 선택적으로 페이지네이션 처리될 수 있습니다.

CreateModelMixin
.create(request, *args, **kwargs) 메서드를 제공하여 새 모델 인스턴스를 생성하고 저장하는 역할을 수행합니다.

객체가 생성되면 201 Created 응답과 함께 객체를 직렬화한 표현을 응답 본문으로 반환합니다. 만약 표현에 url이라는 키가 포함되어 있으면, 응답의 Location 헤더는 해당 값으로 채워집니다.

만약 객체를 생성하는 데 필요한 요청 데이터가 잘못되었다면, 400 Bad Request 응답과 함께 오류 세부 정보를 본문으로 반환합니다.

RetrieveModelMixin
.retrieve(request, *args, **kwargs) 메서드를 제공하여 기존 모델 인스턴스를 응답으로 반환합니다.

객체를 찾으면 200 OK 응답과 함께 객체를 직렬화한 표현을 본문으로 반환하며, 그렇지 않으면 404 Not Found를 반환합니다.

UpdateModelMixin
.update(request, *args, **kwargs) 메서드를 제공하여 기존 모델 인스턴스를 업데이트하고 저장하는 역할을 수행합니다.

또한 .partial_update(request, *args, **kwargs) 메서드를 제공하는데, 이는 update 메서드와 유사하지만 업데이트를 위한 모든 필드가 선택사항이라는 점에서 다릅니다. 이 메서드는 HTTP PATCH 요청을 지원합니다.

객체가 업데이트되면 200 OK 응답과 함께 업데이트된 객체를 직렬화한 표현을 반환합니다. 잘못된 데이터가 제공되면 400 Bad Request 응답을 반환합니다.

DestroyModelMixin
.destroy(request, *args, **kwargs) 메서드를 제공하여 기존 모델 인스턴스를 삭제하는 역할을 합니다.

객체가 삭제되면 204 No Content 응답을 반환하며, 객체가 존재하지 않으면 404 Not Found를 반환합니다.

구체적인 뷰 클래스들

다음 클래스들은 구체적인 제네릭 뷰입니다. 제네릭 뷰를 사용할 때, 특별한 맞춤 동작이 필요하지 않으면 이 레벨에서 작업하게 됩니다.

이 뷰 클래스들은 rest_framework.generics에서 가져올 수 있습니다.

  • CreateAPIView
    생성 전용 엔드포인트에 사용됩니다.
    post 메서드 핸들러를 제공합니다.
    확장: GenericAPIView, CreateModelMixin

  • ListAPIView
    모델 인스턴스 컬렉션을 나타내는 읽기 전용 엔드포인트에 사용됩니다.
    get 메서드 핸들러를 제공합니다.
    확장: GenericAPIView, ListModelMixin

  • RetrieveAPIView
    단일 모델 인스턴스를 나타내는 읽기 전용 엔드포인트에 사용됩니다.
    get 메서드 핸들러를 제공합니다.
    확장: GenericAPIView, RetrieveModelMixin

  • DestroyAPIView
    단일 모델 인스턴스를 삭제하는 전용 엔드포인트에 사용됩니다.
    delete 메서드 핸들러를 제공합니다.
    확장: GenericAPIView, DestroyModelMixin

  • UpdateAPIView
    단일 모델 인스턴스를 업데이트하는 전용 엔드포인트에 사용됩니다.
    putpatch 메서드 핸들러를 제공합니다.
    확장: GenericAPIView, UpdateModelMixin

  • ListCreateAPIView
    모델 인스턴스 컬렉션을 나타내는 읽기-쓰기 엔드포인트에 사용됩니다.
    getpost 메서드 핸들러를 제공합니다.
    확장: GenericAPIView, ListModelMixin, CreateModelMixin

  • RetrieveUpdateAPIView
    단일 모델 인스턴스를 나타내는 읽기 또는 업데이트 엔드포인트에 사용됩니다.
    get, put, patch 메서드 핸들러를 제공합니다.
    확장: GenericAPIView, RetrieveModelMixin, UpdateModelMixin

  • RetrieveDestroyAPIView
    단일 모델 인스턴스를 나타내는 읽기 또는 삭제 엔드포인트에 사용됩니다.
    getdelete 메서드 핸들러를 제공합니다.
    확장: GenericAPIView, RetrieveModelMixin, DestroyModelMixin

  • RetrieveUpdateDestroyAPIView
    단일 모델 인스턴스를 나타내는 읽기-쓰기-삭제 엔드포인트에 사용됩니다.
    get, put, patch, delete 메서드 핸들러를 제공합니다.
    확장: GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin

제네릭 뷰의 커스터마이징

기존의 제네릭 뷰를 사용하면서 약간의 맞춤 동작을 원할 경우가 자주 있습니다. 여러 곳에서 동일한 맞춤 동작을 재사용하는 경우, 공통 클래스로 리팩토링하여 필요한 뷰나 뷰셋에 쉽게 적용할 수 있습니다.

커스텀 믹스인 만들기

예를 들어, URL 설정에서 여러 필드를 기반으로 객체를 조회해야 한다면, 다음과 같은 믹스인 클래스를 만들 수 있습니다:

class MultipleFieldLookupMixin:
    """
    이 믹스인을 뷰 또는 뷰셋에 적용하면
    기본 단일 필드 필터링 대신 여러 필드를 기반으로 한 필터링을 수행할 수 있습니다.
    """
    def get_object(self):
        queryset = self.get_queryset()             # 기본 쿼리셋 가져오기
        queryset = self.filter_queryset(queryset)  # 필터 백엔드 적용
        filter = {}
        for field in self.lookup_fields:
            if self.kwargs.get(field):  # 빈 필드는 무시
                filter[field] = self.kwargs[field]
        obj = get_object_or_404(queryset, **filter)  # 객체 조회
        self.check_object_permissions(self.request, obj)
        return obj

이 믹스인을 뷰나 뷰셋에 적용하면 언제든지 이 맞춤 동작을 사용할 수 있습니다.

class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_fields = ['account', 'username']

커스텀 베이스 클래스 만들기

여러 뷰에서 믹스인을 사용하고 있다면 한 단계 더 나아가 프로젝트 전체에서 사용할 수 있는 자체 베이스 뷰 세트를 만들 수도 있습니다. 예를 들어:

class BaseRetrieveView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
    pass

class BaseRetrieveUpdateDestroyView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView):
    pass

PUT을 통한 생성

3.0 버전 이전에는 REST 프레임워크의 믹스인들이 객체의 존재 여부에 따라 PUT 요청을 업데이트 또는 생성 작업으로 처리했습니다.

PUT 요청을 생성 작업으로 허용하는 것은 문제가 있을 수 있습니다. 이는 객체가 존재하는지 여부에 대한 정보를 필연적으로 노출시키게 되며, 삭제된 인스턴스를 투명하게 재생성하는 것이 단순히 404 응답을 반환하는 것보다 더 나은 기본 동작이라는 보장이 없습니다.

"PUT 요청 시 404 반환"과 "PUT 요청 시 생성" 두 가지 방식은 각각의 상황에 따라 유효할 수 있지만, 3.0 버전부터는 더 단순하고 명확한 동작 방식인 404 동작을 기본값으로 사용하고 있습니다.

만약 PUT 요청을 생성 작업으로 처리하는 일반적인 동작이 필요하다면, 다음과 같은 AllowPUTAsCreateMixin 클래스를 믹스인으로 추가하여 뷰에서 사용할 수 있습니다.

서드 파티 패키지

다음 서드 파티 패키지들은 추가적인 제네릭 뷰 구현을 제공합니다.

Django Rest Multiple Models
이 패키지는 단일 API 요청을 통해 여러 직렬화된 모델과/또는 쿼리셋을 전송할 수 있는 제네릭 뷰(및 믹스인)를 제공합니다.

Reference

DRF API Guide - Generic Views

profile
Back-end Junior Developer

0개의 댓글