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을 해 줘야 한다.
pip install pygments # We'll be using this for the code highlighting
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
... # 생략
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()
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
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'}
>>> 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'>
... # 생략
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}
# 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)
... # 생략
... # 생략
urlpatterns = [
... # 생략
# without router
path('snippets/', views.snippet_list),
path('snippets/<int:pk>', views.snippet_detail),
]
http://127.0.0.1:8000/api/snippets/ 로 접근해서 obejct.all은 잘 하는지 테스트하자. restapi test 도구로는 필자는 인섬니아를 추천한다.
우리가 snippet_list는 GET / POST, snippet_detail는 pk를 파라미터값으로 가져와 'GET', 'PUT', 'DELETE'를 한다.
POST : http://127.0.0.1:8000/api/snippets/ 만 테스트를 해보면, 아래와 같다.
기본적으로, Django에서는 각각의 모델에 id필드를 자동으로 추가해준다. 다만, primary_key가 명시되어있는 컬럼이 있을경우 추가하지 않는다. 자동추가되는 id필드는 auto_increment integer field이다.
이렇게 간단하게 Serializer
에서 알아 봤다. 다음 게시글에서 crud와 더 깊은 고찰을 하는 글을 다룰 예정이니 해당 글까지 참고해 주시면 너무 감사합니다!