[Django REST] Serializers

지니🧸·2022년 10월 18일
0

Django REST

목록 보기
3/5

Serializers

Serializer: 쿼리셋, 모델 인스턴스 등의 복잡한 데이터를 파이썬 데이터 타입으로 바꿔줌

  • 파이썬 데이터 타입이 JSON, XML 등의 컨텐츠 타입으로 렌더링하기 쉬움
  • deserialization도 가능 > 바뀐 데이터를 다시 복잡한 데이터로 바꾸는 것
  • 장고의 Form/ModelForm 클래스와 비슷

Serializer 선언하기

예시로 사용할 오브젝트:

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='leila@example.com', content='foo bar')

Comment 오브젝트에 해당하는 데이터를 serialize/deserialize하는 데 사용할 serializer 선언

  • form 선언과 비슷함
from rest_framework import serializers

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

Serializing objects

위에서 정의한 CommentSerializer을 이용하여 댓글 또는 댓글 리스트 serialize하기

serilizer = CommentSerializer(comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}

데이터를 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

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': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}

instance 저장하기

검증된 데이터를 기반으로 완전한 오브젝트 인스턴스를 돌려주려면 .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

.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()에 부가적인 속성 추가하기

serializer.save(owner=request.user)

deserialize 할 경우에 instance 저장하기

.save() 메서드를 호출해서 검증된 데이터에 기반한 object instance을 돌려줌

comment = serializer.save()

serializer 클래스를 정의할 때 존재하는 인스턴스가 전달됐는지 여부에 따라 .save() 메서드의 호출은 새로운 인스턴스를 생성하거나 존재하는 인스턴스를 업데이트함

# 새로운 인스턴스를 생성할 .save() 호출
serializer = CommentSerializer(data=data)
# 이미 존재하는 'comment' 인스턴스를 업데이트할 .save() 호출
serializer = CommentSerializer(comment, data=data)

Validation

데이터를 deserialize할 때에는 검증된 데이터를 접근하기 전에 무조건 is_valid() 메서드를 호출해야 함

  • 검증 에러 (validation error)가 일어나면 .errors 속성에 사전형의 에러 메시지가 나타날 것
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}
  • 사전형의 키에는 필드 이름, 값에는 에러 메시지에 해당하는 문자열이 나타남
    • non_field_errors 키에는 일반적인 검증 에러가 나타남
      - REST 프레임워크 settings의 NON_FIELD_ERRORS_KEY로 non_field_errors 키의 이름 변경 가능
  • deserialize할 때는 사전형 리스트 (각 사전형이 각 deserialized item 표현)로 나타남

Raising an exception on invalid data

.is_valid() 메서드에는 raise_exception 플래그 옵션이 있음

  • 검증 에러가 있을 경우 serializers.ValidationError exception 발생시킴
  • 기본적으로 HTTP 400 Bad Request 리스폰스 돌려줌
# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)

Validators

serializer의 각 필드는 validator를 포함할 수 있음
1. 필드 인스턴스에 직접 선언하기

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])
    ...
  1. 재사용 가능한 validator는 inner Meta 클래스에 선언
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']
            )
        ]

Partial updates

serializer는 기본적으로 모든 필수 필드에 값을 받지 않으면 검증 에러를 발생 시킴

  • partial argument으로 부분적인 업데이트 수행 가능
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)

Dealing with nested objects

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()
  • nested serializer도 다양하게 사용 가능
    • None 값 수용 > required=False
    • 값이 리스트여야 할 경우 > many=True

nested 표현을 위한 .create() 메소드

nested 프로필 오브젝트가 있는 유저를 생성하는 클래스

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 표현을 위한 .update() 메소드

	 def update(self, instance, validated_data):
        profile_data = validated_data.pop('profile')
        # Unless the application properly enforces that this field is
        # always set, the following 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

모델 매니저 클래스로 관련있는 인스턴스 저장하기

User 인스턴스와 Profile 인스턴스를 항상 페어로 같이 생성하기:

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

Dealing with multiple objects

오브젝트 리스트를 serialize/deserialize하기

Serializing multiple objects

쿼리셋 또는 오브젝트 리스트를 serialize하려면 serializer를 정의할 때 many=True 플래그를 전달해야 함

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

Including extra context

  • serializer에 serialize할 object과 함께 부가적인 컨텍스트를 전달하기
    • (예) 하이퍼링크 관계를 포함하는 serializer > serializer가 현재 요청에 접근할 수 있어야 함
  • context 인자 사용
serializer = AccountSerializer(account, context={'request': request})
serializer.data
# {'id': 6, 'owner': 'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}

ModelSerializer

ModelSerializer

  • 장고 모델과 비슷한 serializer 클래스를 사용하고 싶을 때
  • Model fields에 해당하는 필드를 가진 Serializer 클래스를 자동으로 생성함
  • 일반 Serializer 클래스와의 차이:
    • Model에 따라 자동으로 필드셋을 생성함
    • serializer의 validators를 자동으로 생성함
      • unique_together 등
    • .create()과 .update() 메서드의 기본 구현 포함
class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ['id', 'account_name', 'users', 'created']
  • foreign key 등의 관계는 PrimaryKeyRelatedField에 포함됨
    • 기본적으로 reverse relationship은 포함 X

Specifying which fields to include in Model Serializers

fields 또는 exclude 옵션으로 model serialzer의 기본 필드 중 사용하고 싶은 것을 고를 수 있음

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ['id', 'account_name', 'users', 'created']

모델의 모든 필드 사용하기:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = '__all__'

특정 필드를 제외하고 모두 사용하기:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        exclude = ['users']

Specifying nested serialization

depth 옵션을 사용한 nested 표현

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ['id', 'account_name', 'users', 'created']
        depth = 1
  • depth
    • 정수 값으로 설정
    • 관계의 깊이 표현

Specifying fields explicity

ModelSerializer에 필드 추가하기 또는 기존 필드 override하기

class AccountSerializer(serializers.ModelSerializer):
    url = serializers.CharField(source='get_absolute_url', read_only=True)
    groups = serializers.PrimaryKeyRelatedField(many=True)

    class Meta:
        model = Account
        fields = ['url', 'groups']

Specifying read only fields

읽기만 가능한 필드 정의하기
read_only_fields 메타 옵션 정의하기

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ['id', 'account_name', 'users', 'created']
        read_only_fields = ['account_name']

Additional keyword arguments

필드에 랜덤한 키워드 인자 추가하기 > extra_kwargs 옵션

class CreateUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['email', 'username', 'password']
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = User(
            email=validated_data['email'],
            username=validated_data['username']
        )
        user.set_password(validated_data['password'])
        user.save()
        return user

Different Serializers

HyperlinkedModelSerializer

HyperlinkedModelSerializer

  • ModelSerializer 클래스와 비슷하지만 hyperlink로 관계를 표현함 (primary key 대신)
  • primary key 필드 대신에 url 필드 사용
  • 표현
    • url 필드 > HyperlinkedIdentityField
    • 모델의 관계 > HyperlinkedRelatedField
  • HyperLinkedModelSerializer를 instantiate할 때는 무조건 현재 리퀘스트를 context에 포함해야 함
serializer = AccountSerializer(queryset, context={'request': request})
  • URL 필드 이름 변경 > URL_FIELD_NAME 설정

ListSerializer

ListSerializer 클래스

  • 여러 오브젝트를 한번에 serialize & validate하기
  • serializer를 instantiate할 때 many=True를 넣으면 ListSerializer 인스턴스가 생성됨
  • 기본 설정
    • allow_empty = True
      • False > 빈 리스트는 input으로 안 받음
    • max_length = None
      • 양의 정수 설정 가능
      • 리스트가 포함할 최대 아이템 개수 설정
    • min_length = None
      • 양의 정수 설정 가능
      • 리스트가 포함할 최소 아이템 개수 설정

BaseSerializer

BaseSerializer 클래스

  • 다양한 serialization/deserialization 스타일 적용 가능
  • Serializer 클래스와 같은 기본 API:
    • .data
    • .is_valid()
    • .validated_data
    • .errors
    • .save()
  • override 가능한 메소드:
    • .to_representation()
    • .to_interal_value()
    • .create() & .update()
  • Read-only BaseSerializer class (override .to_representation())
class HighScore(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    player_name = models.CharField(max_length=10)
    score = models.IntegerField()

참고: django REST framework serializer 공식 문서

profile
우당탕탕

0개의 댓글