Django restframework(DRF) - Serializers

정현우·2021년 10월 23일
3

Django Basic to Advanced

목록 보기
17/37

DRF Serializers

serializer에 대해 심플하게 알아보자. 공식 튜토리얼 문서를 따라간다. 탄생과 원리에 대한 좀 더 깊은 얘기는 "serializer 파헤치기" 글에서 다루니, 해당 시리즈의 글에서 찾아주시면 감사합니다!

  • 이전 글에서 말했듯이 serializers(직렬화)가 의미하는 바는 단순하게 쿼리셋, 모델 인스턴스 등의 복잡한 데이터를 JSON, XML 등의 컨텐트 타입으로 쉽게 변환 가능한 python datatype으로 변환이라고 했다.

  • 왜 직렬화가 필요할까? 우리가 API를 만들고 response를 줄 때는 code level에서 object 존재하는 json으로 바꿔서 restful하게 일관된 data type으로 줘야한다.

  • request를 받을때 역시 동일하다. restful한 API라면 request data type은 json이다. 이 json을 다시 code level에서 object로 casting을 해 줘야 한다.

serializers

  • 들어가기전, 어떤 것이 json type으로 바꿔주는 것인지, 어떤 것이 json을 python이 이해가능한 dict형태로 바꿔주는 것인지, json.loads와 json.dumps를 생각하고 넘어가자!!

  • 우선 pygments 먼저 설치하자. 얘가 하는 역할은 여기서 확인 / 그냥 코드 하이라이터다.. 튜토리얼에서 사용한다. pip install pygments # We'll be using this for the code highlighting

model 추가

  • qucikstart/models.py에 "Snippet"이라는 모델을 만들어주자.
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

# pygments config
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])


# Create your models here.
class Person(models.Model):
    first_name=models.CharField(max_length=30)
    last_name=models.CharField(max_length=30)


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    linenos = models.BooleanField(default=False)
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
    class Meta:
        ordering = ['created']
  • 마이그레이션을 진행해주자!
> python manage.py makemigrations quickstart
Migrations for 'quickstart':
  quickstart/migrations/0002_snippet.py
    - Create model Snippet
> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, quickstart, sessions
Running migrations:
  Applying quickstart.0002_snippet... OK

serializders 추가

... # 생략
class 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=models.LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=models.STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return models.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
  • serializers.Serializer 을 extends한다. 그리고 create와 update를 오버라이딩 하고 있다. serializer class를 공식문서에서는 Django Form Class와 가장 유사하다고 표현하고 있다. 특히 validation flags on the various fields, such as required, max_length and default.

  • 선언한 모델과 시리얼라이저를 shell에서 테스트해보자.

from quickstart.models import Snippet
from quickstart.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

# 모델을 2개 만들고 
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print("hello, world")\n')
snippet.save()
  • 만든 모델을 "시리얼라이즈(직렬화)해보자!". dict와 같다. 접근도 동일하게 가능하다.
serializer = SnippetSerializer(snippet)
serializer.data

# 아래와 같이 출력된다. 
{'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

>>> serializer.data['title']
''
>>> serializer.data['code']
'print("hello, world")\n'
>>> serializer.data['linenos']
False
  • 그러면 그 데이터를 JSON타입으로 바꾸고, 다시 시리얼라이징을 하면 어떨까? JSONRenderer 라는 렌더러를 DRF에서는 제공하고 있다. json.dumps를 통해서 "직렬화" 해준다. 그리고 다시 직렬화된 데이터를 io 라이브러리를 활용해 JSONParser로 python object로 가져오자.
content = JSONRenderer().render(serializer.data)
content

# 아래와 같이 출력된다. 
b'{"id":2,"title":"","code":"print(\\"hello, world\\")\\n","linenos":false,"language":"python","style":"friendly"}'


import io

stream = io.BytesIO(content)
data = JSONParser().parse(stream)
>>> type(data)
<class 'dict'>
data

# 아래와 같이 출력된다. 
{'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}


>>> serializer = SnippetSerializer(data=data)
>>> serializer.is_valid()
True
>>> serializer.validated_data
OrderedDict([('title', ''), ('code', 'print("hello, world")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
>>> serializer.save()
>>> serializer.data
{'id': 3, 'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}
  • Snippet모델의 objects.all()로 모든 객체를 가져오고 살펴보자.
>>> serializer = SnippetSerializer(Snippet.objects.all(), many=True)
>>> serializer.data
[OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print("hello, world")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
>>> type(serializer.data)
<class 'rest_framework.utils.serializer_helpers.ReturnList'>

ModelSerializer 사용하기

  • 위에서는 모델에서 Snippet 정의한 필드들, 값들을 serializers에서 하나하나 추가해 줬다. 하지만 serializers.ModelSerializer를 extends해서 훨씬 더 간결하게 할 수 있다. 코드리펙토링을 가볍게 진행해 보자.
... # 생략
class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Snippet
        fields = ['id', 'title', 'code', 'linenos', 'language', 'style']
... # 생략 
  • 그리고 다시 python manage.py shell로 들어가보자! 참고로 repr은 python object에서 __str____repr__ 두 개의 차이점이 있다는 걸 알아야한다! 차이점 살펴보기 문서 체크 고고
>>> from quickstart.serializers import SnippetSerializer
>>> serializer = SnippetSerializer()
>>> print(repr(serializer))
SnippetSerializer():
    id = IntegerField(read_only=True)
    title = CharField(allow_blank=True, max_length=100, required=False)
    code = CharField(style={'base_template': 'textarea.html'})
    linenos = BooleanField(required=False)
    language = ChoiceField(choices=[('abap', 'ABAP'), ('abnf', 'ABNF'), ... # 생략 
    
>>> print(repr(serializer.data))
{'title': '', 'code': '', 'linenos': False, 'language': None, 'style': None}
  • 위와 같은 방식으로 해도 모든 필드를 검사하며, 자동으로 구성한다는 점이 포인트다!

Writing regular Django views using our Serializer

  • django에서 DRF(django restframework)없이 일반적인 View(api)를 어떻게 구성해야하는지 먼저 살펴보자.
# quickstart/views.py
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser # 그냥 json 파싱만 위해,,

@csrf_exempt
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)


@csrf_exempt
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

... # 생략
  • 실제로 csrf_exempt 데코레이팅을 사용하진 않는다! 예시로 사용함! CSRF 토큰이 없는 클라이언트에서 이 보기에 POST할 수 있게 하기 위해 csrf_exempt 를 사용한것! 그리고 urls.py에 아래 두 가지만 추가하자!
... # 생략
urlpatterns = [
    ... # 생략
    # without router
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>', views.snippet_detail),
]


  • 기본적으로, Django에서는 각각의 모델에 id필드를 자동으로 추가해준다. 다만, primary_key가 명시되어있는 컬럼이 있을경우 추가하지 않는다. 자동추가되는 id필드는 auto_increment integer field이다.

  • 이렇게 간단하게 Serializer 에서 알아 봤다. 다음 게시글에서 crud와 더 깊은 고찰을 하는 글을 다룰 예정이니 해당 글까지 참고해 주시면 너무 감사합니다!

profile
도메인 중심의 개발, 깊이의 가치를 이해하고 “문제 해결” 에 몰두하는 개발자가 되고싶습니다. 그러기 위해 항상 새로운 것에 도전하고 노력하는 개발자가 되고 싶습니다!

0개의 댓글