본문 바로가기
개발자 전향 프로젝트

[장고] DRF 함수형 vs 클래스형의 차이 및 사용 예시

by 샘오리 2024. 6. 26.
DRF  = (a.k.a. Django Rest Framework)

 

 

장고는 MTP =  Model, Template, View의 형태로 이루어져 있는데

이 View가 사실상 장고의 핵심으로 request를 받아서 연산 혹은 db 작업을 하고

response까지 하는 모든 작업을 한다.

 

이 장고는 View를 두가지 방법으로 구현할 수 있는데 각각 다음과 같다.

  1. 함수형 ( Function Based View ) 
  2. 클래스형 ( Class Based View )

 

함수형 vs 클래스형의 차이

 

함수형의 경우

초창기부터 있었던 View 구현 방식으로

비교적 러닝커브가 낮아 구현하기가 쉽다는 장점이 있지만

확장하기가 어렵고 코드의 중복이 불가피하다.

 

소규모 프로젝트의 경우 함수형으로 구현을 해도 무방하며

기한이 임박한 상황에서 새로운 걸 배우고 도입하는 것보다 유리하다.

 

클래스형의 경우

기존 함수형 View 의 한계를 보완하여 나온 새로운 View 방식으로

비교적 러닝커브가 높아서 제대로 알지 못하면 구현하기가 어렵고

함수형 만큼 가독성이 좋지 않아서 처음봤을 때 바로 무슨 코드인지 바로 해석이 되지 않는다는 단점이 있지만

 

기존 함수형 View의 단점이었던 확장성은 늘리고 코드 중복 현저히 줄여 코드가 매우 간결하다.

이는 여러 코드를 더 객체지향적인 방법으로 (캡슐화,상속,다형성) 재사용하고 자동화했기 때문이다.

 

당장 코드 찍어내고 배포하고 끝나는 그런 단발성 소규모 프로젝트보다는

하나의 프로젝트를 중장기적으로 계속해서 업그레이드 서비스하는 그런 프로젝트에 더 적합하고

러닝커브가 높기 때문에 팀 전체적으로 클래스형 뷰가 어떻게 작동하는지 알아야한다.

 

뭐가 반드시 좋다 라는것이 없고 상황에 맞춰 선택하면 되는 것이지만

더 권고되는 구현방법을 고르자면 클래스형 View = CBV 이다.

 


예시 및 사용법

 

함수형은 구현하기도 쉽고 예시가 많기 때문에 CBV 예시를 들려고 한다.

 

템플릿 렌더링 (Server Side Rendering)

템플릿으로 전달할게 없으면 아래와 같이 간단하게 되고

from django.views.generic import TemplateView

class MyStaticTemplateView(TemplateView):
    template_name = 'my_static_template.html'

 

있다고 하면 아래처럼 사용하면 된다.

from django.views.generic import TemplateView

class MyTemplateView(TemplateView):
    template_name = 'my_template.html'
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # Add any context data you need here
        context['my_data'] = 'Some data'
        return context

 

뭐 html div같은 곳에 read_only를 받아서 권한이 있을 때와 없을 때를 구분할 때 사용하면 용이할 것 같다.

 

추후에 다루게 될 APIView라고 하는 Generics( pre-built, reusable class-based views ) 을 사용해서도 구현할 수 있는데 렌더링과 api를 하나의 클래스에 처리할 수 있기 때문에 편리하다. 다만 처음 화면 렌더링 하는 용도가 아니라 조회를 할 때도 get 요청이 들어오기 때문에 이를 구분할 수 있는 방법도 필요하다.

 

아래와 같이 할 수 있다.

from rest_framework.views import APIView
from django.shortcuts import render
from .models import MyModel  # Replace with your actual model
from .serializers import MyModelSerializer  # Replace with your actual serializer

class MyTemplateAPIView(APIView):
    def get(self, request, *args, **kwargs):
        # Get the search keyword from query parameters
        search_keyword = request.query_params.get('search', None)
        
        if search_keyword:
            # Filter the data based on the search keyword
            data = MyModel.objects.filter(name__icontains=search_keyword)  # Adjust the field name as needed
        else:
            # Fetch all data
            data = MyModel.objects.all()
        
        # Serialize the data if needed (optional)
        serializer = MyModelSerializer(data, many=True)
        
        # Prepare the context
        context = {
            'my_data': serializer.data
        }
        
        # Render the template with the context
        return render(request, 'my_template.html', context)

 

Serializer는 data가 valid하고 consistent한지 체크를 해주기 때문에 사용한다. 일종의 스프링의 dto와 비슷하며 하나의 틀을 정해놓으면 이 틀에 맞춰서 보내주기 때문에 편하다.

 

아래는 샘플 serializer 클래이다. Model에 선언된 모든 필드를 담아준 것이다.

class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MY_MODEL
        fields = '__all__'

 

 

REST API 처리

위에서는 렌더링을 설명하다가 get을 다뤘는데 get 말고도 CRUD 연산작업을 하기 위해서 다양한 REST API가 활용된다. 예컨데 post,put,patch,delete 등 여러가지가 있는데 이를 하나의 클래스에 다 선언하고 모든 request는 하나의 클래스로 요청되어지면 된다. 그러면 클래스가 REST에 맞게 http method에 맞는 함수를 매핑해준다.

 

가장 중요한 url은 다음과 같이 짜면 된다.

 

from django.urls import path
from .views import MyTemplateAPIView

urlpatterns = [
    path('my-template/', MyTemplateAPIView.as_view(), name='my_template'),
]

 

클래스이기 때문에 대문자인건 당여하고

중요하게 볼 것은 .as_view() 라는 것을 붙여줬는데 이렇게 선언해야 클래스형 View로 사용할 수 있다.

예를 들어 frontend 단에서 my-template/라는 url로 get을 요청하든 post를 요청하든 delete를 요청하든

다 저 클래스의 선언된 함수로 매핑이 될 수 있게 해주는 것이 .as_view() 이다.

 

아무튼 클래스형 View를 사용하면 이 URL이 참 간결해진다는 것도 장점이다.

앞서 언급한 코드 중복을 줄일 수 있다는 장점이 있다.

 

get과 같은 경우 위에서 언급한대로 serializer를 통해 그냥 가져오면 되는데

db의 데이터를 조작해야하는 경우는 validation을 거치는게 일반적이다.

이럴 때 serializer의 장점이 나오는 것이기도 하다.

 

부분적으로 업데이트를 치는 함수를 예시로 설명하겠다.

 

일단 url에 pk가 들어가야 하니 url 은 아래 처럼 pk를 선언해주면 되고

from django.urls import path
from .views import MyTemplateAPIView

urlpatterns = [
    path('my-template/<int:pk>', MyTemplateAPIView.as_view(), name='my_template'),
]

 

가져온 pk를 통해 업데이트 칠 행을 정하고

업데이트를 치려고 frontend 단에서 보낸 데이터를 받은 뒤

serializer에 특정된 행과, 데이터와, 부분적인지 아닌지를 boolean값으로 나타낸 것을 적으면 된다.

그리고 validation을 다음과 거쳐서 통과하면 저장되고 reponse를 받는 형식이다. 

delete도 행이 특정되어야 하기 때문에 같고 create만 data=가져온 데이터로 하면 된다.

def partial_update(self, request, pk):
    try:
        pk를 통해 선택된 행 = MY_MODEL.objects.get(pk=pk)
        가져온 데이터 = json.loads(request.data['key'])
        serializer = MyModelSerializer(pk를 통해 선택된 행, data=가져온 데이터, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=400)
    except MY_MODEL.DoesNotExist:
        return HttpResponse(status=404, content="Error Message")

 

다음에는 REST API처리를 할 때 

사용됐던 APIView와 이 APIView의 단점을 보완한 ModelViewSet의 차이 및 사용예시를 다루려고 한다.