본문 바로가기

Python/Library

[Django] REST framework tutorial - Serialization

* 목차

- intro

- Set Environment

- Serialization을 위한 Model 작성

- Serializer class 만들기

- Serializer 사용하기

- ModelSerializers 사용하기

- Serializer를 Django View에서 활용하기

- Web API test

 


Intro

이번 시간에는 django Rest framework tutorial을 진행해보도록 하겠습니다. django를 주로 backend로 사용하고 있는 저로써는 web api를 구축할 때 django rest framework를 안쓸 수가 없습니다.

 

Rest framework는 아래와 같은 기능들이 포함됩니다.

 

- 웹 브라우징 가능한 API

- OAuth1a, OAuth2를 포함한 인증 정책

- ORM, non-ORM 데이터 소스를 모두 지원하는 Serialization

 

이 튜토리얼은 상당히 심층적으로, 시작하기 전에 많은 여유시간과 깊은 이해를 위한 간식을 간단히 준비하시기 바랍니다.

 

그럼 바로 시작해 보겠습니다.

 

 


Set Environment

먼저 python 가상환경 구성부터 진행해보겠습니다.

mkdir ~/rest_framework_practice
cd ~/rest_framework_practice

python3 -m venv .venv
. .venv/bin/activate

 

그리고 python library를 설치합니다.

pip install django
pip install djangorestframework
pip install pygments	## code highlighting을 위한 library

 

library 설치가 끝났으면 django project과 app을 하나 만들어줍시다. 저는 toy라는 이름으로 app을 만들었습니다.

django-admin startproject config .
django-admin startapp toy

 

config/settings.py의 INSTALLED_APPS에 toy app과 rest_framework를 추가해 주도록 합시다.

INSTALLED_APPS = [
    ...
    'rest_framework',
    'toy.apps.ToyConfig',
]

 

이제 환경 구성이 끝났습니다. Serialization를 시작해 보겠습니다.

 


Serialization을 위한 Model 작성

먼저 toy/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) ## if true, print line numbers
    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']

 

참고로 lexers나 styles는 이런 내용이 들어가 있습니다.

lexers

 

lang choices

 

style choices

 

model을 작성했으니 다음에는 snippet model을 migrate합니다.

python manage.py makemigrations
python manage.py migrate

 


Serializer class 만들기

snippet model의 데이터를 웹 API로 제공하기 위해서는 serializing, deserializing할 방법을 만들어야 합니다. 이를 위해서 Django의 형식과 매우 유사하게 동작하는 Serializer를 선언하면 됩니다.

 

먼저 toy/serializer.py 파일을 새로 만들고 내용을 채웁니다. 사실상 toy/models.py와 거의 비슷합니다.

from rest_framework import serializers
from .models import *


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=256)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANG_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

 

그리고 이 serializer를 통해 데이터를 생성, 수정하는 method를 만들어 붑시다.

from rest_framework import serializers
from .models import *


class SnippetSerializer(serializers.Serializer):
    ...

    def create(self, validated_data: dict):
        return Snippet.objects.create(**validated_data)
    
    def update(self, instance: Snippet, validated_data: dict):
        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

 

이전에 Django에서 Form을 작성해보셨던 분들은 Serializer가 친숙하게 다가왔을 겁니다. 특히 위의 {'base_tamplate': 'textarea.html'} 부분은 Form class에서 widget=widgets.Textarea와 동일합니다. 이 field는 API가 표시되는 방식을 제어하는데 특히 유용합니다.

 


Serializer 사용하기

1. Serializing

django의 shell에 접속해봅시다. python과 거의 동일하지만 django에서 사용되는 module들이 import되어 있습니다.

python manage.py shell

 

django shell에 들어온 모습이다.

 

먼저 Serializer를 사용하기 위한 module들을 import합니다.

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

 

그리고 2개의 sample data를 저장하겠습니다.

snippet = Snippet(code='foo = "bar"\n')
snippet.save()

snippet = Snippet(code='print("hello, world")\n')
snippet.save()

 

자 이제 serializer를 다뤄보겠습니다. 방금 저장한 data를 불러보겠습니다.

serializer = SnippetSerializer(snippet)
serializer.data

 

그러면 데이터를 python에서 지원하는 type 형태로 출력되는 것을 확인할 수 있습니다.

dict을 상속하는 ReturnDict class이다. 사실상 native dict이다.

 

이 dict data를 json으로 rendering해보겠습니다.

content = JSONRenderer().render(serializer.data)

 

DB(model) data -> python dict -> bytes로 type변환이 되었다.

 

2. Deserializing

계속 django shell에서 진행합니다. Deserializing은 Serializing의 역순입니다. 먼저 bytes type을 python native datatype으로 바꾸겠습니다.

import io

stream = io.BytesIO(content)
data = JSONParser().parse(stream)

 

그럼 다음과 같은 결과를 확인할 수 있습니다. bytes stream에서 python dict으로 변환했습니다.

bytes type이 python dict type data로 바뀌었다.

 

그리고 이 python datatype을 object instance으로 만들고 저장하겠습니다.

serializer = SnippetSerializer(data=data)
serializer.is_valid()        ## data 유효성 check
serializer.validated_data    ## data 확인
serializer.save()

 

Shell에서는 data유효성과 관련해서 출력되는 모습도 확인이 가능합니다.

Serializer를 통해 save까지 완료된 모습이다.

 

2개의 Sampledata를 만들고 하나를 추가로 저장했으니 데이터는 총 3개가 떠야 합니다. 실제로 DB(model)에 data가 추가가 되었는지 확인해 보도록 하겠습니다.

serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
len(serializer.data)

3이 뜨는 것을 확인할 수 있다.

 

앞으로 있을 code 반영 사항을 적용하기 위해 django shell을 종료합니다. 

exit()

 


ModelSerializers 사용하기

model과 Serializer가 매우 비슷하다보니 작성하는데 내용이 너무 겹치는게 아닌가 싶습니다. 이렇게 중복해서 작성하지 말라고 rest_framework는 ModelSerializer를 제공합니다.

 

toy/serializers.py내용을 ModelSerializers를 활용해서 다시 작성해봅시다.

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

 

다시 django shell을 엽니다. 그리고 SnippetSerializer가 어떻게 작성되어있는지 확인해 봅시다.

from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))

 

그럼 아래와 같은 결과가 나타납니다.

language와 style은 Choices항목이 매우 많아 출력이 길다.

 

심지어 기존에 작성한 create, update도 ModelSeializer에 포함되어 있습니다. 

update, create가 있다. 이는 위에서 작성했던 것과 동일하게 동작한다.

 


Serializer를 Django View에서 활용하기

Serializer를 작성했으니 이 class를 가지고 API view를 작성해 보겠습니다. 지금은 rest_framework의 추가적인 기능을 사용하지 않고 view를 작성합니다.

from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser

from .models import *
from .serializers import *

 

request Method가 GET이면 snippet의 목록을 보여주고 request Method가 POST면 snippet를 추가합니다.

csrf_exempt는 예제에서 동작만을 확인하기 위해 csrf_token없이 api를 접속할 수 있게 작성되었습니다. 실제 운영 단계에서는 csrf_token을 발행하여 api를 사용하기 합당한 user인지 검증하는 단계가 필요합니다.

@csrf_exempt
def snippets(request):
    if request.method == "GET":
        serializer = SnippetSerializer(Snippet.objects.all(), 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)

 

이번에는 snippet 전체가 아닌 하나의 객체만 조회, 추가, 삭제할 수 있도록 api를 구성합니다.

@csrf_exempt
def snippet_detail(request, pk):
    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)

 

마지막으로, api를 사용할 수 있는 url을 구성해 줍니다. toy/urls.py 파일을 생성 후, 아래와 같은 내용을 작성합니다.

from django.urls import path
from . import views

urlpatterns = [
    path('snippets/', views.snippets),
    path('snippets/<int:pk>/', views.snippet_detail),
]

 

그리고 toy/urls.py를 config/urls.py에 연결합니다.

from django.urls import path, include

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('', include('toy.urls')),
]

 

이제 api를 사용할 준비가 끝났습니다!!

 


Web API test

먼저 python django server를 시작합니다.

python manage.py runserver

 

그리고 chrome으로 접속해서 api url을 접속해 봅니다.

잘 동작하는 것을 알 수 있다.

 


마치며...

현재 API 보이는 json 응답을 제공하는 것 외에 특별히 특별한 기능을 수행하고 있지는 않습니다. 그리고 몇가지 오류 처리가 정리되지는 않았지만 동작은 제대로 하는 API입니다.

 

다음 글에서 어떻게 개선할 수 있는지 알아보겠습니다.

 


*reference

https://www.django-rest-framework.org/tutorial/1-serialization/

 

1 - Serialization - Django REST framework

This tutorial will cover creating a simple pastebin code highlighting Web API. Along the way it will introduce the various components that make up REST framework, and give you a comprehensive understanding of how everything fits together. The tutorial is f

www.django-rest-framework.org

 

'Python > Library' 카테고리의 다른 글

[Redis] - Tutorial with Python  (4) 2024.09.21
[Alembic] - Tutorial  (7) 2024.09.20
[Django] REST framework tutorial - Requests and Responses  (0) 2023.06.23
[Flask] - Quickstart  (0) 2023.04.17
[Flask] - Tutorial  (0) 2023.04.17