장고 시작하기 24. DRF의 CBV FBV

DRF의 CBV FBV 를 들어가며…

Django에서 View는 사용자의 요청과 애플리케이션의 응답 사이를 연결하는 역할을 합니다.

이런 View 구현과 관련하여 개발자는 FBV(Function-Based Views)와 CBV(Class-Based Views)라는 두 가지 주요 접근 방식을 사용할 수 있습니다.

특히나 pure django가 아닌 DRF를 사용하게 되면 FBV, CBV를 조금 더 체계적으로 제공해주게 되는데요.

“체계적으로 다 만들어서 추상화 해서 제공해준다” = “방법을 학습해야 한다” 이기 때문에 이 두 가지 방법이 무엇인지 우선 공부가 필요한 것 같습니다.

이번 포스트에서는 FBV, CBV는 어떻게 다르고 어떻게 사용할 수 있는지에 대해 공부한 내용을 정리해보았습니다.

DRF가 무엇인지 모른다면?

Function-Based Views (FBV)

FBV는 Django에서 뷰를 정의하는 전통적인 방법으로, 각 뷰는 Python 함수로 구현됩니다.

이러한 함수는 HttpRequest 개체를 입력으로 사용하고 HttpResponse 개체를 출력으로 반환합니다.

FBV는 일반적으로 요청을 수신하고, 데이터를 처리하고, 응답을 반환하는 간단한 구조를 따릅니다.

개발자가 함수 내부의 논리를 완벽하게 제어할 수 있으므로 사용자 정의가 쉽고 유연하다는 장점이 있습니다.

FBV의 기본 작성법은 이전 포스트에서 정리했지만 다시 간단하게 정리하자면 아래와 같습니다.

  • 다음 메소드와 클래스를 필수로 import
    • from rest_framework.decorators import api_view
    • from rest_framework.response import Response
  • 요청을 처리하는 함수 위에 @api_view 를 반드시 사용
  • 반환은 Response(데이터, 상태코드) 형태로 반환

FBV 예제

from django.shortcuts import get_object_or_404
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .serializers import ArticleSerializer
from .models import Article


@api_view(["GET", "POST"])
def article_list(request):
    if request.method == "GET":
        articles = Articles.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)

    elif request.method == "POST":
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)


@api_view(["GET", "PUT", "DELETE"])
def article_detail(request, article_pk):
    article = get_object_or_404(Articles, pk=article_pk)
    if request.method == "GET":
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    elif request.method == "PUT":
        article = get_object_or_404(Articles, pk=article_pk)
        serializer = ArticleSerializer(article, data=request.data, partial=True)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)

    elif request.method == "DELETE":
        article.delete()
        data = {"delete": f"Article({article_pk}) is deleted."}
        return Response(data, status=status.HTTP_200_OK)

Class-Based Views (CBV)

CBV는 Django에 FBV 이후에 도입된 것으로, 뷰 정의에 대한 보다 구조화되고 객체 지향적인 접근 방식을 제공합니다.

개별 함수 대신 뷰는 DRF의 내장 뷰 클래스에서 상속되는 Python 클래스로 정의됩니다.

CBV는 공통 패턴과 기능을 재사용 가능한 구성 요소로 캡슐화하여 더 높은 수준의 추상화를 제공합니다.

따라서 최소한의 코드로 CRUD 작업, 양식 처리 및 기타 일반적인 작업을 처리하는 데 이상적입니다.

그러나 위에서 언급하였듯이 “높은 추상화” = “방법 공부해야 됨” 이기 때문에 CBV는 FBV에 비해 학습 곡선이 조금 더 가파릅니다.

그래도 한 번 보면 바로 이해되는 것 같은 느낌적 느낌…?

아래 공부한 내용을 정리해보았습니다.

Class Based View 종류

CBV에는 여러 종류가 있습니다. 우선은 어떤 CBV가 있는지를 우선 다루고 그 중 APIView를 중점적으로 다뤄보았습니다.

ViewSet도 재미있는 CBV인 것 같아서 정리해보았는데 해당 내용은 다음 포스트에 정리하도록 하겠습니다.

GenericAPIViewMixin은 시간이 되면 공부하고 정리….

APIView

APIView는 들어오는 HTTP 요청을 처리하고 적절한 HTTP 응답을 반환하는 뷰의 기본 클래스입니다.

상단의 FBV 코드를 APIView로 만들면 아래와 같습니다.

  • rest_framework.views로 부터 APIView를 import 합니다.
  • FBV 로직을 그대로 사용하되 get 메소드와 post 메소드 명시하면 끝 입니다.

FBV와 다르게 데코레이터와 request 요청 방법에 따른 분기 부분을 개발자가 신경 쓰지 않아도 되게 되었습니다.

from rest_framework.views import APIView

class ArticleListAPIView(APIView):
    def get(self, request):
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)


class ArticleDetailAPIView(APIView):

    def get_object(self, pk):
        return get_object_or_404(Article, pk=pk)

    def get(self, request, pk):
        article = self.get_object(pk)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    def put(self, request, pk):
        article = self.get_object(pk)
        serializer = ArticleSerializer(article, data=request.data, partial=True)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)

    def delete(self, request, pk):
        article = self.get_object(pk)
        article.delete()
        data = {"pk": f"{pk} is deleted."}
        return Response(data, status=status.HTTP_200_OK)

CBV를 사용하게 되면 URL에서 어떤 view로 라우팅 할 것인지를 변경해줘야 합니다.

  • CBV는 클래스명.as_view() 를 사용해서 라우팅
  • FBV에서는 함수명을 사용해서 라우팅 했던 것과 다르다는 점에 유의
from django.urls import path
from . import views

app_name = "articles"
urlpatterns = [
    # FBV 라우팅 
    # path("", views.article_list, name="article_list"),

    # CBV 라우팅
    path("", views.ArticleListAPIView.as_view(), name="article_list"),
]
GenericAPIView

GenericAPIView는 APIView 클래스에서 기본적인 리스트와 상세 보기와 같은 행동을 추가한 확장 클래스 입니다.

Generic APIView도 간단하게만 집고 넘어가겠습니다. GenericAPIView도 추상화가 높은 수준 되어있기 때문에 정말 간단한 대신 규칙대로 사용해야 합니다. (물론 override로 어느 정도 동적으로 쓸 수 있게 한 것 같긴 합니다.)

속성

클래스의 속성을 약속대로 입력해야 합니다.

기본 속성, pagination, 필터링과 관련된 속성 값들은 다음과 같습니다.

기본 세팅 속성

  • queryset
    • 해당 뷰에서 사용할 queryset을 정의 합니다.
  • serializer_class
    • 입력의 유효성을 검사하고 역직렬화하고 출력을 직렬화하는 데 사용해야 하는 시리얼라이져 클래스를 정의
  • lookup_field
    • 개별 모델 인스턴스의 객체 조회를 수행하는 데 사용해야 하는 모델 필드. 기본 값은 pk 입니다.
  • lookup_url_kwarg
    • 객체 조회에 사용해야 하는 URL 키워드 인수입니다.
    • URL conf에는 이 값에 해당하는 키워드 인수가 포함되어야 합니다.
    • 기본값은 lookup_field와 동일한 값을 사용합니다.

pagination

  • pagination_class
    • pagination 리스트 결과를 사용하려면 해당 속성을 사용
    • 기본값은 DEFAULT_PAGINATION_CLASS 과 동일
    • rest_framework.pagination.PageNumberPagination 을 통해서 변경
      • pagination_class=None 은 pagination을 비활성화

Filtering

  • filter_backends
    • 쿼리 세트를 필터링하는 데 사용해야 하는 필터 백엔드 클래스 리스트

method

  • get_queryset(self)
    • 속성에서 명시한 queryset을 가져옵니다.
    • 오버라이드해서 동적을 사용할 수 있습니다.

예제

  • class YourModelAPIView(GenericAPIView):
    • GenericAPIView를 상속 받아서 YourModelAPIView라는 사용자 정의 API 보기를 생성
  • queryset = YourModel.objects.all():
    • 데이터베이스에서 모델(YourModel)에서 queryset을 가져옵니다
  • serializer_class = YourModelSerializer:
    • 모델 인스턴스를 직렬화/역직렬화하는 데 사용되는 시리얼라이져 클래스(YourModelSerializer)를 지정
  • get() 메소드
    • get() 메서드 내에서 쿼리 세트를 사용하여 데이터베이스에서 YourModel의 모든 인스턴스를 검색하고, 시리얼라이져를 사용하여 직렬화하고, 직렬화된 데이터를 응답으로 반환합니다.
  • post() 메소드:
    • post() 메소드 내에서 요청으로 전송된 데이터를 사용하여 YourModel의 새 인스턴스를 생성합니다.
    • 시리얼라이져를 사용하여 데이터의 유효성을 검사하고, 유효한 경우 인스턴스를 저장하고 응답에 직렬화된 데이터를 반환하고, 데이터가 유효하지 않으면 상태 400(잘못된 요청)으로 유효성 검사 오류를 반환합니다.
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from rest_framework import status
from .models import YourModel
from .serializers import YourModelSerializer

class YourModelAPIView(GenericAPIView):
    queryset = YourModel.objects.all()
    serializer_class = YourModelSerializer

    def get(self, request, *args, **kwargs):
        # Retrieve all instances of YourModel
        queryset = self.get_queryset()
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

    def post(self, request, *args, **kwargs):
        # Create a new instance of YourModel
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Mixin

Mixin은 뷰에 추가 기능을 제공하는 재사용 가능한 코드 뭉치 입니다.

일반적으로 GenericAPIView와 함께 사용되어 기능을 확장한다고 합니다.

ViewSets

ViewSet은 여러 관련 뷰에 대한 논리를 단일 클래스로 결합하는 상위 수준 추상화입니다.

여러 엔드포인트(endpoint)를 단일 클래스에서 관리할 수 있게 해줍니다.

이는 RESTful 원칙을 준수하고 일관된 URL 패턴이 필요한 API를 구축하는 데 특히 유용합니다.

ViewSet은 각 HTTP 메서드에 대해 자체적으로 액션 (action)을 정의합니다.

액션은 기본적인 CRUD 같은 프로세스를 함수로 정의했다고 생각하면 되는데요.

이렇게 함으로써 코드가 더 간결하게 만들 수 있습니다.

class ExampleViewSet(viewsets.ViewSet):
    """
    Example empty viewset demonstrating the standard
    actions that will be handled by a router class.

    If you're using format suffixes, make sure to also include
    the `format=None` keyword argument for each action.
    """

    def list(self, request):
        # 전체 데이터 가져오기
        pass

    def create(self, request):
        # 생성
        pass

    def retrieve(self, request, pk=None):
        # 하나의 데이터 가져오기
        pass

    def update(self, request, pk=None):
        # 수정
        pass

    def partial_update(self, request, pk=None):
        # 일부 수정
        pass

    def destroy(self, request, pk=None):
        # 삭제
        pass

FBV와 CBV 중에서 선택

DRF의 FBV CBV 둘 중 무엇을 사용해도 괜찮다고 합니다.

FBV와 CBV 사이의 결정은 궁극적으로 프로젝트의 특정 요구 사항과 팀의 선호도 및 개발자 경험에 따라 달라지는거죠.

FBV는 간단한 논리와 처리가 필요한 경우 적합해 보입니다.

반면에 CBV는 객체지향의 특성 덕에 잘 사용할 경우 재사용성과 유지 보수성이 향상 될 것 같고요.

저는 개인적으로 커스터마이징이 잘되는 FBV도 마음에 들지만…. 현재는..! CBV가 더 직관적이고 실수를 줄일 수 있을 것 같아서 이쪽으로 마음이 조금 더 가는 것 같습니다…..!

마치며

pure django도 편한데 DRF는 편하고 다 제공해주고…프런트 엔드 쪽은 추후 신경 써도 되고…너무 좋네요…!

DRF의 CBV FBV 중 골라서 사용할 수 있는 점도 마음에 듭니다.

하지만 제대로 사용하기 위해서는 확실히 초반에 공부가 많이 필요한 것 같습니다.

특히 CBV는 반복적인 작업을 덜 할 수 있도록 높은 수준으로 추상화 된 대신에 각 방법마다 사용법이 다 다르기 때문에 학습 곡선이 높은 것 같습니다.

이번 포스트에서 CBV는 어떤 종류가 있는 지 대략적으로만 짚었지만 url 설정 방법도 다르고 CBV 종류마다 여러 메소드들이 존재함으로 해당 내용은 추후에 따로 정리해야 할 것 같습니다.

아..! 그리고 DRF는 django 공식 문서가 아니라 DRF 공식 문서가 따로 있어서 이걸 봐야지 제대로 공부할 수 있다는 중요한 사실도 알게 되었습니다.ㅎㅎ

장고 공식문서에서 DRF 관련 정보가 search 안된다고 당황하지 않기…

이 다음에는 DRF로 CRUD를 구현하는 API를 간단하게 개발해보는 내용, 그리고 API 응답 데이터의 구조를 변경해보는 내용을 공부해서 올릴 것 같습니다.

참고하면 좋은 글

Leave a Comment

목차