이번 튜토리얼에서는 Django REST Framework 사용법에 대해 알아본다. 튜토리얼의 대부분은 Django REST Framework 공식 홈페이지의 튜토리얼을 번역한 것이며, 장고를 어느정도 다룰 줄 아는 사람들을 대상으로 하므로 기본적인 장고에 대한 설명은 생략한다.
기본 세팅
이제부터 Pastebin 이라는 신텍스 하이라이팅 웹 서비스를 Django Rest Framework
를 사용해서 만들어 볼 것이다.
먼저 필요한 패키지들을 설치한다.
pip install django
pip install djangorestframework
pip install pygments # 이 패키지는 코드 하이라이팅에 사용될 패키지이다.
패키지를 설치한 다음 튜토리얼에 사용할 장고 프로젝트를 하나 시작하고 기본 세팅을 해주자.
프로젝트 이름은 rest_practice
라고 지어주었다.
django-admin startproject rest_practice
다음으로 snippets
라는 이름의 앱을 하나 추가해준다.
snippet은 작은 정보
, 토막
등의 뜻을 가지고 있다.
./manage.py startapp snippets
세팅을 끝내고 난 프로젝트의 모습은 아래와 같다.
django_rest_practice
├── README.md
├── requirements.txt
└── rest_practice
├── config
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── snippets
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
settings.py
의 INSTALLED_APPS
에 Rest 패키지와 snippets 앱을 추가해주자.
# settings.py
...
INSTALLED_APPS = [
...
`rest_framework`,
`snippets`
]
Serialization
Model 만들기
이제 코드 토막들을 담을 간단한 모델을 하나 만들어보자.
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',)
모델을 작성했으면 migrate
를 해준다.
./manage.py makemigration snippets
./manage.py migrate
Serializer 클래스 만들기
이제 Snippet 모델의 인스턴스를 직렬화 (Serialize)
하고 역직렬화 (Deserialize)
할 수 있는 방법을 만들어주어야한다.
갑자기 이게 무슨 소린가 싶은 분들(나 포함)을 위한 설명이 필요한 시점인 것 같다.
직렬화
직렬화(直列化) 또는 시리얼라이제이션(serialization)은 컴퓨터 과학의 데이터 스토리지 문맥에서 데이터 구조나 오브젝트 상태를 동일하거나 다른 컴퓨터 환경에 저장(이를테면 파일이나 메모리 버퍼에서, 또는 네트워크 연결 링크 간 전송)하고 나중에 재구성할 수 있는 포맷으로 변환하는 과정이다.
오브젝트를 직렬화하는 과정은 오브젝트를 마샬링한다고도 한다. 반대로, 일련의 바이트로부터 데이터 구조를 추출하는 일은 역직렬화 또는 디시리얼라이제이션(deserialization)이라고 한다. 출처: 위키피디아
직렬화는 쉽게 말해서 서로 다른 두 환경이 서로 소통하기 위해 데이터를 두 환경 모두에서 사용가능한 형태로 변환하는 것을 말한다.
예를 들어서, 한국사람이 스페인사람에게 인사를 하려고 할 때, ‘안녕하세요’ 라고 말하면 한국말을 알아들을 수 없는 스페인사람은 당연히 뭐라고 하는지 알 수가 없다.
이 때, 안녕하세요라고 하는 대신 만국 공통어인 영어로 ‘Hello’ 라고 하면 스페인사람이 ‘아 이 사람이 인사를 하는구나’ 하고 알아들을 수 있을 것이다.
많은 프로그래밍 언어들이 Byte
형 데이터를 다룰 수 있으므로 보통 직렬화를 할 때 데이터를 Byte 형식으로 변환한다.
그리고 http 통신을 할 때에는 주로 XML
이나 JSON
형식의 데이터를 쓴다고 한다.
이 튜토리얼에서는 JSON 형식으로 데이터를 직렬화 할 것이다.
장고 Rest Framework는 직렬화를 위해 Serializer
라는 클래스를 제공한다.
이 클래스는 장고의 form
클래스와 비슷하게 작동한다.
snippets
폴더 아래에 serializer.py
를 만들고 아래와 같이 입력한다.
# snippets.py
from rest_framework import serializers
from snippets.models import LANGUAGE_CHOICES, STYLE_CHOICES, Snippet
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):
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.title = validated_data('title', instance.title)
instance.code = validated_data('code', instance.code)
instance.linenos = validated_data('linenos', instance.linenos)
instance.language = validated_data('language', instance.language)
instance.style = validated_data('style', instance.style)
instance.save()
return instance
Serializer 다루기
직렬화 (Serialization)
이제 만들어준 Serializer가 잘 작동하는지 확인하기 위해 직접 하나의 Snippet 객체를 직렬화해서 JSON 형 데이터로 바꾸는 테스트를 해보자.
shell_plus
를 활용하면 손쉽게 테스트 해볼 수 있으므로 django_extensions
패키지를 설치하고 settings.py
에 추가해주자.
pip install django_extensions
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'django_extensions',
...
]
아래 명령어로 셸 플러스를 실행한다.
./manage.py shell_plus
셸에서 우선 아래를 입력해서 필요한 객체들을 불러와 준다.
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
이제 아래와 같이 입력해서 Snippet 객체를 하나 만들어 보자.
s = Snippet.objects.create(code="print('hello, world')")
<Snippet: Snippet object>
이제 이 Snippet 객체를 Serializer로 직렬화 해준다.
serialized = SnippetSerializer(s)
serialized.data
ReturnDict([('id', 1),
('title', ''),
('code', "print('hello, world')"),
('linenos', False),
('language', 'python'),
('style', 'friendly')])
위 작업의 결과로 Snippet 모델 객체가 Python 딕셔너리 자료형으로 변환되었다.
이제 직렬화의 마지막 단계로 이 딕셔너리 자료형을 JSON형 데이터로 변환시킨다.
content = JSONRenderer().render(serialized.data)
content
b'{"id":1,"title":"","code":"print(\'hello, world\')","linenos":false,"language":"python","style":"friendly"}'
위 작업의 결과로 딕셔너리형 데이터가 JSON형 데이터로 변환되었다.
직렬화라는 이름에 걸맞게 데이터가 한 줄로 쭉 쓰여있는 것을 볼 수 있다.
앞의 b
는 이 데이터가 바이트형 데이터임을 뜻한다.
바이트는 아무런 인코딩이 되어있지 않은 데이터이다. Python 문자열 자료형은 바이트 데이터를 UTF-8
로 인코딩한 것이다.
b'{"id":1,"...
는 ‘’ 로 감싸져 있지만 바이트 데이터이고 그 형식은 JSON 형식이다.
얼핏보면 딕셔너리 자료형같이 생겼지만 JSON은 작은 따옴표(‘’) 를 쓸 수 없고 큰 따옴표(“”) 로만 키, 벨류 값을 입력할 수 있다.
지금까지 한 것을 정리해보자.
먼저 Snippet 모델의 인스턴스를 하나 생성했다.
그리고 이 인스턴스를 SnippetSerializer
를 이용해서 파이썬 딕셔너리 데이터로 변환했다. 마지막으로 파이썬 딕셔너리 데이터를 JSONRenderer
를 이용해서 JSON 데이터로 변환했다.
장고 모델 객체 > 파이썬 딕셔너리 > JSON 데이터
파이썬 딕셔너리로 변환하는 중간 단계를 거치는 이유는 JSON 데이터로 변환하기 위해서는 딕셔너리형 자료가 필요하기 때문이다.
실제로 JSON 데이터와 파이썬 딕셔너리 데이터는 매우 흡사하게 생겼기 때문에 변환도 간편하다.
역직렬화 (Deserialization)
역직렬화는 JSON 데이터를 거꾸로 장고 모델 객체로 변환하는 작업이다.
역직렬화가 언제 필요하나면 클라이언트 쪽에서 데이터를 전달해왔을 때 필요하다.
방금 우리가 테스트 용으로 만든 Snippet 인스턴스는 그냥 ORM을 통해서 임의로 만든 데이터이지만 실제 웹 서비스의 데이터들은 모두 서비스 사용자들이 입력한 데이터들이다.
그런 경우는 프론트엔드를 통해서 데이터가 넘어오기 때문에 JSON 형식으로 전달될 것이고 이것을 역직렬화를 통해 파이썬 장고가 알아들을 수 있는 객체로 변환해주어야 한다.
아까전에 직렬화했던 데이터를 다시 역직렬화해보자.
역직렬화에는 BytesIO
라는 모듈이 필요하다.
from django.utils.six import BytesIO
BytesIO가 하는 일은 바이트형 자료를 파일 형태로 메모리에 저장한다고 한다.
자세히 이해가 안되어서 나중에 자세한 설명을 추가하도록 하겠다.
아무튼 아래와 같이 입력해서 바이트 상태인 JSON 데이터를 메모리로 불러와준다.
stream = BytesIO(content)
stream
<_io.BytesIO at 0x7f841f3d9fc0>
이제 이 바이트 데이터를 JSONParser
를 이용해서 파이썬 딕셔너리로 바꿔준다.
data = JSONParser().parse(stream)
{'code': "print('hello, world')",
'id': 1,
'language': 'python',
'linenos': False,
'style': 'friendly',
'title': ''}
이제 이 딕셔너리 데이터를 가지고 Snippet 객체를 생성하면 된다.
먼저 딕셔너리 데이터를 SnippetSerializer
에 집어넣고 is_valied
함수를 통해 전달된 데이터가 모델의 구조에 맞는지 유효성 검사를 시행한다.
serializer = SnippetSerializer(data=data)
serializer.is_valid()
True
데이터가 유효하다면 입력된 데이터들은 validated_data
속성에 저장된다.
serializer.validated_data
OrderedDict([('title', ''),
('code', "print('hello, world')"),
('linenos', False),
('language', 'python'),
('style', 'friendly')])
이제 save()
메서드를 호출해서 validated_data
안의 내용 그대로를 가지고 Snippet
인스턴스를 생성한다.
serializer.save()
<Snippet: Snippet object>
그러면 이제 데이터베이스에 클라이언트가 넘겨준 데이터가 저장된 것이다.
Reference
이한영 강사님 강의자료
Django Rest Framework 공식 문서: http://www.django-rest-framework.org/tutorial/1-serialization/
Nesoy 블로그: https://nesoy.github.io/articles/2017-02/JSON
kshyun87 블로그: http://kshyun87.tistory.com/14