backend/django-rest-framework

DRF (Django Rest Framework) serializer 공식문서 정리

seul chan 2017. 11. 23. 23:42

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으로 다시 변환

  1. 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"}'

2. Deserializing objects

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)

4. Validation

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']
        )

5. Accessing the initial data and instance

initial_data로 initial data 사용 가능. 없다면 None 반환

6. Partial updates

default로 모든 required fields를 넣어주지 않으면 validation error.

partial arg를 통해서 업데이트 가능

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

7. Dealing with nested objects

다른 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 로 사용 가능

8. Writable nested representations

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일 경우에,

  1. relationship을 db에서 NULL처리?
  2. 연관된 istance를 삭제?
  3. 데이터를 무시하고 instance를 그대로 놔두기?
  4. 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 문서 참조

9. Dealing with multiple objects

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에서 자세히 설명

10. Including extra context

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'}


'backend > django-rest-framework' 카테고리의 다른 글

django rest framework viewset router의 namespace  (0) 2018.01.11