[DRF] Serializers

강민성·2024년 9월 20일

DRF API Guide

목록 보기
9/28

시리얼라이저 (Serializers)

시리얼라이저 확장성

Russell Keith-Magee는 시리얼라이저의 확장 가능성에 대해 언급하며, 이를 해결하기 위해서는 신중한 설계 작업이 필요하다고 강조했습니다. 시리얼라이저는 복잡한 데이터를 네이티브 파이썬 데이터 유형으로 변환하여 JSON, XML 등의 콘텐츠로 쉽게 렌더링할 수 있게 해줍니다. 또한 역직렬화를 통해 파싱된 데이터를 검증 후 다시 복잡한 타입으로 변환할 수 있습니다.

REST 프레임워크의 시리얼라이저는 Django의 Form 및 ModelForm 클래스와 매우 유사하게 작동합니다. Serializer 클래스는 응답의 출력을 제어할 수 있는 강력한 방법을 제공하고, ModelSerializer 클래스는 모델 인스턴스와 쿼리셋을 처리하는 시리얼라이저를 쉽게 생성할 수 있는 유용한 지름길을 제공합니다.


시리얼라이저 선언

먼저 예시로 사용할 간단한 객체를 만듭니다:

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 객체에 해당하는 데이터를 직렬화 및 역직렬화할 시리얼라이저를 선언합니다:

from rest_framework import serializers

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

객체 직렬화

CommentSerializer를 사용하여 댓글 객체 또는 댓글 목록을 직렬화할 수 있습니다:

serializer = 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"}'

객체 역직렬화

역직렬화는 먼저 스트림을 파이썬 네이티브 데이터 타입으로 파싱한 후:

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

인스턴스 저장

검증된 데이터를 바탕으로 객체 인스턴스를 반환하려면 .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

만약 Comment가 Django 모델이라면, 데이터베이스에 객체를 저장하는 코드도 포함해야 합니다:

    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

유효성 검사

역직렬화 시에는 반드시 .is_valid() 메서드를 호출하여 데이터를 검증해야 합니다. 예를 들어:

serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': ['유효한 이메일 주소를 입력하세요.'], 'created': ['이 필드는 필수입니다.']}

필드 수준 유효성 검사

필드 단위로 커스텀 유효성 검사를 정의하려면 validate_<field_name> 메서드를 추가합니다. 예를 들어:

class BlogPostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField()

    def validate_title(self, value):
        if 'django' not in value.lower():
            raise serializers.ValidationError("블로그 게시물 주제가 Django와 관련이 없습니다.")
        return value

객체 수준 유효성 검사

여러 필드에 걸쳐 유효성 검사를 하려면 .validate() 메서드를 추가합니다:

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

    def validate(self, data):
        if data['start'] > data['finish']:
            raise serializers.ValidationError("종료 시간은 시작 시간보다 늦어야 합니다.")
        return data

중첩 객체 처리

이전 예시는 단순한 데이터 타입(예: 문자열, 날짜, 정수)만 포함한 객체를 처리하는 데 적합합니다. 그러나 때로는 객체의 일부 속성이 문자열, 날짜, 정수와 같은 단순 데이터 타입이 아닌 복잡한 객체를 표현할 필요가 있습니다.

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()

중첩 표현에서 None 값을 선택적으로 허용하려면 required=False 플래그를 중첩 직렬화기에 전달해야 합니다.

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)  # 익명의 사용자일 수 있습니다.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

마찬가지로, 중첩 표현이 항목의 리스트여야 하는 경우에는 many=True 플래그를 중첩 직렬화기에 전달해야 합니다.

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)
    edits = EditItemSerializer(many=True)  # 'edit' 항목의 중첩된 리스트.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

기록 가능한 중첩 표현

데이터를 역직렬화할 수 있는 중첩 표현을 다룰 때, 중첩된 객체에서 발생하는 오류는 중첩 객체의 필드 이름 아래에 중첩됩니다.

serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': ['유효한 이메일 주소를 입력하십시오.']}, 'created': ['이 필드는 필수입니다.']}

마찬가지로 .validated_data 속성에는 중첩된 데이터 구조가 포함됩니다.

중첩 표현을 위한 .create() 메서드 작성

기록 가능한 중첩 표현을 지원하려면 여러 객체를 저장하는 .create() 또는 .update() 메서드를 작성해야 합니다.

다음 예는 중첩된 프로필 객체와 함께 사용자를 생성하는 방법을 보여줍니다.

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

중첩 표현을 위한 .update() 메서드 작성

업데이트할 때 관계 업데이트를 처리하는 방법에 대해 신중하게 고려해야 합니다. 예를 들어 관계에 대한 데이터가 None이거나 제공되지 않은 경우 다음 중 어떤 것이 발생해야 할까요?

  • 데이터베이스에서 관계를 NULL로 설정합니다.
  • 관련된 인스턴스를 삭제합니다.
  • 데이터를 무시하고 인스턴스를 그대로 둡니다.
  • 유효성 검사 오류를 발생시킵니다.

다음은 이전 UserSerializer 클래스에 대한 .update() 메서드 예시입니다.

def update(self, instance, validated_data):
    profile_data = validated_data.pop('profile')
    # 이 필드가 항상 설정되도록 애플리케이션이 제대로 강제하지 않는 한
    # 아래 코드는 `DoesNotExist` 오류를 발생시킬 수 있으며,
    # 이를 처리해야 합니다.
    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

중첩된 생성 및 업데이트의 동작은 모호할 수 있으며, 관련 모델 간 복잡한 의존 관계가 필요할 수 있습니다. 따라서 REST 프레임워크 3에서는 이러한 메서드를 명시적으로 작성해야 합니다. 기본 ModelSerializer.create().update() 메서드는 기록 가능한 중첩 표현을 지원하지 않습니다.

그러나 자동 기록 가능한 중첩 표현을 지원하는 DRF Writable Nested와 같은 서드파티 패키지가 있습니다.

모델 관리자 클래스에서 관련 인스턴스를 저장하는 방법 처리

여러 관련 인스턴스를 직렬화기에서 저장하는 대신, 올바른 인스턴스를 생성하는 커스텀 모델 관리자 클래스를 작성할 수 있습니다.

예를 들어, 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

이 관리자 클래스는 user 인스턴스와 profile 인스턴스가 항상 동시에 생성되도록 보다 깔끔하게 캡슐화합니다. 이제 직렬화 클래스의 .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']
    )

이 접근 방식에 대한 자세한 내용은 Django의 모델 관리자에 대한 문서모델 및 관리자 클래스를 사용하는 방법에 대한 블로그 포스트를 참조하십시오.

여러 객체 처리

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

여러 객체 역직렬화

여러 객체를 역직렬화할 때의 기본 동작은 여러 객체 생성을 지원하되, 여러 객체 업데이트는 지원하지 않는 것입니다. 이러한 사례를 지원하거나 사용자 정의하는 방법에 대한 자세한 내용은 ListSerializer 문서를 참조하십시오.

추가 컨텍스트 포함

직렬화하는 객체 외에 직렬화기에 추가 컨텍스트를 제공해야 하는 경우가 있습니다. 일반적인 예는 하이퍼링크 관계를 포함하는 직렬화기를 사용하는 경우입니다. 이 경우 직렬화기가 완전히 지정된 URL을 올바르게 생성할 수 있도록 현재 요청에 액세스할 수 있어야 합니다.

직렬화기를 인스턴스화할 때 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'}

context 사전은 self.context 속성을 통해 직렬화 필드 로직 내에서 사용할 수 있으며, 예를 들어 커스텀 .to_representation() 메서드에서도 사용할 수 있습니다.

ModelSerializer

종종 Django 모델 정의와 밀접하게 매핑되는 serializer 클래스를 원하게 될 것입니다.

ModelSerializer 클래스는 모델 필드에 해당하는 필드를 자동으로 생성하는 serializer 클래스를 손쉽게 생성할 수 있는 단축 방법을 제공합니다.

ModelSerializer 클래스는 일반 Serializer 클래스와 동일하지만, 다음과 같은 차이점이 있습니다:

  • 모델을 기반으로 자동으로 필드 세트를 생성합니다.
  • unique_together와 같은 validator를 자동으로 생성합니다.
  • .create().update()의 기본 구현을 포함합니다.

ModelSerializer 선언은 다음과 같습니다:

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

기본적으로 클래스의 모든 모델 필드는 해당하는 serializer 필드로 매핑됩니다.

모델에서 외래 키와 같은 관계는 PrimaryKeyRelatedField로 매핑됩니다. 역방향 관계는 명시적으로 포함되지 않는 한 기본적으로 포함되지 않습니다. 이는 serializer 관계 문서에 명시된 대로 설정할 수 있습니다.

ModelSerializer 검사

Serializer 클래스는 그 필드의 상태를 완전히 검사할 수 있는 유용한 자세한 설명 문자열을 생성합니다. 특히 ModelSerializer를 사용할 때, 자동으로 생성된 필드 및 validator의 세트를 확인하고자 할 때 매우 유용합니다.

이 작업을 수행하려면 Django 셸을 열고 python manage.py shell 명령을 실행한 후, serializer 클래스를 가져오고 인스턴스화한 다음 객체의 표현을 출력하면 됩니다:

>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print(repr(serializer))
AccountSerializer():
    id = IntegerField(label='ID', read_only=True)
    name = CharField(allow_blank=True, max_length=100, required=False)
    owner = PrimaryKeyRelatedField(queryset=User.objects.all())

포함할 필드 지정

ModelSerializer에서 기본 필드의 하위 집합만 사용하고 싶다면, fields 또는 exclude 옵션을 사용하여 지정할 수 있습니다. 모델이 변경될 때 의도치 않게 데이터를 노출하는 것을 방지하기 위해 fields 속성을 명시적으로 설정하는 것이 좋습니다.

예시:

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

모델의 모든 필드를 사용하려면 fields 속성을 __all__로 설정할 수 있습니다.

예시:

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

exclude 속성은 serializer에서 제외할 필드 목록을 설정하는 데 사용됩니다.

예시:

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

위 예시에서는 Account 모델에 account_name, users, created라는 3개의 필드가 있다고 가정할 때, account_namecreated만 serializer에 포함됩니다.

fieldsexclude 속성에 나열된 이름은 일반적으로 모델 클래스의 필드와 매핑됩니다.

또한, fields 옵션의 이름은 모델 클래스에 있는 인수 없는 속성이나 메서드에 매핑될 수 있습니다.

버전 3.3.0부터는 fields 또는 exclude 속성 중 하나를 반드시 제공해야 합니다.

중첩된 직렬화 지정

기본 ModelSerializer는 관계에 대해 기본 키를 사용하지만, depth 옵션을 사용하여 중첩된 표현을 쉽게 생성할 수도 있습니다:

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

depth 옵션은 관계가 평평한 표현으로 돌아가기 전에 탐색해야 하는 깊이를 나타내는 정수 값으로 설정해야 합니다.

직렬화 방식을 사용자 정의하려면 필드를 직접 정의해야 합니다.

필드 명시적으로 지정하기

ModelSerializer에 추가 필드를 추가하거나 기본 필드를 재정의하려면 Serializer 클래스에서와 같이 필드를 선언하면 됩니다.

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

추가 필드는 모델의 속성이나 호출 가능한 객체에 해당할 수 있습니다.

읽기 전용 필드 지정

여러 필드를 읽기 전용으로 지정하고 싶을 수 있습니다. 각 필드를 명시적으로 read_only=True 속성을 사용해 추가하는 대신, Meta 옵션인 read_only_fields를 사용할 수 있습니다.

이 옵션은 필드 이름의 리스트 또는 튜플이어야 하며 다음과 같이 선언됩니다:

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

editable=False로 설정된 모델 필드 및 AutoField 필드는 기본적으로 읽기 전용으로 설정되며 read_only_fields 옵션에 추가할 필요가 없습니다.

참고

읽기 전용 필드가 모델 레벨에서 unique_together 제약 조건의 일부인 경우가 있습니다. 이 경우 serializer 클래스는 제약 조건을 검증하기 위해 필드를 필요로 하지만, 사용자가 필드를 수정할 수 없어야 합니다.

이 문제를 해결하는 올바른 방법은 필드를 serializer에서 명시적으로 지정하고, read_only=Truedefault=... 키워드 인수를 제공하는 것입니다.

예를 들어, 현재 인증된 사용자에 대한 읽기 전용 관계가 다른 식별자와 unique_together인 경우, 사용자 필드를 다음과 같이 선언합니다:

user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())

UniqueTogetherValidatorCurrentUserDefault 클래스에 대한 자세한 내용은 Validator 문서를 참조하십시오.

추가 키워드 인수

필드에 임의의 추가 키워드 인수를 지정하는 단축 방법도 있으며, 이를 위해 extra_kwargs 옵션을 사용할 수 있습니다. read_only_fields와 마찬가지로 이 경우 필드를 명시적으로 선언할 필요가 없습니다.

이 옵션은 필드 이름을 키워드 인수 사전으로 매핑하는 사전입니다. 예를 들어:

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

필드가 이미 serializer 클래스에서 명시적으로 선언된 경우, extra_kwargs 옵션은 무시된다는 점을 유의하십시오.

관계 필드

모델 인스턴스를 직렬화할 때 관계를 표현하는 여러 가지 방법이 있습니다. ModelSerializer의 기본 표현은 관련 인스턴스의 기본 키를 사용하는 것입니다.

다른 표현 방법으로는 하이퍼링크를 사용한 직렬화, 완전한 중첩 표현을 사용한 직렬화, 또는 사용자 정의 표현을 사용한 직렬화가 있습니다.

자세한 내용은 serializer 관계 문서를 참조하십시오.

필드 매핑 커스터마이징

ModelSerializer 클래스는 직렬화기를 인스턴스화할 때 직렬화 필드가 자동으로 결정되는 방식을 수정할 수 있는 API를 제공합니다.

일반적으로 ModelSerializer가 기본적으로 필요한 필드를 생성하지 않는다면, 해당 필드를 클래스에 명시적으로 추가하거나 대신 일반 Serializer 클래스를 사용하는 것이 좋습니다. 하지만 일부 경우에는 주어진 모델에 대한 직렬화 필드 생성 방식을 정의하는 새로운 기본 클래스를 만들고 싶을 수 있습니다.

serializer_field_mapping

이 속성은 Django 모델 필드를 REST 프레임워크의 직렬화 필드에 매핑한 것입니다. 이 매핑을 재정의하여 각 모델 필드에 대해 사용되는 기본 직렬화 필드를 변경할 수 있습니다.

이 속성은 기본적으로 관계 필드에 사용되는 직렬화 필드 클래스를 지정합니다.

  • ModelSerializer의 기본값은 serializers.PrimaryKeyRelatedField입니다.
  • HyperlinkedModelSerializer의 기본값은 serializers.HyperlinkedRelatedField입니다.

serializer_url_field

직렬화기에서 URL 필드에 사용되는 직렬화 필드 클래스를 지정합니다. 기본값은 serializers.HyperlinkedIdentityField입니다.

serializer_choice_field

선택 필드에 사용되는 직렬화 필드 클래스를 지정합니다. 기본값은 serializers.ChoiceField입니다.

field_classfield_kwargs API

다음 메서드들은 직렬화기에 자동으로 포함되어야 하는 각 필드의 클래스와 키워드 인수를 결정하는 데 사용됩니다. 이 메서드들은 각각 (field_class, field_kwargs)의 튜플을 반환해야 합니다.

build_standard_field(self, field_name, model_field)

표준 모델 필드에 매핑되는 직렬화 필드를 생성할 때 호출됩니다. 기본 구현은 serializer_field_mapping 속성에 기반한 직렬화 클래스를 반환합니다.

build_relational_field(self, field_name, relation_info)

관계 모델 필드에 매핑되는 직렬화 필드를 생성할 때 호출됩니다. 기본 구현은 serializer_related_field 속성에 기반한 직렬화 클래스를 반환합니다.

relation_info 인수는 model_field, related_model, to_many, has_through_model 속성을 포함하는 이름이 지정된 튜플입니다.

build_nested_field(self, field_name, relation_info, nested_depth)

depth 옵션이 설정되었을 때 관계 모델 필드에 매핑되는 직렬화 필드를 생성할 때 호출됩니다. 기본 구현은 ModelSerializer 또는 HyperlinkedModelSerializer에 기반하여 동적으로 중첩된 직렬화 클래스(Nested Serializer Class)를 생성합니다.

nested_depthdepth 옵션의 값에서 1을 뺀 값입니다. relation_info 인수는 model_field, related_model, to_many, has_through_model 속성을 포함하는 이름이 지정된 튜플입니다.

build_property_field(self, field_name, model_class)

모델 클래스에서 속성 또는 인수가 없는 메서드에 매핑되는 직렬화 필드를 생성할 때 호출됩니다. 기본 구현은 ReadOnlyField 클래스를 반환합니다.

build_url_field(self, field_name, model_class)

직렬화기의 URL 필드에 대한 직렬화 필드를 생성할 때 호출됩니다. 기본 구현은 HyperlinkedIdentityField 클래스를 반환합니다.

build_unknown_field(self, field_name, model_class)

필드 이름이 모델 필드나 모델 속성에 매핑되지 않을 때 호출됩니다. 기본 구현은 오류를 발생시키지만, 서브클래스에서는 이 동작을 커스터마이징할 수 있습니다.

HyperlinkedModelSerializer

HyperlinkedModelSerializer 클래스는 ModelSerializer 클래스와 비슷하지만, 기본 키 대신 하이퍼링크를 사용하여 관계를 나타냅니다.

기본적으로 이 직렬화기는 기본 키 필드 대신 URL 필드를 포함합니다. url 필드는 HyperlinkedIdentityField 직렬화 필드로 표현되며, 모델의 관계는 HyperlinkedRelatedField 직렬화 필드를 사용해 표현됩니다.

기본 키를 명시적으로 포함하려면 예를 들어 아래와 같이 필드 옵션에 추가하면 됩니다:

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

절대적 및 상대적 URL

HyperlinkedModelSerializer를 인스턴스화할 때는 현재 요청(request)을 직렬화기 컨텍스트에 포함시켜야 합니다. 예를 들어:

serializer = AccountSerializer(queryset, context={'request': request})

이렇게 하면 생성된 하이퍼링크가 적절한 호스트명을 포함하여 완전한 URL(예: http://api.example.com/accounts/1/)을 사용할 수 있습니다. 그렇지 않으면 상대 URL(예: /accounts/1/)을 사용하게 됩니다.

만약 상대적 URL을 사용하고 싶다면 직렬화기 컨텍스트에 명시적으로 {'request': None}을 전달하면 됩니다.

하이퍼링크된 뷰가 결정되는 방식

모델 인스턴스로 연결되는 하이퍼링크가 어느 뷰를 참조할지 결정해야 합니다. 기본적으로 하이퍼링크는 {model_name}-detail 형식의 뷰 이름에 해당하는 뷰를 사용하며, pk라는 키워드 인수를 사용해 인스턴스를 조회합니다.

URL 필드의 뷰 이름과 조회 필드를 재정의하려면 extra_kwargs 설정에서 view_namelookup_field 옵션을 사용할 수 있습니다. 예를 들면:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Account
        fields = ['account_url', 'account_name', 'users', 'created']
        extra_kwargs = {
            'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
            'users': {'lookup_field': 'username'}
        }

또는 필드를 명시적으로 직렬화기에 설정할 수도 있습니다. 예를 들면:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(
        view_name='accounts',
        lookup_field='slug'
    )
    users = serializers.HyperlinkedRelatedField(
        view_name='user-detail',
        lookup_field='username',
        many=True,
        read_only=True
    )

    class Meta:
        model = Account
        fields = ['url', 'account_name', 'users', 'created']

: 하이퍼링크된 표현과 URL 설정(URL conf)을 정확하게 맞추는 것이 약간 까다로울 수 있습니다. HyperlinkedModelSerializer 인스턴스의 repr을 출력하여 관계가 어떤 뷰 이름과 조회 필드에 매핑되는지 확인하는 것이 매우 유용합니다.

URL 필드 이름 변경

URL 필드 이름의 기본값은 'url'입니다. 이를 전역적으로 변경하려면 URL_FIELD_NAME 설정을 사용할 수 있습니다.

ListSerializer

ListSerializer 클래스는 여러 객체를 한 번에 직렬화하고 검증할 수 있는 기능을 제공합니다. 일반적으로 ListSerializer를 직접 사용할 필요는 없으며, 직렬화 객체를 인스턴스화할 때 many=True를 전달하면 됩니다.

many=True가 전달된 경우, ListSerializer 인스턴스가 생성되고, 직렬화 클래스는 부모 ListSerializer의 자식으로 동작하게 됩니다.

다음 인자를 ListSerializer 필드나 many=True로 인스턴스화된 직렬화 객체에 전달할 수 있습니다:

  • allow_empty: 기본값은 True입니다. 빈 리스트를 유효한 입력으로 허용하지 않으려면 False로 설정할 수 있습니다.
  • max_length: 기본값은 None입니다. 리스트에 포함될 요소의 최대 개수를 제한하려면 양의 정수로 설정할 수 있습니다.
  • min_length: 기본값은 None입니다. 리스트에 포함될 요소의 최소 개수를 제한하려면 양의 정수로 설정할 수 있습니다.

ListSerializer 동작 커스터마이징

특정한 경우에는 ListSerializer의 동작을 커스터마이징할 필요가 있을 수 있습니다. 예를 들어:

  • 리스트의 요소 간 충돌 여부를 검사하는 등의 특정한 리스트 검증을 제공하고자 할 때.
  • 여러 객체의 생성 또는 업데이트 동작을 커스터마이징하고자 할 때.

이런 경우에는 serializer의 Meta 클래스에서 list_serializer_class 옵션을 사용하여 many=True가 전달되었을 때 사용할 클래스를 수정할 수 있습니다.

class CustomListSerializer(serializers.ListSerializer):
    ...

class CustomSerializer(serializers.Serializer):
    ...
    class Meta:
        list_serializer_class = CustomListSerializer

다중 생성 커스터마이징

기본적으로 여러 객체를 생성하는 방식은 리스트의 각 항목에 대해 .create() 메서드를 호출하는 것입니다. 이 동작을 커스터마이징하려면 many=True로 인스턴스화된 ListSerializer 클래스의 .create() 메서드를 수정해야 합니다.

예시:

class BookListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        books = [Book(**item) for item in validated_data]
        return Book.objects.bulk_create(books)

class BookSerializer(serializers.Serializer):
    ...
    class Meta:
        list_serializer_class = BookListSerializer

다중 업데이트 커스터마이징

ListSerializer 클래스는 기본적으로 다중 업데이트를 지원하지 않습니다. 삽입과 삭제에 대해 기대해야 할 동작이 모호하기 때문입니다. 다중 업데이트를 지원하려면 명시적으로 구현해야 합니다. 이때 다음 사항을 고려해야 합니다:

  1. 리스트의 각 항목에 대해 어떤 인스턴스를 업데이트할지 어떻게 결정할 것인가?
  2. 삽입은 어떻게 처리할 것인가? 삽입이 유효하지 않은가, 아니면 새 객체를 생성할 것인가?
  3. 삭제는 어떻게 처리할 것인가? 객체 삭제를 의미하는가, 관계를 제거하는 것인가? 무시해야 하는가, 유효하지 않은가?
  4. 정렬은 어떻게 처리할 것인가? 두 항목의 위치 변경이 상태 변경을 의미하는가, 아니면 무시해야 하는가?

다중 업데이트를 구현하려면 인스턴스 직렬화 객체에 명시적인 id 필드를 추가해야 합니다. 기본적으로 생성된 id 필드는 읽기 전용으로 설정되어 있어 업데이트 시 제거됩니다. 이를 명시적으로 선언하면 리스트 직렬화 객체의 업데이트 메서드에서 사용할 수 있습니다.

다음은 다중 업데이트를 구현한 예시입니다:

class BookListSerializer(serializers.ListSerializer):
    def update(self, instance, validated_data):
        # id와 instance 및 id와 data 항목의 맵핑을 생성합니다.
        book_mapping = {book.id: book for book in instance}
        data_mapping = {item['id']: item for item in validated_data}

        # 생성 및 업데이트 수행
        ret = []
        for book_id, data in data_mapping.items():
            book = book_mapping.get(book_id, None)
            if book is None:
                ret.append(self.child.create(data))
            else:
                ret.append(self.child.update(book, data))

        # 삭제 수행
        for book_id, book in book_mapping.items():
            if book_id not in data_mapping:
                book.delete()

        return ret

class BookSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    ...

    class Meta:
        list_serializer_class = BookListSerializer

ListSerializer 초기화 커스터마이징

many=True로 인스턴스화된 직렬화 객체는 자식 Serializer 클래스와 부모 ListSerializer 클래스 모두의 .init() 메서드에 전달할 인자와 키워드 인자를 결정해야 합니다. 기본 구현은 모든 인자를 두 클래스에 전달하지만, 검증기와 사용자 정의 키워드 인자는 자식 직렬화 클래스에 전달되는 것으로 간주합니다.

때때로 자식과 부모 클래스가 many=True로 인스턴스화될 때 어떻게 인스턴스화되어야 할지를 명시적으로 지정해야 할 수 있습니다. 이는 many_init 클래스 메서드를 사용하여 할 수 있습니다.

@classmethod
def many_init(cls, *args, **kwargs):
    # 자식 직렬화 객체를 인스턴스화합니다.
    kwargs['child'] = cls()
    # 부모 리스트 직렬화 객체를 인스턴스화합니다.
    return CustomListSerializer(*args, **kwargs)

BaseSerializer

BaseSerializer 클래스는 대안적인 직렬화 및 역직렬화 스타일을 쉽게 지원할 수 있도록 만들어졌습니다. 이 클래스는 Serializer 클래스와 동일한 기본 API를 구현합니다:

  • .data: 아웃고잉 원시 표현을 반환합니다.
  • .is_valid(): 인커밍 데이터를 역직렬화하고 검증합니다.
  • .validated_data: 검증된 인커밍 데이터를 반환합니다.
  • .errors: 검증 중 발생한 오류를 반환합니다.
  • .save(): 검증된 데이터를 객체 인스턴스로 저장합니다.

BaseSerializer 클래스는 HTML 폼을 생성하지 않으며, 기본 제공되는 직렬화 클래스와 동일한 방식으로 사용할 수 있습니다.

읽기 전용 BaseSerializer 클래스

BaseSerializer 클래스를 사용하여 읽기 전용 직렬화 객체를 구현하려면 .to_representation() 메서드를 재정의하면 됩니다.

예시:

class HighScore(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    player_name = models.CharField(max_length=10)
    score = models.IntegerField()

class HighScoreSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        return {
            'score': instance.score,
            'player_name': instance.player_name
        }

이제 단일 HighScore 인스턴스를 직렬화할 수 있습니다:

@api_view(['GET'])
def high_score(request, pk):
    instance = HighScore.objects.get(pk=pk)
    serializer = HighScoreSerializer(instance)
    return Response(serializer.data)

읽기 및 쓰기 BaseSerializer 클래스

쓰기 기능을 지원하려면 .to_internal_value() 메서드를 구현해야 합니다. 이 메서드는 유효성을 검사한 값을 반환하며, 유효하지 않은 경우 serializers.ValidationError를 발생시킬 수 있습니다.

다음은 읽기 및 쓰기 작업을 모두 지원하는 HighScoreSerializer의 완성된 예시입니다:

class HighScoreSerializer(serializers.BaseSerializer):
    def to_internal_value(self, data):
        score = data.get('score')
        player_name = data.get('player_name')

        if not score:
            raise serializers.ValidationError({
                'score': 'This field is required.'
            })
        if not player_name:
            raise serializers.ValidationError({
                'player_name': 'This field is required.'
            })
        if len(player_name) > 10:
            raise serializers.ValidationError({
                'player_name': 'May not be more than 10 characters.'
            })

        return {
            'score': int(score),
            'player_name': player_name
        }

    def to_representation(self, instance):
        return {
            'score': instance.score,
            'player_name': instance.player_name
        }

    def create(self, validated_data):
        return HighScore.objects.create(**validated_data)

새로운 기본 클래스 생성

BaseSerializer 클래스는 특정 직렬화 스타일을 처리하거나 대체 저장소 백엔드와 통합하는 새로운 직렬화 클래스 생성 시 유용하게 사용할 수 있습니다.

class ObjectSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        output = {}
        for attribute_name in dir(instance):
            attribute = getattr(instance, attribute_name)
            if attribute_name.startswith('_'):
                pass
            elif hasattr(attribute, '__call__'):
                pass
            elif isinstance(attribute, (str, int, bool, float, type(None))):
                output[attribute_name] = attribute
            elif isinstance(attribute, list):
                output[attribute_name] = [
                    self.to_representation(item) for item in attribute
                ]
            elif isinstance(attribute, dict):
                output[attribute_name] = {
                    str(key): self.to_representation(value)
                    for key, value in attribute.items()
                }
            else:
                output[attribute_name] = str(attribute)
        return output

고급 Serializer 사용법

직렬화 및 역직렬화 동작 재정의
만약 serializer 클래스의 직렬화 또는 역직렬화 동작을 수정해야 할 경우, .to_representation() 또는 .to_internal_value() 메서드를 재정의할 수 있습니다.

이러한 동작 재정의가 유용한 이유는 다음과 같습니다:

  • 새로운 serializer 기본 클래스에 새로운 동작을 추가하고자 할 때
  • 기존 클래스의 동작을 약간 수정하고자 할 때
  • 많은 데이터를 반환하는 자주 호출되는 API 엔드포인트의 직렬화 성능을 개선하고자 할 때

이 메서드의 시그니처는 다음과 같습니다:

to_representation(self, instance)
객체 인스턴스를 입력으로 받아 직렬화가 필요한 데이터를 처리하며, 원시 표현을 반환해야 합니다. 일반적으로 이는 Python 기본 데이터 타입의 구조로 반환하는 것을 의미합니다. 처리할 수 있는 타입은 API에 설정된 render 클래스에 따라 다를 수 있습니다.

이 메서드를 재정의하여 표현 방식을 수정할 수 있습니다. 예를 들어:

def to_representation(self, instance):
    """`username`을 소문자로 변환"""
    ret = super().to_representation(instance)
    ret['username'] = ret['username'].lower()
    return ret

to_internal_value(self, data)
검증되지 않은 입력 데이터를 입력으로 받아, serializer.validated_data로 사용할 검증된 데이터를 반환해야 합니다. 이 반환 값은 .save()가 호출될 때 .create() 또는 .update() 메서드로 전달됩니다.

만약 검증 중 오류가 발생하면 serializers.ValidationError(errors)를 발생시켜야 합니다. 오류 인자는 필드 이름과 오류 메시지 목록으로 매핑된 딕셔너리여야 합니다. 객체 수준의 검증을 제공하려면 .validate() 메서드를 재정의하는 것이 좋습니다.

이 메서드에 전달된 data 인자는 보통 request.data의 값이므로, 제공된 데이터 타입은 API에 설정된 parser 클래스에 따라 달라집니다.


Serializer 상속
Django의 폼과 마찬가지로, 상속을 통해 serializer를 확장하고 재사용할 수 있습니다. 이를 통해 부모 클래스에서 선언한 공통 필드나 메서드를 여러 serializer에서 사용할 수 있습니다. 예를 들어:

class MyBaseSerializer(Serializer):
    my_field = serializers.CharField()

    def validate_my_field(self, value):
        ...

class MySerializer(MyBaseSerializer):
    ...

Django의 ModelModelForm 클래스와 마찬가지로, serializer의 내부 Meta 클래스는 부모 클래스의 Meta 클래스에서 암시적으로 상속되지 않습니다. Meta 클래스를 상속받고 싶다면 명시적으로 상속해야 합니다. 예를 들어:

class AccountSerializer(MyBaseSerializer):
    class Meta(MyBaseSerializer.Meta):
        model = Account

일반적으로 내부 Meta 클래스에서는 상속을 사용하지 않고, 모든 옵션을 명시적으로 선언하는 것을 권장합니다.

또한, serializer 상속에는 다음과 같은 주의 사항이 있습니다:

  • 일반 Python 이름 해석 규칙이 적용됩니다. 만약 여러 부모 클래스가 Meta 내부 클래스를 선언한 경우, 첫 번째 것만 사용됩니다. 즉, 자식의 Meta가 존재하면 이를 사용하고, 그렇지 않으면 첫 번째 부모의 Meta를 사용합니다.
  • 부모 클래스에서 선언된 필드를 자식 클래스에서 선언을 통해 제거할 수 있습니다. 필드 이름을 None으로 설정하면 됩니다.
class MyBaseSerializer(ModelSerializer):
    my_field = serializers.CharField()

class MySerializer(MyBaseSerializer):
    my_field = None

그러나 이 기법은 부모 클래스에서 선언된 필드를 제거하는 데만 사용할 수 있습니다. ModelSerializer가 기본 필드를 생성하는 것을 막지는 않습니다. 기본 필드를 제외하려면, 포함할 필드를 지정하는 방법을 참조하십시오.


필드 동적 수정
한 번 serializer가 초기화되면, serializer에 설정된 필드 사전은 .fields 속성을 통해 접근할 수 있습니다. 이 속성에 접근하고 이를 수정하면 serializer를 동적으로 수정할 수 있습니다.

필드 인자를 직접 수정하면, serializer 필드의 인자를 선언 시점이 아닌 런타임에 변경할 수 있습니다.

예시
예를 들어, serializer를 초기화할 때 어떤 필드를 사용할지를 설정하고 싶다면, 다음과 같은 serializer 클래스를 생성할 수 있습니다:

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    """
    필드를 표시할지 제어하는 추가 `fields` 인자를 받는 ModelSerializer.
    """

    def __init__(self, *args, **kwargs):
        # 'fields' 인자를 슈퍼클래스에 전달하지 않음
        fields = kwargs.pop('fields', None)

        # 슈퍼클래스를 정상적으로 인스턴스화
        super().__init__(*args, **kwargs)

        if fields is not None:
            # `fields` 인자에 지정되지 않은 필드는 삭제
            allowed = set(fields)
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)

이제 다음과 같은 작업이 가능합니다:

>>> class UserSerializer(DynamicFieldsModelSerializer):
>>>     class Meta:
>>>         model = User
>>>         fields = ['id', 'username', 'email']
>>>
>>> print(UserSerializer(user))
{'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'}
>>>
>>> print(UserSerializer(user, fields=('id', 'email')))
{'id': 2, 'email': 'jon@example.com'}

기본 필드 커스터마이징
REST framework 2에서는 ModelSerializer 클래스가 자동으로 기본 필드를 생성하는 방식을 재정의할 수 있는 API를 제공했습니다.

이 API에는 .get_field(), .get_pk_field() 등의 메서드가 포함되었습니다.

그러나, 직렬화기가 3.0으로 근본적으로 재설계되었기 때문에 이 API는 더 이상 존재하지 않습니다. 여전히 생성되는 필드를 수정할 수는 있지만, 소스 코드를 참조해야 하며, 비공개 API를 수정하는 경우 향후 변경될 수 있음을 인지해야 합니다.


서드 파티 패키지

다음 서드 파티 패키지도 사용할 수 있습니다.

Django REST marshmallow
django-rest-marshmallow 패키지는 Python의 marshmallow 라이브러리를 사용하여 serializer의 대체 구현을 제공합니다. 이 패키지는 REST 프레임워크 직렬화기와 동일한 API를 제공하며, 일부 사용 사례에서 교체하여 사용할 수 있습니다.

Serpy
serpy 패키지는 속도를 위해 구축된 직렬화기 대체 구현입니다. serpy는 복잡한 데이터 유형을 단순한 기본 유형으로 직렬화합니다. 기본 유형은 JSON 또는 필요한 다른 형식으로 쉽게 변환할 수 있습니다.

MongoengineModelSerializer
django-rest-framework-mongoengine 패키지는 MongoDB를 Django REST 프레임워크의 스토리지 계층으로 사용하기 위한 MongoEngineModelSerializer 직렬화기 클래스를 제공합니다.

GeoFeatureModelSerializer
django-rest-framework-gis 패키지는 GeoJSON의 읽기 및 쓰기 작업을 지원하는 GeoFeatureModelSerializer 직렬화기 클래스를 제공합니다.

HStoreSerializer
django-rest-framework-hstore 패키지는 django-hstoreDictionaryField 모델 필드 및 그 스키마 모드를 지원하는 HStoreSerializer를 제공합니다.

Dynamic REST
dynamic-rest 패키지는 ModelSerializerModelViewSet 인터페이스를 확장하여 필터링, 정렬, 필드 및 관계의 포함/제외를 위한 API 쿼리 매개변수를 추가합니다.

Dynamic Fields Mixin
drf-dynamic-fields 패키지는 URL 매개변수로 지정된 serializer 필드를 동적으로 제한하는 믹스를 제공합니다.

DRF FlexFields
drf-flex-fields 패키지는 ModelSerializerModelViewSet을 확장하여 URL 매개변수 및 serializer 클래스 정의에서 필드를 동적으로 설정하고 기본 필드를 중첩된 모델로 확장하는 일반적인 기능을 제공합니다.

Serializer Extensions
django-rest-framework-serializer-extensions 패키지는 serializer를 DRY하게 유지하기 위한 도구 모음을 제공하여, 필드를 요청/뷰별로 정의할 수 있도록 합니다. 필드는 허용 목록에 추가하거나 차단할 수 있으며, 하위 serializer는 선택적으로 확장할 수 있습니다.

HTML JSON Forms
html-json-forms 패키지는 (비활성 상태인) HTML JSON Form 사양에 따라 <form> 제출을 처리하기 위한 알고리즘과 직렬화기를 제공합니다. 이 직렬화기는 HTML 안의 추상적으로 중첩된 구조를 처리하는 것을 돕습니다. 예를 들어, <input name="items[0][id]" value="5">{"items": [{"id": "5"}]}으로 해석될 것입니다.

DRF-Base64
DRF-Base64는 base64 인코딩된 파일의 업로드를 처리하는 필드 및 모델 직렬화기 세트를 제공합니다.

QueryFields
djangorestframework-queryfields는 API 클라이언트가 포함/제외 쿼리 매개변수를 통해 응답에서 전송될 필드를 지정할 수 있게 해줍니다.

DRF Writable Nested
drf-writable-nested 패키지는 중첩된 관련 데이터를 사용하여 모델을 생성하거나 업데이트할 수 있는 쓰기 가능한 중첩 모델 직렬화기를 제공합니다.

DRF Encrypt Content
drf-encrypt-content 패키지는 ModelSerializer를 통해 직렬화된 데이터를 암호화하는 데 도움을 줍니다. 또한, 데이터를 암호화하는 데 도움을 주는 몇 가지 보조 함수도 포함되어 있습니다.

Reference

DRF API Guide - Serializers

profile
Back-end Junior Developer

0개의 댓글