본문 바로가기
Web/Django

Django Rest Framework - (1 : API 테스트 해보기)

by DUSTIN KANG 2023. 12. 25.

DRF

Django에서 RestAPI를 쉽게 구축할 수 있게 도와주는 프레임워크이다.

그렇다면 왜 Django가 아니라 REST한 프레임워크인 DRF를 사용하게 된 것일까?

예전에는 브라우저만으로 웹 서비스를 접근할 수 있었지만 스마트폰, 태블릿 PC등이 생기면서 여러가지 플랫폼에서도 웹/앱 서비스를 접근할 수 있어야한다는 중요성이 생기게 되었다. 그래서 Django Rest Framework가 필요해지게 된 것이다.

 

Restful한 API를 개발할 수 있다는 이야기는 한마디로 사용자가 원하는 것을 잘 찾을 수 있게 정리해놓은 것을 말한다.

예를 들어, 클라이언트가 커피를 주문하면 `order/1` 이런식으로 찾아 줄 수 있는 것이다. 

DRF의 자세한 내용은 공식 문서를 참고하면 된다.

DRF은 Django와 다르게 다른 프론트엔드와 협업을 쉽게 할 수 있다는 점이다.

 

DRF 설치하기

DRF를 설치하는 방법은 `pip`를 이용해 패키지를 설치하면 된다.

pip install djangorestframework

# 여기서 부터는 선택 사항
pip install markdown # 브라우저 API의 Markdown 지원을 위함
pip install django-filter # 추후 필터링을 위한 지원

 

그다음, `INSTALLED_APPS`에 `rest_framework`를 넣어준다.

INSTALLED_APPS = [
    ...
    'rest_framework',
]

DRF 구성 요소

Rest Framework의 대표적인 구성 요소는 Serializer, View(ViewSet), Router로 이루어져 있다.

각 구성요소마다 하는 역할이 다르다. 일반적으로 View는 Django에서 배웠듯 CRUD 동작이나 요청 처리를 하는 것으로 알고 있고 Router는 URL 패턴을 생성하고 뷰와 연결시키는 역할을 한다.

Serializer는 무엇일까? 시리얼라이저는 데이터를 변환하는 역할을 한다. 

Serializer

시리얼라이저는 일명 직렬화라고 부른다. 파이썬 데이터를 JSON 형식으로 바꿔주는 역할을 한다. 보통 우리는 ORM을 통해 파이썬 문법으로 데이터를 처리하지만 다양한 클라이언트나 프론트 엔드에게 전달하기 위해 공통의 문서인 JSON으로 바꿔줄 필요가 있다.

그렇다면 반대로 역 직렬화(DeSerializer)가 있을까? 있다. 다음 코드를 보자.

lass SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

 

해당 스니펫이라는 모델을 시리얼라이저를 이용해 JSON으로 바꿀 수 있게끔 `serializers.py`를 생성해 상위 코드를 구현해냈다. `create()`와 `update()` 함수는 나중에 POST와 같은 요청이 들어왔을 때 파이썬 데이터로 역 직렬화하여 DB에 넣을 때 사용된다.  여기서 위 메서드에 `validated_data`가 파라미터로 들어있는데 이유는 검증된 데이터를 기반으로 모델 인스턴스를 반환할 수 있도록 하기 위해서이다. 그럼 def 내부에 if 문을 사용해 한 메서드로 만들 수 있지 않을까 싶은데 가능하다. 

 

이번엔 Django Shell에서 어떻게 직렬화가 이루어지는지 알아보자.

 

직렬화와 역직렬화

 

우선 해당 코드 이전에 생성된 데이터에 대한 `snippet`이라는 쿼리셋 변수를 생성했다.

생성된 변수를 직렬화하여 파이썬 데이터 유형으로 변경하고 JSONRenderer로 렌더링하면 Json 형태로 바뀌게 된다. 물론 view에서는 JsonResponse가 있으니 그걸 사용하면 된다. 

직렬화

역직렬화도 가능하다.

역직렬화를 할때는 데이터를 가져와 파이썬 데이터로 변환하면서 유효성 검증을 확인한 후 데이터를 저장한다. 

역직렬화1
역직렬화2

 

Django Shell에서 직렬화와 역직렬화를 사용하는 경우도 있지만 직렬화 과정에서도 가능하다. 아래 코드를 보면 `to_representation()`과 `to_internal_value()`를 사용하고 있는 것을 확인할 수 있다. 시리얼라이저 클래스의 모델 인스턴스를 JSON 데이터로 변환하거나 그 반대로도 변환할 수 있다. 주로 `ModelSerializer`에서 사용된다. 

    def to_representation(self, instance):
        # 모델 인스턴스를 JSON 데이터로 변환
        ret = super().to_representation(instance)
        return ret
    
    def to_internal_value(self, data):
        # JSON 데이터를 모델 인스턴스로 변환
        internal_values = super().to_internal_value(data)
        return internal_values

 

ModelSerializer

 Django에서 Model Form과 Form을 제공하듯 시리얼라이저의 종류에는 일반적인 시리얼라이저가 있고 모델시리얼라이저가 있다.

모델 시리얼라이저는 모델을 기반으로 필드를 생성하기 때문에 기존의 시리얼라이저보다 간결하게 작성할 수 있다는 이점이 있다. 

Django Shell에서 `print(repr(SnippetSerializer()))`로 자동 필드 생성이 된 것을 확인할 수 있다.

ModelSerializers는 `read_only_field`(읽기전용 필드) 나 `exclude`(제외 필드)를 통해 필드를 설정할 수 있다.


View

RestFramework에서는 View 클래스를 상속하는 API View 클래스를 제공한다. 물론, 일반적인 FBV를 사용하면 안되는 것은 아니다. 

우선 일반적으로 우리가 사용하는 FBV를 사용하는 방법에 대해 알아보자.

 

FBV와 API 정책 데코레이터

DRF 기반으로 View를 작성할 때 모델과 시리얼라이저 외에 반드시 세가지 모듈을 가져와야 한다.

여기서 특별한 점은 FBV나 CBV나 전달되는 요청이 DRF의 요청이기 때문에 Django Response가 아닌 DRF의 Response로 응답해야한다는 점이다. 물론 Django의 일반 뷰 클래스를 사용하는 방법이나 동일하다. 

from rest_framework import status # HTTP status를 반환한다.
from rest_framework.decorators import api_view # FBV 방식
from rest_framework.views import APIView # CBV 방식
from rest_framework.response import Response # 응답

 

다음 모듈을 임포트하였으면 다음과 같이 코드를 작성하면 된다. FBV를 작성할 때는 `@api_view` 데코레이터를 이용해 Method를 작성해야한다. FBV의 특징으로 보자면, 직관적이고 명시적이라는 것이다. 다만 페이징이나 필터링, 권한 처리등을 할 때 수동으로 처리해야한다는 단점이 있다. 

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
# Create your views here.
@api_view(['GET', 'POST'])
def snippet_list(request):
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True) # 해당 모델 인스턴스 직렬화
        return Response(serializer.data) # 직렬화한 데이터 반환
    
    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data) # JSON 데이터 가져오기
        if serializer.is_valid():
            # 유효성 검사가 통과하는 경우
            serializer.save() # CREATE()나 UPDATE 호출
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

CBV

Django에서 제공하는 View클래스는 다양하다.

  • GenericAPIView
  • ViewSet
  • GenericViewSet
  • ModelViewSet

먼저 View 클래스를 상속하는 API View 클래스이다.

우리는 API View를 상속하여 간단하게 일반 CBV 작성하듯 작성하면 된다. 기존 FBV로 작성했었던 것을 CBV로 변경해보았다.

class SnippetList(APIView):
    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True) # 해당 모델 인스턴스 직렬화
        return Response(serializer.data) # 직렬화한 데이터 반환
    
    def post(self, request, format=None):
        serializer = SnippetSerializer(data=request.data) # JSON 데이터 가져오기
        if serializer.is_valid():
            # 유효성 검사가 통과하는 경우
            serializer.save() # CREATE()나 UPDATE 호출
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

그럼 API View에는 어떤 클래스들이 있는 것일까?

다음과 같이 상속받은 API View 클래스에 대해 알아보자.

추후에 사용할 예정이다.

 

Mixins, Generics

DRF에서는 CRUD를 좀 더 쉽게 작성하기위해 Mixins와 Generics라는 라이브러리를 제공한다. 이 방법은 URL과 Method 조합을 하나로 묶어 다른 방식으로 사용하여 API를 요청할 수 있게 한다. 이러한 방법들은 다양한 상황에서 사용할 수 있고 코드를 간결하게 만들 수 있다는 장점이 있다.

from rest_framework import generics, mixins

mixins의 클래스들을 상속받아 아래처럼 작성할 수 있다. 만약 목록이 아닌 데이터를 가져오는 경우는 `Retrieve`를 사용해 조회한다.

이때 주의할 점은 반드시 `queryset` 변수에는 쿼리셋, `serializer_class` 변수에는 시리얼라이저를 넣어 생성해야한다. GenericAPIView로 뷰로 구축하기 때문이며 이 뷰 내부에 queryset과 serializer_class가 정해져 있기 때문이다. `lookup_field`로 pk 값을 설정해야한다. 기본적인 pk를 사용한다면 생략해도 된다. 

 

class SnippetList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

 

조금 다른 점이라면 기존 APIView와 작성했을 때와 비교하여 친화적으로 변경되었다는 점이다.

 

`Generic`을 사용하면 좀 더 간결하게 작성할 수 있다. mixins이랑 차이가 있다면 return 구문이 사라지게 된 것이다. 이전에는 메소드를 매칭에서 작성해야 했었는데 Generic은  mixins의 조합으로 generics를 만들어 냈으니  혼합해서 사용하면 무슨 메소드가 들어있는지 알고 있으니(알고 만들었으니) 필요없게 된 것이다.

 

ViewSet

Django에서 제공하는 Viewset은 좀 더 함축적을 작성할 수 있다는 장점을 가지고 있다. 그리고 라우터를 통해 하나하나 URL을 지정하지 않아도 일정한 규칙의 URL을 만들어낼 수 있다. 보통 명확한 CRUD가 있는 경우에만 사용하는 것 같다.

from rest_framework import viewsets

 

그럼 왜 Viewset은 CRUD의 기능을 함축적으로 담고 있을까?

이유는 Mixin과 Generic을 한 클래스에 담아버렸기 때문이다. 그래서 우리는 ModelViewSet 하나만 작성하면 글쓰기 목록과 데이터를 한번에 관리할 수 있다.

Viewset

class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

 

Viewset은 view 클래스와 다르게 `Viewset`이라는 클래스를 사용하기 때문에 URL conf를 설계할 필요가 없다. URL과 뷰를 자동으로 연결해줄 수 있는 `Router` 클래스가 있기 때문에 라우터에 등록만 해주면 된다.

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register('users', UserViewSet, 'user') # url, viewset, basename
urlpatterns = router.urls

Postman에서도 정상 출력

 

다음 포스팅에서는 DRF관련 다양한 설정과 Swagger에 관해 포스팅할 예정이다.

 

마치며,

항상 CBV가 옳은 선택인 것은 아니다.

아래 문장은 공식문서에서도 확인할 수 있는데 클래스 뷰가 항상 우월한 솔루션이 아니라는 것을 알려주고 있다. 즉 상황에 따라 괜찮은 선택지를 판단해 작성하는 것이 좋을 것 같다. 나는 보통 CRUD를 명확하게 사용해야하는 경우에나 클래스 형 뷰를 이용하지 간단한 함수에 경우에는 함수형 뷰를 사용한다. 

 

Viewset도 장점이 많긴 하지만 자유도가 낮기 때문에 커스텀이나 수정에 있어 어려움이 생길 수 있다. 그래서 항상 viewset 또한 정답은 아니다.