[drf] Tutorial 1: Serialization

Hyeseong·2021년 2월 16일
0

DRF

목록 보기
1/4

튜토리얼 1: Serialization

서론

이 튜토리얼은 심플하게 하이라이팅 된 페이스트빈 코드를 다룰 것입니다.

그 과정에서 REST 프레임 워크를 구성하는 다양한 구성 요소를 소개하고 모든 것이 어떻게 결합되는지에 대한 포괄적인 이해를 제공합니다.

튜토리얼은 상당히 심층적이므로 시작하기 전에 쿠키와 좋아하는 맥주 한 잔을 가져와야합니다. 빠르게 훑어 보기 원한다면 quickstart 문서로 넘어가세요.


참고 :이 가이드의 코드는 GitHub 의 encode / rest-framework-tutorial 저장소에서 사용할 수 있습니다. 완성 된 구현은 테스트 용 샌드 박스 버전으로 온라인으로 제공되며 여기 에서 사용할 수 있습니다 .


환경 설정

python -m venv env
source env/Scripts/activate (windows)
or
source env/bin/activate (linux)

패키지 설치

pip install django
pip install djangorestframework
pip install pygments # code하이라이팅에 사용됨

시작

cd ~
django-admin startproject tutorial
cd tutorial

python manage.py startapp snippets

여기까지 잘 했나요?
그럼 아래에서는 snippets 앱을 장고 앱으로 등록할게요.

INSTALLED_APPS = [
    ...
    'rest_framework',
    'snippets.apps.SnippetsConfig',
]

이제 굴러 갈 준비가 어느정도 되었군요.

모델 작성

Snippet모델을 만들어 볼게요.
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']

Serializer class 생성

json으로 만들기 위해서 snippet 인스턴스를 직렬화 및 역직렬화를 해줘야해요. 특히 serializer 선언은 django form과 그 동작이 비슷해요.

직렬화 및 역직렬화 개념

우선 아래와 같이 파일을 작성 할게요.
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

serializer 클래스는 우선 직렬화 / 역 직렬화되는 필드를 정의합니다. create () 및 update () 메서드는 serializer.save () 호출시 완전한 인스턴스가 생성되거나 수정되는 방식을 정의합니다.

그리고 serializer는 장고 Form 클래스와 같은점이,
required, max_length및 default과 같은 다양한 유효성 검사 플래그를 쓰는 점이 특징이기도해요.

HTML 랜더링시에도 이런 필드 플래그는 시리얼라이즈를 control하게합니다.
위의 {'base_template': 'textarea.html'} flag는 장고 Form 클래스의 widget=widgets.Textarea 동일합니다. 튜토리얼에서 확인하겠지만 browsable API에서 디스플레이될때 특히 유용하게 controling할수 있어요.

Serializers 작업

python manage.py shell

아래 클래스를 이용하여 인스턴스를 만들어 볼게요.

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()

snippet 인스턴스를 이용해서 직렬화를 살펴볼게요.

serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")

위에서 파이썬 데이터 타입인 bytes로 변경하였는데로 마무리로 data를 json화 시킬게요.

content = JSONRenderer().render(serializer.data)
content
# b'{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}'

역직렬화 역시 유사합니다. 우선 stream을 Python 고유 데이터 타입으로 파싱할게요.

import io

stream = io.BytesIO(content)
stream
#  <_io.BytesIO at 0x262860bcb30>

data = JSONParser().parse(stream)
data
#{'id': 3,
# 'title': '',
# 'code': 'print("안녕! 내이름은 이혜성이야")\n',
# 'linenos': False,
# 'language': 'python',
# 'style': 'friendly'} 

이제 파이썬 데이터 타입을 객체인스턴스로 저장할게요.

serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
#OrderedDict([('title', ''),
#             ('code', 'print("안녕! 내이름은 이혜성이야")'),
#             ('linenos', False),
#             ('language', 'python'),
#             ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>

정말 forms과 API가 유사합니다. 특히 뷰를 작성할 때 그 유사성이 더 명확하다는 걸 확인 할 수 있어요.
또한 model instance대신에 querset을 직렬화 할 수 있어요. 그냥 단순히 many=True flag를 SnippetSerialzer() 클래스의 키워드 인자로 사용하면 되요.

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 클래스는 Snippet 모델에도 포함 된 많은 정보를 복제합니다.
코드를 좀 더 간결하게 유지할 수 있는 것이 ModelSerializer입니다.

Django가 Form 클래스와 ModelForm 클래스를 모두 제공하는 것과 같은 방식으로 REST 프레임 워크에는 Serializer 클래스와 ModelSerializer 클래스가 모두 포함됩니다.

ModelSerializer 클래스를 이용해서 리팩토링을 진행할게요. snippets/serializers.py을 열어 볼게요. 그리고 SnippetSerializer클래스를 아래와 같이 바꿀게요.

class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ['id', 'title', 'code', 'linenos', 'language', 'style']

serializer가 갖는 한 가지 좋은 속성은 해당 표현을 출력하여 serializer 인스턴스의 모든 필드를 검사 할 수 있다는 것입니다.

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 클래스를 만드는 지름길 일뿐입니다.

  • 자동으로 결정된 필드 집합입니다.
  • create()update() 메서드에 대한 간단한 기본 구현입니다.

일반적인 장고 views를 이용한 Serializer작성

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
The root of our API is going to be a view that supports listing all the existing snippets, or creating a new snippet.

@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 토큰이없는 클라이언트가 view에서 POST 할 수 있기를 때문에 뷰를 csrf_exempt로 데코레이터를 박아줘야해요.
또한 단건 조회와 수정 또는 삭제에 해당하는 snippet 을 만들어 볼게요.

@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)

위 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의 snippet을 URL에 포함시키기 위해서 프로젝트 폴더의 urlconf를 연결해줘야해요.

from django.urls    import include, path
from rest_framework import routers
from quickstart     import views

urlpatterns = [
    path('',  include('snippets.urls')),

]

현재 위에 작성한대로 한다면 몇가지 다루지 못하는 엣지한 경우가 있어요. 만약 json형식의 오류거나 뷰에서 다룰서 없는 request인 경우를 말이조. 그런 경우 500 error를 통상 뱉어내지만. 지금은 일단 할게요.(학습을 위해서)

WEB API 테스팅 첫 시도

쉘을 켰다면 꺼주고 이제는 서버 올려주세요. curl or httpie 이용해서 실습할 수 있습니다. 참고로 Httpie의 경우 파이썬에서 User friendly하게 http client를 위해 작성된 거에요.

만약 안깔려 있다면 설치해주세요.

pip install httpie

터미널에서 아래 명령어를 입력하면 response를 받게되요.

❯ http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
Content-Length: 413
Content-Type: application/json
Date: Thu, 18 Feb 2021 18:59:47 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.9.1
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

[
    {
        "code": "foo = \"bar\"\n",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
    {
        "code": "print(\"hello, world\")\n",
        "id": 2,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
    {
        "code": "print(\"안녕! 내이름은 이혜성이야\")\n",
        "id": 3,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    }
]

단건으로 호출해 볼까요?

❯ http http://127.0.0.1:8000/snippets/1/
HTTP/1.1 200 OK
Content-Length: 110
Content-Type: application/json
Date: Thu, 18 Feb 2021 19:09:08 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.9.1
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "code": "foo = \"bar\"\n",
    "id": 1,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}

결론

지금까지는 문제없이 Django의 Forms API, 그리고 몇가지 일반적 Django view들과 유사한 serializationAPI를 짜봤어요.

현재 작성한 API는 json응답 제공 외에는 특별한 작업을 수행하지는 않아요. 여전히 처리하고 싶은 오류 핸들링 부분이 있지만 그래도 굴러가는 Web API에요.

tutorial2에서는 좀더 향상된 모습으로 진행해보조.

profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글