본문 바로가기
  • 실행력이 모든걸 결정한다
Django Rest Framework

[DRF] DRF Serializer

by 김코더 김주역 2023. 2. 21.
반응형

1. DRF Serializer가 필요한 이유

- DRF가 순수 텍스트를 반환하는 경우는 많이 없고, 일반적으로 Django의 모델 객체를 JSON으로 변환(직렬화)한 결과를 반환하게 된다. Django의 모델로부터 추출한 queryset 또는 모델 객체를 JSON 타입으로 변환해주기 위해서는 바로 DRF Serializer가 필요하다.

- 반대로 클라이언트로부터 받은 JSON을 파이썬 객체로 변환하기 위해서는 역직렬화(deserialize) 과정도 필요한데, DRF Serializer는 역직렬화 기능도 동시에 갖고 있다.

- 클라이언트와 서버 간 데이터 양식을 맞춰주는 변환기 역할을 하고 있다고 간단히 이해하면 될 것이다.

 

 

 

2. ModelSerializer

- ModelSerializer는 Django 모델을 기반으로 직렬화해주는 시리얼라이저다. 예를 들어, 다음과 같이 간식 정보가 담겨있는 Snack 모델이 있다고 하자.

class Snack(models.Model):
    name = models.CharField('NAME', max_length = 50)
    image = models.ImageField('IMAGE', upload_to = 'snack/images/')
    url = models.URLField('URL', max_length = 500)
    description = models.CharField('DESCRIPTION', max_length = 300, blank = True, help_text = ' (개수 등 설명 추가)')
    is_accepted = models.BooleanField('IS_ACCEPTED', default = False)
    supply_year = models.PositiveSmallIntegerField('SUPPLY_YEAR', null = True)
    supply_month = models.PositiveSmallIntegerField(
        'SUPPLY_MONTH', null = True,
        validators = [
           MaxValueValidator(12),
           MinValueValidator(1),
       ]
    )
    create_dt = models.DateField('CREATE_DT', auto_now_add = True)
    
    ...

 

- 모델 시리얼라이저는 rest_framework의 serializers.ModelSerializer 클래스를 상속받아 생성하면 된다. 그리고 반드시 Meta 내부 클래스의 fields를 지정해야 하는데, fields에는 외부로 공개해도 괜찮은 필드들만 선언해야 한다. 필자의 Snack 모델의 모든 필드는 외부로 공개해도 안전하기 때문에 모든 필드를 작성해주었다.

# snack/serializers.py

from rest_framework import serializers

from snack.models import Snack


class SnackSerializer(serializers.ModelSerializer):

    class Meta:
        model = Snack
        fields = ['id', 'name', 'image', 'url', 'description', 'is_accepted',
                  'supply_year', 'supply_month', 'create_dt']

모든 필드를 보여줄 것이라면 fields='__all__'을 명시하는 것이 좋은데, 필자는 이해를 돕기 위해 모든 필드를 작성했다. 또, 모델에서 기본키 필드를 별도로 작성하지 않았다면 'id'라는 기본키 필드가 자동으로 생성되는데, 이러한 경우에는 fields에 'id'도 명시 가능하다.

 

- 모델을 기반으로 하지 않는 시리얼라이저라면 ModelSerializer 대신 Serializer를 사용한다.

 

 

 

3. Serializer 적용하기

- 모델과 시리얼라이저가 준비되었으니 이제 뷰에 시리얼라이저를 적용할 차례다. FBV(Function Based View)와 CBV(Class Based View) 2가지 방법으로 나누어보았다.

- 직렬화시에는 시리얼라이저의 instance 속성에, 역직렬화시에는 data 속성에 대상 데이터를 넣는다. 딕셔너리, 모델 객체, 모델 객체 리스트(many=True인 경우)가 대상 데이터가 될 수 있다.

- 시리얼라이저의 유효성 검사에 실패하면 오류 내용이 시리얼라이저의 errors 속성에 담기고, 유효성 검사에 통과한 데이터들은 validated_data 속성에 담기게 된다.

 

1) FBV

- 뷰 함수에 @api_view 데코레이터를 붙이는 방식이다.

- 조건문을 통해 HTTP 메소드를 분리한 것이 주요 특징이다.

from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view
from rest_framework.generics import get_object_or_404

from .models import Snack
from .serializers import SnackSerializer


@api_view(['GET', 'POST'])
def snacksAPI(request):
    if request.method == 'GET':
        snacks = Snack.objects.all()
        serializer = SnackSerializer(snacks, many=True) # 여러 모델 처리시 many=True로 지정
        return Response(serializer.data, status=status.HTTP_200_OK) # GET 성공 응답
    elif request.method == 'POST':
        serializer = SnackSerializer(data=request.data)
        if serializer.is_valid(): # 유효성 검사
            serializer.save() # 역직렬화 후 저장
            return Response(serializer.data, status=status.HTTP_201_CREATED) # POST 성공 응답
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # 잘못된 요청 응답

@api_view(['GET'])
def snackAPI(request, pk): # URL 변수 pk 추출
    snack = get_object_or_404(Snack, pk=pk)
    serializer = SnackSerializer(snack)
    return Response(serializer.data, status=status.HTTP_200_OK)

 

- urls.py에는 다음과 같이 url과 뷰 함수를 짝지어 등록해주면 된다.

# snack/urls.py

app_name='snack'
urlpatterns = [
    # /snack/fbv/snacks
    path('fbv/snacks', snacksAPI),
    # /snack/fbv/snack
    path('fbv/snack/<int:pk>/', snackAPI),
]

 

 

2) CBV

- 뷰 클래스에 APIView 클래스를 상속해주는 방식이다. 클래스형 뷰를 강력하게 만들어주는 도구들이 많이 존재하기 때문에 FBV에 비해 더 많이 사용된다.

- 파이썬 메소드를 통해 HTTP 메소드를 분리한 것이 주요 특징이다.

from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view
from rest_framework.generics import get_object_or_404

from .models import Snack
from .serializers import SnackSerializer


class SnacksAPI(APIView):
    def get(self, request):
        snacks = Snack.objects.all()
        serializer = SnackSerializer(snacks, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    def post(self, request):
        serializer = SnackSerializer(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)

class SnackAPI(APIView):
    def get(self, request, pk):
        snack = get_object_or_404(Snack, pk=pk)
        serializer = SnackSerializer(snack)
        return Response(serializer.data, status=status.HTTP_200_OK)

1)에서 작성한 FBV 방식을 CBV 방식으로 그대로 바꾼 것이기 때문에 요청 결과는 동일하다.

 

- urls.py에는 다음과 같이 url과 뷰 클래스를 짝지어 등록해주면 된다. 클래스형 뷰의 경우에는 당연히 as_view()를 추가해야 한다.

# snack/urls.py

app_name='snack'
urlpatterns = [
    # /snack/cbv/snacks
    path('cbv/snacks', SnacksAPI.as_view()),
    # /snack/cbv/snack
    path('cbv/snack/<int:pk>/', SnackAPI.as_view()),
]

 

 

 

4. 적용 결과 확인

- 브라우저 주소창을 통해 요청하면 다음과 같이 DRF가 제공하는 UI를 통해 결과를 확인할 수 있다.

- 시리얼라이저의 내부 Meta 클래스의 fields에 선언한 필드들에 대한 데이터가 모두 보여질 것이다.

 

 

 

5. Nested Serializer

- nested serializer란 시리얼라이저 내에 들어가는 또 다른 시리얼라이저를 뜻한다. 하위 JSON 객체를 만들 때 사용하면 된다. 아래 코드는 공식 문서 https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers에 있는 내용이다.

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = ['order', 'title', 'duration']

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

    def create(self, validated_data):
        tracks_data = validated_data.pop('tracks')
        album = Album.objects.create(**validated_data)
        for track_data in tracks_data:
            Track.objects.create(album=album, **track_data)
        return album

 

[결과]

>>> data = {
    'album_name': 'The Grey Album',
    'artist': 'Danger Mouse',
    'tracks': [
        {'order': 1, 'title': 'Public Service Announcement', 'duration': 245},
        {'order': 2, 'title': 'What More Can I Say', 'duration': 264},
        {'order': 3, 'title': 'Encore', 'duration': 159},
    ],
}
>>> serializer = AlbumSerializer(data=data)
>>> serializer.is_valid()
True
>>> serializer.save()
<Album: Album object>

 

 

 

다음 포스팅 예고 - DRF 뷰 코드 개선 

사실 지금까지 작성한 DRF 뷰 코드는 이해를 돕기 위한 코드로, 프로젝트의 규모가 커져가면서 반복성이 짙어질 위험이 있다. 그리고, 이 페이지에서 POST 요청을 전송하려면 손수 JSON 문자열을 입력해야된다는 어려움이 있다.

만약 DRF 뷰에 믹스인을 적용한다면 메소드마다 반복적으로 작성해야 하는 코드가 대폭 줄고, 다음과 같이 사용자 친화적인 HTML 폼을 제공받을 수 있기 때문에 편리하게 POST 요청을 전송할 수 있다.

POST 요청이 아니어도 PUT, DELETE 요청도 쉽게 수행할 수 있는 환경을 제공해주기 때문에, 믹스인을 적용한다면 클래스형 뷰는 훨씬 더 강력해질 것이다. 게다가, 믹스인만이 DRF 뷰의 발전 과정의 끝이 아니다.

이번 포스팅은 DRF Serializer가 메인 주제였기 때문에, DRF 뷰의 발전 과정에 대한 내용은 아래 포스팅에 따로 설명했다. 앞서 설명한 믹스인 클래스, 그 이상으로 DRF 뷰 코드를 줄이기 위한 발전 과정에 대해 이해하고 있는 것이 좋기 때문에 아래 포스팅을 읽고 오는 것을 권장한다.

https://kimcoder.tistory.com/598

 

[DRF] DRF 뷰의 발전 과정

 

kimcoder.tistory.com

 

반응형

댓글