λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

Web Programming/Django

[Django REST framework] (2) Serializer, Viewset, Router

μ§€λ‚œ κΈ€

[Django REST framework] (1) ν”„λ‘œμ νŠΈ 생성과 μ„€μ •

 

 

 

Django REST frameworkλ₯Ό μ΄μš©ν•΄ κ°œλ°œν•  λ•Œ λ“±μž₯ν•˜λŠ” μ£Όμš” μ»΄ν¬λ„ŒνŠΈμ— λŒ€ν•΄μ„œ μ΄ν•΄ν•˜κ³  μ‚¬μš©λ²•μ„ μ•Œμ•„λ³΄μž.

Serializer

Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.
Django REST framework API Guide - Serializer

SerializerλŠ” μ΄λ¦„μ—μ„œ μ§μž‘ν•  수 μžˆλ“―μ΄ νŠΉμ •ν•œ ꡬ쑰의 데이터(객체, Queryset λ“±)λ₯Ό Python의 dictionary둜 λ³€ν™˜ν•˜λŠ” κΈ°λŠ₯(직렬화, Serialization)을 μ œκ³΅ν•œλ‹€. μ—­μœΌλ‘œ Python의 dictionaryλ₯Ό μ›λž˜ ꡬ쑰의 λ°μ΄ν„°λ‘œ μ—­λ³€ν™˜ν•˜λŠ”(역직렬화, Deserialization) κΈ°λŠ₯도 μ œκ³΅ν•œλ‹€.

Serializer μ •μ˜μ™€ 직렬화(Serialization)

κ²Œμ‹œκΈ€μ˜ 정보λ₯Ό ν‘œν˜„ν•˜λŠ” PostλΌλŠ” μ΄λ¦„μ˜ ν΄λž˜μŠ€κ°€ μžˆλ‹€.

class Post
    def __init__(self, title, content):
        self.title = title
        self.content = content

이 post 객체의 ν•„λ“œ titleκ³Ό contentλ₯Ό 톡해 κ²Œμ‹œκΈ€μ˜ 제λͺ©κ³Ό λ‚΄μš© 값을 μ΄μš©ν•  수 μžˆλ‹€.

>>> post = Post(title='This is post', content='Hello world!')
>>> post.title
'This is post'
>>> post.content
'Hello world!'

이λ₯Ό μ§λ ¬ν™”ν•˜λŠ” Serializerλ₯Ό λ‹€μŒκ³Ό 같이 μ •μ˜ν•  수 μžˆλ‹€.

from rest_framework import serializers

class PostSerializer(serializers.Serializer):
    title = serializers.CharField()
    content = serializers.CharField()

Serializerλ₯Ό μ΄μš©ν•΄ 객체의 ꡬ쑰와 μœ μ‚¬ν•œ dictionary 객체λ₯Ό 얻을 수 μžˆλ‹€. Serializer의 ν•„λ“œλ₯Ό μ •μ˜ν•  λ•Œ 별닀λ₯Έ μ˜΅μ…˜μ΄ μ—†λ‹€λ©΄ λ™μΌν•œ ν•„λ“œ 이름을 μ΄μš©ν•΄ 생성 μ‹œ μ „λ‹¬λœ 객체(post)의 ν•„λ“œ 값을 μ΄μš©ν•œλ‹€.

>>> serializer = PostSerializer(post)
>>> serializer.data
{'title': 'This is post', 'content': 'Hello world!'}

μ›λž˜μ˜ post 객체와 λΉ„κ΅ν•΄λ³΄μž. ν•„λ“œ 이름을 톡해 값을 μ΄μš©ν•˜λ˜ 객체와 μœ μ‚¬ν•œ ꡬ쑰의 dictionaryλ₯Ό 얻을 수 μžˆμŒμ„ 확인할 수 μžˆλ‹€.

>>> # post 객체
>>> post.title
'This is post'
>>> post.content
'Hello world!'
>>> # μ§λ ¬ν™”λœ 데이터 (dictionary)
>>> serializer.data['title']
'This is post'
>>> serializer.data['content']
'Hello world!'

titleκ³Ό contentλΌλŠ” λ™μΌν•œ μ΄λ¦„μ˜ ν•„λ“œλ₯Ό μ΄μš©ν•  수 μžˆλŠ” λ‹€μŒ Model의 μΈμŠ€ν„΄μŠ€λ„ Serializerλ₯Ό μ΄μš©ν•΄ 직렬화λ₯Ό ν•  수 μžˆλ‹€.

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=1024)
    content = models.TextField()
>>> post = Post.objects.create(title='This is post', content='Hello world!')
>>> serializer = PostSerializer(post)
>>> serializer.data
{'title': 'This is post', 'content': 'Hello world!'}

역직렬화(Deserialization)와 객체 μ €μž₯

SerializerλŠ” 직렬화와 λ°˜λŒ€λ‘œ dictionary 객체λ₯Ό μ΄μš©ν•΄ μ›λž˜μ˜ 객체λ₯Ό μ–»μ–΄λ‚΄λŠ” μ—­μ§λ ¬ν™”μ˜ κΈ°λŠ₯도 μ œκ³΅ν•œλ‹€. 직렬화 κ³Όμ •κ³Ό 달리 역직렬화λ₯Ό μœ„ν•΄μ„  Serializerλ₯Ό 생성할 λ•Œ data 인자둜 dictionary 객체λ₯Ό λ°˜λ“œμ‹œ 전달해야 ν•œλ‹€. Serializerλ₯Ό 생성 ν•œ λ’€μ—λŠ” is_valid()ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•΄ 각 ν•„λ“œμ™€ 전체 λ°μ΄ν„°μ˜ μœ νš¨μ„±μ„ 검증 ν•΄μ•Όν•œλ‹€. validated_dataλŠ” 객체의 생성, μ €μž₯, μˆ˜μ • λ“±μ˜ μž‘μ—…μ— 이용되며, is_valid()ν•¨μˆ˜λ₯Ό 톡해 μœ νš¨μ„±μ„ κ²€μ¦ν•˜μ§€ μ•ŠμœΌλ©΄ ν•΄λ‹Ή μž‘μ—…μ„ μ§„ν–‰ν•  수 μ—†λ‹€. 즉 객체의 생성, μ €μž₯, μˆ˜μ •μ— μ•žμ„œ λ°˜λ“œμ‹œ is_valid()ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•΄μ•Ό ν•œλ‹€.

>>> post_data
{'title': 'This is post', 'content': 'Hello world!'}
>>> serializer = PostSerializer(data=post_data)
>>> serializer.is_valid()
True
>>> serializer.validated_data
{'title': 'This is post', 'content': 'Hello world!'}

객체의 생성과 μ €μž₯을 μœ„ν•΄μ„œ create(), update()λ₯Ό μ˜€λ²„λΌμ΄λ“œ ν•΄μ•Όν•œλ‹€. 두 ν•¨μˆ˜λŠ” 인자둜 validated_dataλ₯Ό μ „λ‹¬λ°›λŠ”λ‹€. μ΄λŠ” is_valid()ν•¨μˆ˜λ₯Ό 톡해 μœ νš¨μ„±μ΄ λͺ¨λ‘ κ²€μ¦λœ κ°’μ˜ dictionary이닀. create()λŠ” validated_dataλ₯Ό μ΄μš©ν•΄ μƒμ„±ν•œ 객체λ₯Ό λ°˜ν™˜ν•˜λ„λ‘, 그리고 update()λŠ” validated_dataλ₯Ό μ΄μš©ν•΄ instanceλ₯Ό μˆ˜μ •ν•œ λ’€ λ°˜ν™˜ν•˜λ„λ‘ μ •μ˜ν•œλ‹€.

class PostSerializer(serializers.Serializer):
    title = serializers.CharField()
    content = serializers.CharField()

    def create(self, validated_data):
        return Post(
            title=validated_data['title'],
            content=validated_data['content']
        )

    def update(self, instance, validated_data):
        instance.title = validated_data.get('title', instance.title)
        instance.content = validated_data.get('content', instance.content)
        return instance

SerializerλŠ” Modelκ³Ό ν•¨κ»˜ μ‚¬μš©λ˜λŠ” κ²½μš°κ°€ λ§Žλ‹€. μ•„λž˜λ₯Ό μœ„μ˜ μ½”λ“œμ™€ λΉ„κ΅ν•˜μžλ©΄ model managerλ₯Ό μ΄μš©ν•΄ model instanceλ₯Ό 생성해 λ°˜ν™˜ν•˜λŠ” λΆ€λΆ„, μˆ˜μ • 이후 save()λ₯Ό μ΄μš©ν•΄ μ μš©ν•œ 뢀뢄이 μΆ”κ°€λ˜μ—ˆλ‹€.

class PostSerializer(serializers.Serializer):
    title = serializers.CharField()
    content = serializers.CharField()

    def create(self, validated_data):
        return Post.objects.create(
            title=validated_data['title'],
            content=validated_data['content']
        )

    def update(self, instance, validated_data):
        instance.title = validated_data.get('title', instance.title)
        instance.content = validated_data.get('content', instance.content)
        isinstance.save()
        return instance

μ•žμ„œ λ§ν–ˆλ˜ μœ νš¨μ„± 검사λ₯Ό ν•„λ“œλ³„λ‘œ, λ˜ν•œ 객체 전체 μˆ˜μ€€μ—μ„œ μ˜€λ²„λΌμ΄λ“œν•  수 μžˆλ‹€. νŠΉμ • ν•„λ“œμ— λŒ€ν•œ μœ νš¨μ„± 검사λ₯Ό μ˜€λ²„λΌμ΄λ“œ ν•˜κ³ μž ν•œλ‹€λ©΄, validate_${field_name}(self, value)λ₯Ό μ˜€λ²„λΌμ΄λ“œν•˜λ©΄ λœλ‹€. λ§Œμ•½ 제λͺ©(title)의 길이가 100 μ΄ν•˜μΈ κ²½μš°μ— λŒ€ν•΄μ„œλ§Œ μœ νš¨ν•˜λ‹€ νŒμ •ν•˜κ³  μ‹Άλ‹€λ©΄ λ‹€μŒμ²˜λŸΌ κ΅¬ν˜„ν•œλ‹€. μœ νš¨ν•˜λ‹€λ©΄ ν•΄λ‹Ή 값을 λ°˜ν™˜ν•˜κ³  κ·Έλ ‡μ§€ μ•Šλ‹€λ©΄ μ˜ˆμ™Έλ₯Ό λ˜μ§€λ„λ‘ κ΅¬ν˜„ν•œλ‹€.

def validate_title(self, value):
    if len(value) <= 100:
        return value
    raise ValueError('제λͺ©μ˜ κΈΈμ΄λŠ” 100μ΄ν•˜μ—¬μ•Ό ν•©λ‹ˆλ‹€.')

ViewSet

Django REST framework allows you to combine the logic for a set of related views in a single class, called a ViewSet
Django REST framework API Guide - Viewsets

ViewSet μ—­μ‹œ class-based viewμ΄λ‚˜ HTTP method 기반의 ν•Έλ“€λŸ¬λ₯Ό μ œκ³΅ν•˜λŠ” APIView와 달리 μžμ›μ— λŒ€ν•œ λ™μž‘ 기반의 ν•Έλ“€λŸ¬μΈ list, create, retrieve 등을 μ œκ³΅ν•œλ‹€.

μœ„μ˜ μ˜ˆμ œμ—μ„œ μ΄μš©ν–ˆλ˜ Post model을 계속 μ΄μš©ν•˜μž.

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=1024)
    content = models.TextField

Post model을 생성, μ‘°νšŒν•˜λŠ” ViewSet은 λ‹€μŒμ²˜λŸΌ μ •μ˜ν•œλ‹€.

from rest_framework.viewsets import ViewSet

class PostViewset(ViewSet):
    def create(self, request):
        serializer = PostSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

    def list(self, request):
        queryset = Post.objects.all()
        serializer = PostSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = User.objects.all()
        serializer = UserSerializer(user)
        return Response(serializer.data)

ViewSetμ—μ„œ μ œκ³΅ν•˜λŠ” λ™μž‘μ€ λ‹€μŒκ³Ό κ°™λ‹€.

from rest_framework.viewsets import ViewSet

class PostViewset(ViewSet):
    def list(self, request):
        pass

    def create(self, request):
        pass

    def retrieve(self, request, pk=None):
        pass

    def update(self, request, pk=None):
        pass

    def partial_update(self, request, pk=None):
        pass

    def destroy(self, request, pk=None):
        pass

ViewSetμ—μ„œ as_view ν•¨μˆ˜λ₯Ό μ΄μš©ν•΄ νŠΉμ • viewλ₯Ό μΆ”μΆœν•  수 μžˆλ‹€. HTTP method와 λ™μž‘μ˜ 이름이 λ§€ν•‘λœ dictionaryλ₯Ό 인자둜 μ „λ‹¬ν•œλ‹€.

post_list = PostViewset.as_view({'get': 'list', 'post': 'create'})
post_detail = PostViewset.as_view({'get': 'retrieve'})

μœ„μ—μ„œ μΆ”μΆœν•œ viewλ₯Ό μ΄μš©ν•΄ URLConfλ₯Ό μ„€μ •ν•  수 μžˆλ‹€.

urlpatterns = [
    ...

    path('posts/', post_list),
    path('posts/<int:pk>', post_detail),

    ...
]

Router

ViewSet을 μ΄μš©ν•΄ viewλ₯Ό μΆ”μΆœν•˜λŠ” 과정을 μƒκ°ν•΄λ³΄μž. μžμ›μ— λŒ€ν•œ CRUDλ₯Ό μ œκ³΅ν•˜λŠ” API에 자주 λ“±μž₯ν•˜λŠ” URL νŒ¨ν„΄κ³Ό λ™μž‘μ΄ μžˆμŒμ„ μœ μΆ”ν•  수 μžˆλ‹€. 그리고 μ΄λŸ¬ν•œ νŒ¨ν„΄μ— λ§žμΆ”μ–΄ view와 URL νŒ¨ν„΄μ„ 기본으둜 λ§€μΉ­ν•΄μ£ΌλŠ” 편의 κΈ°λŠ₯을 Routerκ°€ μ œκ³΅ν•œλ‹€. λ‹€μŒ 두 μ½”λ“œλŠ” λ™μΌν•œ λ™μž‘μ„ μˆ˜ν–‰ν•œλ‹€.

from posts.views import PostViewSet

post_list = PostViewSet.as_view({
    'get': 'list',
    'post': 'create'
})
post_detail = PostViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})

urlpatterns = [
    path('posts/', post_list),
    path('posts/<int:pk>', post_detail),
]
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from posts.views import PostViewSet

router = DefaultRouter()
router.register(r'posts', PostViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

REST framework λ§Œμ„ μ΄μš©ν•œ κ°„λ‹¨ν•œ 예제

μœ„μ—μ„œ μ„€λͺ…ν•œ μ»΄ν¬λ„ŒνŠΈλ“€μ€ 일반적으둜 Modelκ³Ό ν•¨κ»˜ μ‚¬μš©λ˜λŠ” κ²½μš°κ°€ 많으며, Model을 μ΄μš©ν•΄ ν•΄λ‹Ή μ»΄ν¬λ„ŒνŠΈλ“€μ„ μ •μ˜ν•  λ•Œ 자주 λ“±μž₯ν•˜λŠ” νŒ¨ν„΄λ“€μ΄ 미리 μ •μ˜λ˜μ—ˆμžˆλ‹€. 이λ₯Ό μ΄μš©ν•΄ μ•„μ£Ό 짧은 μ½”λ“œλ§ŒμœΌλ‘œλ„ μ›ν•˜λŠ” κΈ°λŠ₯의 APIλ₯Ό 금방 κ΅¬ν˜„ν•  수 μžˆλ‹€.

μœ„μ—μ„œ μ‚¬μš©ν–ˆλ˜ Post model에 CRUDλ₯Ό ν•  수 μžˆλŠ” APIλ₯Ό μž‘μ„±ν•΄λ³΄μž.

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=1024)
    content = models.TextField()
from rest_framework import serializers
from models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fiedls = ['id', 'title', 'content']
from rest_framework.viewsets import ModelViewSet
from posts.serializers import PostSerializer
from models import Post

class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from posts.views import PostViewSet

router = DefaultRouter()
router.register(r'posts', PostViewSet)

urlpatterns = [
    path('', include(router.urls)),
]