https://www.django-rest-framework.org/api-guide/serializers/
https://www.django-rest-framework.org/tutorial/1-serialization/#setting-up-a-new-environment
Serializers이란?
querysets and model instances를 JSON, XML과 같은 content type으로 쉽게 바꿀 수 있게 native Python datatypes으로 바꿔주는 역할을 한다.
나의 환경
Windows 10 HOME
python 3..8.1
개발환경 세팅
> python -m venv venv
> venv\Scripts\activate
> pip install django
> pip install djangorestframework
> pip install pygments # for the code highlighting
다음과 같이 설치되었다. (requirements.txt)
asgiref==3.2.7
Django==3.0.5
djangorestframework==3.11.0
Pygments==2.6.1
pytz==2019.3
sqlparse==0.3.1
Getting Started
simple pastebin code highlighting Web API를 만들어보자
프로젝트를 만들고 web api를 만들 app을 만들자
> django-admin startproject tutorial
> cd tutorial
> python manage.py startapp snippets
그리고 설정파일에 rest framework와 새로 만든 app을 등록하자
# tutorial/settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'snippets.apps.SnippetsConfig'
]
Model 만들기
# snippets/models.py
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
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()])
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']
snippet model에 대해 initial migration을 하고, DB를 동기화하자
> python manage.py makemigrations snippets
> python manage.py migrate
Serializer class 만들기
snippet instances를 json과 같은 representations으로 serializing & deserializing 하는 방법을 제공해보자
이를 위해서는 Django의 forms와 비슷하게 동작하는 serializers를 선언함으로써 가능하다.
snippets/serializers.py를 만들고 다음 코드를 넣자
# snippets/serializers.py
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
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=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
- create(), update() method를 통해 serializer.save()를 호출할 때 어떻게 instances가 생성되고 수정될 수 있는지를 정의한다.
- 여러 field flag가 있는데, 이를 통해 serializer이 어떻게 특정 상황에 어떻게 보여져야하는지를 통제할 수 있다.
예) HTML로 만들 때
- {'base_template': 'textarea.html'} flag 같은 경우에는 browsable API가 어떻게 보여져야하는지와 같은 상황에서 유용하다. 나중 튜토리얼에서 다룰 예정!
Serializers 다루기
Serializers class에 익숙해보자.
> python manage.py shell
다룰 snippets를 준비해보면
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print("hello, world")\n')
snippet.save()
serializing을 해보면(model instance를 Python native datatypes로)
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
data를 json으로 바꿔보자
content = JSONRenderer().render(serializer.data)
content
# b'{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}'
deserialization을 해보자
stream을 Python native datatypes로 parse 하고 fully populated object instance로 다시 저장하자
import io
stream = io.BytesIO(content)
data = JSONParser().parse(stream)
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>
참고로~
model instance 대신 querysets을 serialize할 수 있다. many=True flag로 간단히 해결할 수 있다.
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')])]
ModelSerializers 사용하기
SnippetSerializer class는 Snippet model에도 있는 정보를 여러개 만든다. 만약 우리가 코드를 더 간결하게 만들면 좋을 것이다.
Django가 Form class와 ModelForm class를 제공하는 것처럼, REST framework도 Serializer class와 ModelSerializer class를 제공한다.
snippets/serializers.py를 ModelSerializer class를 사용하는 것으로 바꿔보자.
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ['id', 'title', 'code', 'linenos', 'language', 'style']
serializer이 가지고 있는 좋은 속성 중 하나는,
serializer instance에 있는 fields를 representation을 프린트함으로써 점검할 수 있다는 것이다.
django shell을 열고 확인해보자.
from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
# id = IntegerField(label='ID', 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=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
# style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
ModelSerializer은 뭔가 엄청난 일을 하는게 아니라,
serializer class를 만드는 shortcut일 뿐이다.
Serializer을 이용해서 regular Django views 작성하기
snippets/views.py를 다음과 같이 작성하자
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@csrf_exempt
def snippet_list(request):
"""
List all code snippets, or create new snippet.
"""
if request.method == 'GET': # snippets 모두 불러오기
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True) # queryset을 serialize 하기
return JsonResponse(serializer.data, safe=False) # json으로 return
elif request.method == 'POST': # snippet 새로 만들기
data = JSONParser().parse(request) # python native type으로 parsing
serializer = SnippetSerializer(data=data) # object instance로 serialize
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) # 특정 snippet 가져오기
except Snippet.DoesNotExist: # 일치하는 snippet이 없을 경우
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하는 경우를 대비하여 사용한다.
원래 REST framework view는 이보다 나은 방식으로 동작하지만, 일단은 이렇게 임시방편으로 한다.
snippets/urls.py 파일을 만들고 연결하자
from django.urls import path
from snippets import views
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>/', views.snippet_detail),
]
tutorial/urls.py도 수정하자
from django.urls import path, include
urlpatterns = [
path('', include('snippets.urls')),
]
테스트하기
> python manage.py runserver
server이 run하고 있는 상태에서 다른 터미널을 열어서 curl이나 httpie를 통해 API를 테스트할 수 있다.
httpie는 user friendly한 Python으로 쓰인 http client이기에 이를 사용해보도록 하자
> pip install httpie
다음과 같이 명령어를 입력하면 snippets 리스트를 얻을 수 있다
> http http://127.0.0.1:8000/snippets/
그 중 하나를 선택해서 볼 수도 있다
> http http://127.0.0.1:8000/snippets/2/
추가적인 에러처리를 하면 좋겠지만, 이렇게 기본적인 API 만들기는 완성하였다!