본문 바로가기
Django

DRF Tutorial 1 Serialization

by AlbertIm 2024. 9. 11.

시작

DRF(Django Rest Framework) 튜토리얼을 따라 Serialization에 대해 알아보려고 합니다. 본 포스트에서는 macOSVS Code를 사용하여 진행합니다.

본문

프로젝트 생성 및 설정

먼저 프로젝트를 생성하고 필요한 설정을 진행합니다. 다음 명령어를 차례대로 실행하여 Django와 Django Rest Framework(DRF)를 설치하고 프로젝트를 생성합니다.

# 'drf_tutorial'이라는 디렉터리를 생성합니다.
mkdir drf_tutorial

# 'drf_tutorial' 디렉터리로 이동합니다.
cd drf_tutorial/

# 가상 환경(venv)을 생성합니다.
python3 -m venv venv

# 가상 환경을 활성화합니다.
source venv/bin/activate

# Django 패키지를 설치합니다.
pip install django

# Django Rest Framework 패키지를 설치합니다.
pip install djangorestframework

# Pygments 패키지를 설치합니다. 
# (Pygments는 코드 하이라이팅을 위한 라이브러리입니다.)
pip install pygments

# Django 프로젝트를 'config'라는 이름으로 시작합니다. 
# (현재 디렉터리에 생성되도록 '.'을 추가)
django-admin startproject config .

# Visual Studio Code로 프로젝트를 엽니다.
code .

# 'snippets'라는 이름의 새로운 Django 앱을 생성합니다.
python manage.py startapp snippetes

settings.py 파일 수정

DRF를 사용하기 위해 settings.py 파일에서 필요한 앱들을 추가해야 합니다. INSTALLED_APPSrest_framework와 새로 생성할 snippets 앱을 추가합니다.

INSTALLED_APPS = [

    ...

    'rest_framework', # DRF 추가

    'snippets.apps.SnippetsConfig', # snippets 앱 추가

]

모델 구현

먼저 snippets/models.py 파일에서 Snippet 모델을 정의합니다. 이 모델은 코드 스니펫을 저장하는 역할을 합니다

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

# Pygments에서 사용할 수 있는 lexers(언어 목록)를 필터링하여 선택 가능한 언어 리스트를 만듭니다.
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 모델을 정의한 후 이를 데이터베이스에 반영하기 위해 마이그레이션을 수행합니다.

python manage.py makemigrations snippets
python manage.py migrate snippets

Serializer class 생성

이제 snippets 디렉터리 아래에 serializers.py 파일을 생성하여, Snippet 모델에 대한 Serializer 클래스를 만듭니다. Serializer는 데이터를 JSON 형태로 변환해주며 또한 유효성 검사를 수행합니다.

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):
        """
        유효성 검사를 통과한 데이터를 이용해 새로운 `Snippet` 인스턴스를 생성합니다.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        기존의 `Snippet` 인스턴스를 업데이트하고 저장한 후 반환합니다.s
        """
        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를 활용해 데이터베이스와 JSON 간의 변환을 확인합니다. 먼저 Django Shell을 열고 필요한 데이터를 생성하고 직렬화(Serialization) 작업을 수행해 보겠습니다.

1. Django Shell 실행

python manage.py shell

2. Snippet 데이터 생성 및 저장

Django Shell에서 아래 명령어를 하나씩 입력하여 Snippet 객체를 생성하고 저장합니다.

  • Snippet 모델을 불러옵니다.
  • from snippets.models import Snippet
  • SnippetSerializer를 불러옵니다.
  • from snippets.serializers import SnippetSerializer
  • JSON으로 직렬화할 때 사용할 렌더러를 불러옵니다.
  • from rest_framework.renderers import JSONRenderer
  • JSON 데이터를 파싱할 때 사용할 파서를 불러옵니다.
  • from rest_framework.parsers import JSONParser
  • 새로운 스니펫을 생성하고 저장합니다.
  • snippet = Snippet(code='foo = "bar"\n') snippet.save()
  • 또 다른 스니펫을 생성하고 저장합니다.
  • snippet = Snippet(code='print("hello, world")\n') snippet.save()

3. Serializer를 사용해 데이터 직렬화

SnippetSerializer를 사용하여 데이터베이스에서 가져온 snippet 객체를 직렬화합니다. 직렬화된 데이터는 파이썬의 기본 데이터 형식으로 반환됩니다.

  • 마지막에 저장된 Snippet 객체를 직렬화합니다.
  • serializer = SnippetSerializer(snippet)
  • 직렬화된 데이터를 출력합니다.
  • serializer.data # 결과: # {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

4. JSON으로 변환

serializer.data로 얻은 직렬화된 데이터를 JSON으로 변환합니다.

  • 직렬화된 데이터를 JSON 형식으로 변환합니다.
  • content = JSONRenderer().render(serializer.data)
  • JSON 형식의 데이터를 출력합니다.
  • content # 결과: # b'{"id":2,"title":"","code":"print(\\"hello, world\\")\\n","linenos":false,"language":"python","style":"friendly"}'

5. JSON 데이터를 파싱하여 다시 파이썬 데이터로 변환

이제 JSON 데이터를 다시 파이썬의 기본 데이터 형식으로 변환합니다.

  • 데이터를 스트림 형태로 다루기 위해 io 모듈을 사용합니다.
  • import io
  • JSON 데이터를 바이너리 스트림으로 변환합니다.
  • stream = io.BytesIO(content)
  • 스트림을 파싱하여 파이썬 데이터로 변환합니다.
  • data = JSONParser().parse(stream)

6. 파싱된 데이터를 이용해 새로운 Snippet 생성

파싱된 데이터를 바탕으로 새로운 Snippet을 생성합니다.

  • 파싱된 데이터를 이용해 Serializer를 생성합니다.
  • serializer = SnippetSerializer(data=data)
  • 데이터가 유효한지 확인합니다.
  • serializer.is_valid() # 결과: # True
  • 유효성 검사를 통과한 데이터를 확인합니다.
  • serializer.validated_data # 결과: # {'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}
  • 새로운 스니펫을 저장합니다.
  • serializer.save() # 결과: # <Snippet: Snippet object (3)>

7. 여러 객체 직렬화

Snippet 모델의 모든 객체를 한꺼번에 직렬화하고, many=True 옵션을 사용하여 여러 개의 스니펫을 직렬합니다.

  • 모든 Snippet 객체를 직렬화합니다.
  • serializer = SnippetSerializer(Snippet.objects.all(), many=True)
  • 직렬화된 데이터를 출력합니다.
  • serializer.data # 결과: # [{'id': 1, 'title': '', 'code': 'foo = "bar"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}, {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}, {'id': 3, 'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}]

리팩토링: ModelSerializer 사용

ModelSerializer 클래스를 활용하여 코드를 더 간결하게 리팩토링할 수 있습니다. 기존의 SnippetSerializerModelSerializer를 사용하여 리팩토링해보겠습니다. 먼저 snippets/serializers.py 파일을 열어 다음과 같이 수정합니다:

from rest_framework import serializers

from snippets.models import Snippet


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

 

python Manage.py 셸을 사용하여 아래의 명령어를 입력하여 SnippetSerializer의 동작을 확인합니다:

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=[('abap', 'ABAP'), ... ]

ModelSerializer 클래스는 특별히 마법적인 작업을 수행하지 않으며 단순히 직렬 변환기 클래스를 생성하기 위한 shortcut일 뿐입니다.

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

Serializer를 사용하여 일반 Django 뷰 작성

이제 snippets/views.py 파일을 편집하여 일반 Django 뷰를 작성합니다.

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

 

snippet_list 뷰를 작성합니다.

@csrf_exempt
def snippet_list(request):
    """
    모든 코드 snippet을 나열하거나 새로운 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를 사용하여 CSRF 토큰 없이 클라이언트가 이 뷰에 POST 요청을 할 수 있도록 허용합니다.

snippet_detail 뷰를 작성합니다.

@csrf_exempt
def snippet_detail(request, pk):
    """
    특정 코드 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)

 

snippets/urls.py 파일을 생성하여 위에서 작성한 뷰를 URL에 연결합니다.

from django.urls import path

from snippets import views

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

 

마지막으로 config/urls.py 파일에서 루트 URL 구성을 설정하여 snippets 앱의 URL을 포함시킵니다.

from django.urls import include, path

urlpatterns = [
    path("", include("snippets.urls")),
]

웹 API에 대한 첫 번째 시도 테스트

앱을 실행합니다.

python manage.py runserver

 

브라우저에서 http://127.0.0.1:8000/snippets/ URL에 접속하면 다음과 같은 JSON 형식의 데이터가 표시됩니다.

[
    {
        "id": 1,
        "title": "",
        "code": "foo = \"bar\"\n",
        "linenos": false,
        "language": "python",
        "style": "friendly"
    },
    {
        "id": 2,
        "title": "",
        "code": "print(\"hello, world\")\n",
        "linenos": false,
        "language": "python",
        "style": "friendly"
    },
    {
        "id": 3,
        "title": "",
        "code": "print(\"hello, world\")",
        "linenos": false,
        "language": "python",
        "style": "friendly"
    }
]

마무리

이로써 DRF의 Serialization을 살펴보았습니다. Serialization은 데이터의 직렬화와 역직렬화를 다루며 Django의 Forms API와 유사한 느낌을 줍니다.

참고자료

댓글