DRF (Django Rest Framework) serializer 공식문서 정리
Serializers
Serializer란??
처음 듣는 용어. 직렬 변환기?
In computer science, in the context of data storage, serialization is the process of translating data structures or object state into a format that can be stored (for example, in a file or memory buffer) or transmitted (for example, across a network connection link) and reconstructed later (possibly in a different computer environment).[1]
serializers: 쿼리셋, 모델 인스턴스 등의 복잡한 데이터를 JSON, XML 등의 컨텐트 타입으로 쉽게 변환 가능한 python datatype으로 변환시켜줌
deserialization: 받은 데이터를 validating 한 후에 parsed data를 complex type으로 다시 변환
- Declaring Serializers - 선언
Comment object example
from datetime import datetime
class Comment(object):
def __init__(self, email, content, created=None):
self.email = email
self.content = content
self.created = created or datetime.now()
comment = Comment(email='leila@example.com', content='foo bar')
데이터를 serialize, deserialize 하기 위해 serializer를 선언, form 선언하는것과 비슷하게 serializer를 선언
from rest_framework import serializers
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
1. Serializing objects
위에서 만든 CommentSerializer를 통해 comment (list)를 serialize 할 수 있다. (form class 이용과 흡사하게)
serializer = CommentSerializer(comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
=> 모델 인스턴스를 python native datatypes로 변환. 최종적으로 data를 json 타입으로 변환
from rest_framework.renderers import JSONRenderer
json = JSONRenderer().render(serializer.data)
json
# b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'
Deserializing objects
2.deserialization도 비슷하게.
from django.utils.six import BytesIO
from rest_framework.parsers import JSONParser
stream = BytesIO(json)
data = JSONParser().parse(stream)
validaed data를 저장
serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
3. Saving instances
create()나 update() 메소드를 이용해서 object instance를 반환 가능. (Optional)
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
이후 data를 deserializing 할 때 save()를 호출해서 저장 가능. save 는 instance가 존재하면 update를, 그렇지 않으면 create를 해줌.
- save()에 추가적인 atrribute 사용 가능
serializer.save(owner=request.user)
- save()를 직접 overriding 가능
class ContactForm(serializers.Serializer):
email = serializers.EmailField()
message = serializers.CharField()
def save(self):
email = self.validated_data['email']
message = self.validated_data['message']
send_email(from=email, message=message)
Validation
4.data를 deserializing 할 때, instance를 저장하기 전항상 is_valid()
를 호출해야함
에러가 발생하면 .errors로 에러 메세지 호출 가능. {'field name': ['error message']}
형식으로.
Raise_exception
: is_valid() 메소드에 optional로 raise_exception
True일 경우 REST framework가 제공하는 기본 exception handler => 400에러 반환
# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)
- Field-level validation: 각 field별로 validate 생성
.validate_<field_name>
형태로 필드별 validation 가능
from rest_framework import serializers
class BlogPostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
content = serializers.CharField()
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
- Object-level validation: object 전역에 validate
multiple 필드에 validation이 필요하면 subclass로 .validate() 사용,
data를 arg로 받음=> field value dictionary. validate해서 ValidationError를 반환하거나 data 반환
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 the start is before the stop.
"""
if data['start'] > data['finish']:
raise serializers.ValidationError("finish must occur after start")
return data
- validator: 따로 validator를 만들어서 field 정의시 삽입
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에 넣어서 완성된 field data에 적용시킬수도 있음
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 initial data and instance
5.initial_data로 initial data 사용 가능. 없다면 None 반환
Partial updates
6.default로 모든 required fields를 넣어주지 않으면 validation error.
partial arg를 통해서 업데이트 가능
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
Dealing with nested objects
7.다른 serializer class를 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, many=True 로 사용 가능
Writable nested representations
8.nested된 serializer data에서 error 발생시 nested된 field name으로 나옴. validated_data도 마찬가지
serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ('username', 'email', 'profile')
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
- nested representation의 .update() medtod
update인 경우에는 조금 더 문제갸복잡하다. 만약에 관계가 있는 field 가 None일 경우에,
- relationship을 db에서 NULL처리?
- 연관된 istance를 삭제?
- 데이터를 무시하고 instance를 그대로 놔두기?
- validation error?
def update(self, instance, validated_data):
profile_data = validated_data.pop('profile')
# Unless the application properly enforces that this field is
# always set, the follow could raise a `DoesNotExist`, which
# would need to be handled.
profile = instance.profile
instance.username = validated_data.get('username', instance.username)
instance.email = validated_data.get('email', instance.email)
instance.save()
profile.is_premium_member = profile_data.get(
'is_premium_member',
profile.is_premium_member
)
profile.has_support_contract = profile_data.get(
'has_support_contract',
profile.has_support_contract
)
profile.save()
return instance
=> create, update가 모호하고 related model간에는 복잡한 의존도가 필요하기 때문에 REST에서는 항상 method를 명시적으로 사용 해야함.
- Handling saving related instance in model manager class
related instance를 여러 개 저장하는 다른 방법은 custom model manager를 사용하는 방법
class UserManager(models.Manager):
...
def create(self, username, email, is_premium_member=False, has_support_contract=False):
user = User(username=username, email=email)
user.save()
profile = Profile(
user=user,
is_premium_member=is_premium_member,
has_support_contract=has_support_contract
)
profile.save()
return user
위에서 create를 재정의 한 후에,
def create(self, validated_data):
return User.objects.create(
username=validated_data['username'],
email=validated_data['email']
is_premium_member=validated_data['profile']['is_premium_member']
has_support_contract=validated_data['profile']['has_support_contract']
)
이런 식으로 model manager에서 정의한 create를 호출해서 사용 가능.
자세한 내용은 django model manager 문서 참조
Dealing with multiple objects
9.serializer는 object들의 list도 serializing / deserializing 가능
- 여러 objects serializing
many=True
flag를 추가. 쿼리셋이나 리스트를 serializing 가능
queryset = Book.objects.all()
serializer = BookSerializer(queryset, many=True)
serializer.data
# [
# {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'},
# {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'},
# {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'}
# ]
- deserializing
multiple create는 가능, update는 불가능. 아래 ListSerializer에서 자세히 설명
Including extra context
10.serializer를 처음 만들 때 context arg로 다른 context를 추가 가능하다.
serializer = AccountSerializer(account, context={'request': request})
serializer.data
# {'id': 6, 'owner': u'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}