이 포스팅은 django rest framework 공식 문서의 serializers를 공부하면서 직접 옮겼습니다.

출처 : https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.bezkoder.com%2Fdjango-rest-api%2F&psig=AOvVaw0mXowddNqT3ed_XuKnFOxZ&ust=1684242915408000&source=images&cd=vfe&ved=0CBEQjRxqFwoTCKjFxPiz9_4CFQAAAAAdAAAAABAE

 

1️⃣ Serializers란?

serializers는 직렬화(serialization)와 역직렬화(deserialization)기능을 제공한다. 
◼ 직렬화(Serialization)
queryset이나 model instance와 같은 복잡한 데이터를 python 데이터 형식로 변환하여 쉽게 JSON이나 XML으로 만드는 작업을 수행한다.
◼ 역직렬화(Deserialization)
JSON이나 XML과 같은 수신 데이터를 검증한 후 구문 분석된 데이터를 queryset이나 model instance와 같은 복잡한 데이터로 다시 변환하는 작업을 한다.

DRF의 Serializers는 Django의 Form 및 ModelForm 클래스와 매우 유사하게 작동한다. Serializer class는 응답 출력을 제어할 수 있는 강력하고 일반적인 방법을 제공하고, ModelSerializer class는 model instance 및 queryset을 처리하는 Serializer를 만드는 데 유용한 shortcut을 제공한다.

 

 

2️⃣ Serialization (직렬화)

◼ Declaring Serializers

# model instance → python

# forms.py
from datetime import datetime

class Comment:
    def __init__(self, email, content, created=None):
        self.email = email
        self.content = content
        self.created = created or datetime.now()

comment = Comment(email='sonzwon@example.com', content='hello')
# serializers.py
from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()
    
serializer = CommentSerializer(comment)
serializer.data
# {'email': 'sonzwon@example.com', 'content': 'hello', 'created': '2016-01-27T15:17:10.375877'}

간단한 예시로 comment 객체를 만들고, comment 객체에 해당하는 데이터를 serialization, deserialization 할 수 있는 serializer을 선언해주면, 이제 CommentSerializer을 사용하여 Comment 객체를 직렬화할 수 있다. 예시를 통해 우리는 Serializer class와 Form class는 매우 유사하다는 것을 확인할 수 있다. 여기까지 model instance(Comment 객체)를 python 데이터 형식으로 변환하는 과정이다.

 

◼ Serializing objects

# python → JSON

데이터를 json으로 렌더링하여 직렬화를 완료한다.

from rest_framework.renderers import JSONRenderer

json = JSONRenderer().render(serializer.data)
json
# b'{"email":"sonzwon@example.com","content":"hello","created":"2016-01-27T15:17:10.375877"}'

 

 

3️⃣ Deserialization (역직렬화)

◼ Deserializing objects

역직렬화도 유사하다. 먼저 스트림을 python 데이터 형식으로 구문 분석한 다음, validated_data 딕셔너리에 복원한다.

import io
from rest_framework.parsers import JSONParser

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

serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'hello', 'email': 'sonzwon@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}

 

◼ Saving instances

# POST(create) & PUT(update)

validated_data를 기반으로 객체 인스턴스 전체를 반환하려면 .create() 및 .update() 메서드를 구현해야 한다. 

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

    def create(self, validated_data):
        return Comment(**validated_data)

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

객체 인스턴스가 Django 모델에 해당하는 경우, create()와 update()가 객체를 데이터베이스에 저장하는지 확인할 수 있다.

"""
   create 메서드에서 Comment.objects.create(**validated_data)
   update 메서드에서 instance.save()
"""
   
    def create(self, validated_data):
        return Comment.objects.create(**validated_data)

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

이제 데이터를 역직렬화할 때, .save()를 호출하여 validated_data를 기반으로 객체 인스턴스를 반환할 수 있다.
serializer 클래스를 인스턴스화할 때 기존 인스턴스가 전달되었는지 여부에 따라 .save()를 호출하면 새 인스턴스가 생성되거나 기존 인스턴스가 업데이트된다.

comment = serializer.save()

# .save() will create a new instance.
serializer = CommentSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)

Passing additional attributes to .save()

경우에 따라 views.py에서 인스턴스를 저장하는 시점에 추가 데이터(현재 사용자, 현재 시간 또는 요청 데이터의 일부가 아닌 다른 모든 정보)를 넣을 수 있다.  이 데이터는 .create() 또는 .update()가 호출되면 validated_data 인수에 추가된다.

serializer.save(owner=request.user)

Overriding .save() directly

새로운 인스턴스를 생성하는 기능을 원하지 않을 때에는 .create()와 .update()라는 이름의 의미가 없어지는데, 이럴 때는 .save()를 재정의하여 사용할 수 있다. 이 경우에는 serializer의 validated_data 속성에 직접 액세스해야 한다.

"""
ContactForm에서는 인스턴스를 생성하는 것이 아니라, 이메일을 보내기 위한 용도임
"""
class ContactForm(serializers.Serializer):
    email = serializers.EmailField()
    message = serializers.CharField()

    def save(self):
        email = self.validated_data['email'] # validated_data에 직접 엑세스
        message = self.validated_data['message'] # validated_data에 직접 엑세스
        send_email(from=email, message=message)

 

◼ Validation

데이터를 역직렬화할 때, validated_data에 접근하거나 객체 인스턴스를 저장하기 위해서는 is_valid() 메서드를 호출해야한다. 만약 validation error가 나면 .errors를 통해 에러메세지를 확인할 수 있다.

serializer = CommentSerializer(data={'email': 'hello', 'content': 'hi'})
serializer.is_valid()
# False
serializer.errors
# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}

serializer.errors는 딕셔너리 형태로 반환되는데, 각 키는 필드 이름이고 값들은 그 필드에 해당하는 에러 메세지의 문자 리스트다. 딕셔너리에  non_field_errors 키가 있을 수 있는데, 이는 일반적인 validation error가 나열되는 것이다. non_field_errors 키 이름은 settings.py에 REST_FRAMEWORK에서 지정하여 사용할 수 있다.

Rasising a exception on invalid data

.is_valid() 메서드는 validation error가 있을 때 예외처리를 할 수 있는 raise_exception을 사용할 수 있다. 그러면 자동으로 REST framework에서 제공하는 예외처리기를 통해 HTTP 400 Bad Request 응답을 반환한다 

# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)

Field-level validation

Serializer 하위클래스에 메서드를 추가하여 사용자 지정 필드 수준의 유효성 검사을 지정할 수 있다. 이 메서드는 검증이 필요한 필드 값인 단일 인수를 사용하며, 검증된 값이나 serializers.ValidationError을 반환한다

from rest_framework import serializers

class BlogPostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField()
 
    # 메서드명을 validate_<field_name>으로 지정해준다.
    def validate_title(self, value):
        """
        Check that the blog post is about Django.
        """
        if 'django' not in value.lower():
            raise serializers.ValidationError("Blog post is not about Django")
        return value

※ NOTE : Serializer에서 <field_name>이 required=False로 선언된 경우, 필드가 포함되지 않으면 이 검증 단계가 수행되지 않음

Object-level validation

Serializer 하위 클래스에 .validate() 메서드를 추가하여 여러 필드에 접근해야 하는 다른 유효성 검사를 수행할 수 있다. 이 메서드는 필드 값의 딕셔너리 형태를 단일 인수를 사용하며, serializer.ValidationError을 반환한다. (필요한 경우 검증된 값만 반환)

from rest_framework import serializers

class EventSerializer(serializers.Serializer):
    description = serializers.CharField(max_length=100)
    start = serializers.DateTimeField()
    finish = serializers.DateTimeField()

    def validate(self, data):
        """
        Check that start is before finish.
        """
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data

validators

validators를 필드 인스턴스에 선언하여 serializer의 개별 필드에 포함할 수 있다.

def multiple_of_ten(value):
    if value % 10 != 0:
        raise serializers.ValidationError('Not a multiple of ten')

class GameRecord(serializers.Serializer):
    score = IntegerField(validators=[multiple_of_ten])
    ...

또한,  내부 Meta class에 전체 필드 데이터 집합에 적용되는 재사용 가능한 validators를 선언하여 사용할 수 있다.

class EventSerializer(serializers.Serializer):
    name = serializers.CharField()
    room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
    date = serializers.DateField()

    class Meta:
        # Each room only has one event per day.
        validators = [
            UniqueTogetherValidator(
                queryset=Event.objects.all(),
                fields=['room_number', 'date']
            )
        ]

 

 Accessing the initail data and instance

초기 객체나 queryset가 serializer 인스턴스로 전달되면 .instance로 객체를 사용할 수 있다. 초기 객체가 전달되지 않으면 None을 반환한다.
데이터가 serializer 인스턴스로 전달되면 수정되지 않은 데이터는 .initial_data로 사용할 수 있는데, 만약 데이터 키워드 인수가 전달되지 않으면 .initial_data는 존재하지 않는다.

 

 Partial updates

기본적으로, serializers는 모든 required 필드에 값을 전달하거나 validation error을 발생시키는데, partial 인수를 사용하면 부분적인 업데이트를 할 수 있다.

# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)

 

 

◼ Dealing with nested objects

우리는 간단한 데이터타입보다 더 복잡한 데이터타입의 객체를 다루곤 하는데, Serializer 클래스의 자체적인 Field 유형을 사용해 한 객체가 다른 객체 내부에 중첩되어 있는 관계를 나타낼 수 있다.

class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField(max_length=100)

class CommentSerializer(serializers.Serializer):
    user = UserSerializer()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

required=False를 사용하여 중첩된 표현에 대해 None 값을 받을 수 있다.

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)  # May be an anonymous user.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

유사하게 중첩된 표현이 리스트면, many=True를 사용하면 된다.

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)
    edits = EditItemSerializer(many=True)  # A nested list of 'edit' items.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

 

Writable nested representations

...(업데이트중)

'Back-end > Django' 카테고리의 다른 글

[DRF] Django Rest Framework  (0) 2023.05.12
[DRF] REST API란?  (0) 2023.05.10
Django URL 분리_include() 사용  (0) 2022.09.25
Django 개발 흐름  (2) 2022.09.25
Django 데이터 처리  (0) 2022.09.24

+ Recent posts