이 포스팅은 django rest framework 공식 문서의 serializers를 공부하면서 직접 옮겼습니다.
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를 필드 인스턴스에 선언하여 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 |