Django REST Framework/DRF 일반

[DRF] 공식 문서 - serializers 정리

bluebamus 2024. 1. 14. 23:49

 1. Serializers

   1) 정의

      - Serializer는 쿼리셋이나 모델 인스턴스와 같은 복잡한 데이터를 JSON, XML 등의 콘텐츠 타입으로 쉽게 변환할 수 있는 원시 파이썬 데이터 타입으로 바꿔주는 역할을 한다.

      - 또한 Serializer는 역직렬화도 제공하여, 들어온 데이터를 검증한 후에 복잡한 데이터 타입으로 다시 변환할 수 있게 해준다.

      - REST 프레임워크의 Serializer는 Django의 Form과 ModelForm 클래스와 매우 비슷하게 동작한다.

         - 우리는 응답의 출력을 제어하는 강력하면서도 범용적인 방법을 제공하는 Serializer 클래스를 제공하고 있다.

         - 또한, 모델 인스턴스와 쿼리셋을 다루는 Serializer를 쉽게 생성할 수 있도록 ModelSerializer 클래스를 제공한다.

 

   1. Declaring Serializers

      - 예시를 위해 사용할 수 있는 간단한 객체를 생성하는 것부터 시작해보자:

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 객체에 해당하는 데이터를 직렬화하고 역직렬화하는 데 사용할 수 있는 Serializer를 선언해 보자.

      - Serializer를 선언하는 것은 Form을 선언하는 것과 매우 유사하다:

from rest_framework import serializers

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

 

   2. Serializing objects

      - CommentSerializer를 사용하여 댓글이나 댓글 목록을 직렬화할 수 있다.

         - 다시 말해, Serializer 클래스를 사용하는 것은 Form 클래스를 사용하는 것과 많이 비슷하다.

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

 

      - 현재 시점에서 모델 인스턴스를 Python 기본 데이터 유형으로 변환했다.

         - 직렬화 프로세스를 완료하기 위해 데이터를 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"}'

 

   3. Deserializing objects

      - 역직렬화도 유사한 방식으로 진행된다. 

      - 먼저 스트림을 파싱하여 Python 기본 데이터 유형으로 변환한다.

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

 

   4. Saving instances

      - 만약 유효성이 검증된 데이터를 기반으로 완전한 객체 인스턴스를 반환하고 싶다면, .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 모델에 해당한다면, 이러한 메서드가 객체를 데이터베이스에 저장되도록 해야 한다.
         - 예를 들어, 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

 

      - 이제 데이터를 역직렬화할 때, 유효성이 검증된 데이터를 기반으로 .save()를 호출해 객체 인스턴스를 반환할 수 있다.

 

      - .update()를 사용해 코드를 줄일 수 있다.

def update(self, instance, validated_data):
    instance.update(**validated_data)
    return instance

 

      - 데이터를 역직렬화할 때 유효성을 검사한 후, .save() 메서드를 호출하여 해당 데이터를 기반으로 객체 인스턴스를 반환할 수 있다. 

      - 이를 통해 검증된 데이터를 사용하여 객체 인스턴스를 생성하고, 필요한 경우 해당 인스턴스를 데이터베이스에 저장할 수 있다.

comment = serializer.save()

 

      - .save()를 호출하면, 직렬화 클래스를 인스턴스화할 때 기존 인스턴스가 전달되었는지에 따라 새로운 인스턴스를 생성하거나 기존 인스턴스를 업데이트한다.

      - .create() 메서드와 .update() 메서드는 모두 선택사항이다.

      - serializer class의 사용 사례에 따라 두 메서드 중 하나, 둘 다, 또는 하나도 구현할 필요가 없다.

# .save() will create a new instance.
serializer = CommentSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)

 

         1) .save() 메서드에 추가 속성을 전달하기

            - 때로는 인스턴스를 저장하는 시점에서 뷰 코드에서 추가 데이터를 주입하고 싶을 수 있다.

            - 이 추가 데이터에는 현재 사용자, 현재 시간 또는 요청 데이터에 포함되지 않은 기타 정보와 같은 내용이 포함될 수 있다.

            - .save()를 호출할 때 추가 키워드 인자를 포함하여 이를 수행할 수 있다.

            - .create() 또는 .update()가 호출될 때, 모든 추가 키워드 인자는 validated_data 인자에 포함된다.

serializer.save(owner=request.user)

 

         2) .save()를 직접 오버라이딩하기

            - 일부 경우에는 .create() 및 .update() 메서드 이름이 의미가 없을 수 있다.

               - 예를 들어, 연락처 양식에서는 새로운 인스턴스를 생성하는 것이 아니라 이메일이나 다른 메시지를 전송하는 경우일 수 있다.

            - 이러한 경우에는 .save()를 직접 오버라이딩하는 것이 더 가독성이 좋고 의미가 있는 방법일 수 있다.

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)

            - 위의 경우에는 이제 직접 serializer의 .validated_data 속성에 접근해야 한다.

   5. Validation

      - 데이터를 역직렬화할 때는 항상 .is_valid()를 호출한 후에 유효한 데이터에 접근하거나 객체 인스턴스를 저장해야 한다.

      - 유효성 검사 중에 오류가 발생하면 .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 키도 존재할 수 있으며, 일반적인 유효성 검사 오류를 나열한다.

         - non_field_errors 키의 이름은 NON_FIELD_ERRORS_KEY REST framework 설정을 사용하여 사용자 정의할 수도 있다.

from rest_framework import serializers

class MySerializer(serializers.Serializer):
    class Meta:
        non_field_errors_key = 'custom_non_field_errors'

    def validate(self, data):
        # 유효성 검사를 수행하고 오류가 발생하면 custom_non_field_errors에 오류 메시지를 추가합니다.
        if not data.get('some_field'):
            self.errors[self.Meta.non_field_errors_key] = ['some_field는 필수입니다.']
        return data

 

      -  혹은 아래와 같이 사용이 가능하다.

REST_FRAMEWORK = {
    'NON_FIELD_ERRORS_KEY': 'custom_non_field_errors'
}
self.errors['custom_non_field_errors'] = ['일반적인 유효성 검사 오류입니다.']

 

      - 항목 목록을 역직렬화할 때, 오류는 각각의 역직렬화된 항목을 나타내는 딕셔너리의 목록으로 반환된다.

 

   1) 잘못된 데이터에 대해 예외 발생

      - .is_valid() 메서드는 선택사항으로 raise_exception 플래그를 사용할 수 있으며, 유효성 검사 오류가 있는 경우 serializers.ValidationError 예외를 발생시킨다.

         - 이러한 예외는 REST 프레임워크가 제공하는 기본 예외 처리기에 의해 자동으로 처리되며, 기본적으로 HTTP 400 Bad Request 응답을 반환한다.

# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)

 

   2) 필드 수준의 유효성 검사

      - Serializer 하위 클래스에 .validate_<field_name> 메서드를 추가함으로써 사용자 정의 필드 수준의 유효성 검사를 지정할 수 있다.

         - 이는 Django 폼의 .clean_<field_name> 메서드와 유사하다.

         - 이 메서드들은 유효성 검사가 필요한 필드 값인 단일 인자를 받는다.

         - validate_<field_name> 메서드는 유효성이 검증된 값을 반환하거나 serializers.ValidationError을 발생시켜야 한다.

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

 

      - 참고: 만약 <field_name>이 필드가 필수가 아닌 매개변수 required=False로 선언된 경우, 해당 필드가 포함되지 않으면 이 유효성 검사 단계는 수행되지 않는다.

 

      3) 객체 수준의 유효성 검사

         - 여러 필드에 접근이 필요한 다른 유효성 검사를 수행하려면 Serializer 하위 클래스에 .validate()라는 메서드를 추가하면 된다.

         - 이 메서드는 필드 값들로 이루어진 딕셔너리를 단일 인자로 받는다.

         - 필요한 경우 serializers.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

 

      4) 유효성 검사자

         - 시리얼라이저의 개별 필드는 필드 인스턴스에서 선언하여 유효성 검사자(Validators)를 포함할 수 있다.

def multiple_of_ten(value):
    if value % 10 != 0:
        raise serializers.ValidationError('Not a multiple of ten')

class GameRecord(serializers.Serializer):
    score = serializers.IntegerField(validators=[multiple_of_ten])
    ...

 

         - 시리얼라이저 클래스는 필드 데이터 전체에 적용되는 재사용 가능한 유효성 검사자(Validators)를 포함할 수 있다.

         - 이러한 유효성 검사자는 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']
            )
        ]

 

         - 더 자세한 정보는 유효성 검사자(Validators) 문서를 참조

            - https://www.django-rest-framework.org/api-guide/validators/

 

Validators - Django REST framework

 

www.django-rest-framework.org

 

   6. 초기 데이터와 인스턴스에 접근하는 방법

      - 시리얼라이저 인스턴스에 초기 객체 또는 쿼리셋을 전달할 때, 해당 객체는 .instance 속성으로 사용할 수 있다.

      - 초기 객체가 전달되지 않은 경우 .instance 속성은 None이 된다.

      - 시리얼라이저 인스턴스에 데이터를 전달할 때, 수정되지 않은 데이터는 .initial_data로 사용할 수 있다.

      - 데이터 키워드 인자가 전달되지 않은 경우 .initial_data 속성은 존재하지 않을 것이다.

 

   7. 부분 업데이트

      - 기본적으로, 시리얼라이저는 모든 필수 필드에 대한 값을 전달받아야 하며, 그렇지 않을 경우 유효성 검사 오류가 발생한다. 

         - 부분 업데이트를 허용하려면 partial 인자를 사용할 수 있다.

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

 

   8. 중첩된 개체 다루기

      - 이전 예시는 문자열, 날짜 또는 정수와 같은 간단한 데이터 유형만 가진 객체를 다루는 데에는 적합하다.

      - 그러나 때로는 더 복잡한 객체를 표현해야 하는 경우도 있다.

         - 이 경우 객체의 일부 속성이 문자열, 날짜 또는 정수와 같은 간단한 데이터 유형이 아닐 수 있다.

      - 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 플래그를 중첩된 serializer에 전달해야 한다.

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)  # May be an anonymous user.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

 

      - 마찬가지로 중첩된 표현이 항목들의 목록이어야 하는 경우, 중첩된 serializer에 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()

 

 9. 쓰기 가능한 중첩 표현

   - 중첩 표현을 다룰 때 데이터를 역직렬화 지원하는 경우, 중첩된 객체와 관련된 모든 오류는 중첩된 객체의 필드 이름 아래에 중첩된다.

serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': ['Enter a valid e-mail address.']}, 'created': ['This field is required.']}

 

   - 유사한 것으로, .validated_data 속성은 중첩된 데이터 구조를 포함하게 된다..

if serializer.is_valid():
    # 유효성 검사를 통과한 데이터를 확인할 수 있음
    print(serializer.validated_data)

 

   1) 중첩 표현에 대한 .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

 

   2) 중첩 표현에 대한 .update() 메소드 작성

      - 업데이트의 경우, 관계에 대한 업데이트를 어떻게 처리할지 신중하게 고려해야 한다.

      - 예를 들어, 관계 데이터가 None이거나 제공되지 않은 경우, 다음 중 어느 것이 발생해야 할까?
         1.데이터베이스에서 관계를 NULL로 설정한다.
         2. 관련 인스턴스를 삭제한다.
         3. 데이터를 무시하고 인스턴스를 그대로 둔다.
         4. 유효성 검사 오류를 발생시킨다.

 

      - 다음은 이전에 언급했던 UserSerializer 클래스에 대한 .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

 

      - 중첩된 생성과 업데이트의 동작이 모호할 수 있고, 관련 모델 간에 복잡한 의존성을 요구할 수 있기 때문에, REST 프레임워크 3 에서는 이러한 메소드를 항상 명시적으로 작성하도록 요구한다.

      - 기본 ModelSerializer의 .create()와 .update() 메소드는 쓰기 가능한 중첩 표현을 지원하지 않는다. 때문에 중첩된 데이터 구조를 다루려면 사용자가 직접 .create()와 .upcate() 메소드를 오버라이딩해야 한다.

      - 그러나 DRF Writable Nested 같은 쓰기 가능한 중첩 표현을 자동으로 지원하는 서드파티 패키지들이 있다.

 

   3) 모델 매니저 클래스에서 관련 인스턴스 저장 처리

      - Serializer에서 여러 관련 인스턴스를 저장하는 대안으로, 올바른 인스턴스를 생성하는 것을 처리하는 사용자 정의 모델 매니저 클래스를 작성할 수 있다. 

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

 

      - 이 매니저 클래스는 사용자 인스턴스와 프로필 인스턴스가 항상 동시에 생성된다는 것을 더욱 잘 캡슐화한다.

      - Serializer 클래스의 .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 문서의 모델 매니저 부분과 모델 및 매니저 클래스 사용에 대한 이 블로그 포스트를 참조하면 된다.

         - model managers : https://docs.djangoproject.com/en/5.0/topics/db/managers/

         - this blogpost on using model and manager classes : https://www.dabapps.com/insights/django-models-and-encapsulation/

 

   10. 여러 객체 다루기

      - 추가 컨텍스트 포함하기Serializer 클래스는 객체의 리스트를 직렬화하거나 역직렬화하는 것도 처리할 수 있다.

 

      1) 여러 객체 직렬화하기

         - 단일 객체 인스턴스 대신 쿼리셋 또는 객체 리스트를 직렬화하려면, 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'}
# ]

 

      2) 여러 객체 역직렬화하기

         - 여러 객체를 역직렬화하는 기본 동작은 여러 객체 생성을 지원하지만, 여러 객체 업데이트는 지원하지 않는다. 

         - 이러한 경우를 지원하거나 사용자 정의하는 방법에 대한 자세한 정보는 아래의 ListSerializer 문서를 참조하면 된다.

            - https://www.django-rest-framework.org/api-guide/serializers/#listserializer

 

   11. 추가 컨텍스트 포함하기

      - 직렬화되는 객체 외에도 Serializer에게 추가 컨텍스트를 제공해야 하는 경우가 있다.

      - 하나의 일반적인 경우는 하이퍼링크된 관계를 포함하는 Serializer를 사용하는 경우로, 이 경우 Serializer는 완전히 자격을 갖춘 URL을 적절히 생성할 수 있도록 현재 요청에 액세스해야 한다.

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

 

      - context 딕셔너리는 self.context 속성에 접근함으로써 사용자 정의 .to_representation() 메소드 등의 어떤 Serializer 필드 로직에서도 사용될 수 있다.

from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['username', 'email']

    def to_representation(self, instance):
        representation = super().to_representation(instance)

        # self.context에서 추가 정보를 가져옵니다.
        extra_data = self.context.get('extra_data')

        # 추가 정보가 있는 경우, 반환할 데이터에 추가합니다.
        if extra_data:
            representation['extra_data'] = extra_data

        return representation

 

   - 참고 : 

https://velog.io/@arara90/django-torepresentation

 

[Django] to_representation

serializer에서 원하는 대로 request, response data 작성하기. (serialization, deserialization)

velog.io

https://wookkl.tistory.com/63

 

[Django] DRF Serializer(시리얼라이저) 동작 원리

들어가며 DRF에서 주요 클래스(ViewSet, Router, Serializer) 중 하나인 Serializer의 동작 원리를 살펴본다. Serializer의 기능은 세 가지가 있는데 다음과 같다. 모델 객체 → 파이썬 네이티브 타입인 dict 타입

wookkl.tistory.com

 

 2. ModelSerializer

   - 대부분의 경우, Django 모델 정의와 밀접하게 연관된 Serializer 클래스가 필요할 것이다.

   - ModelSerializer 클래스는 모델 필드에 대응하는 필드를 가진 Serializer 클래스를 자동으로 생성하는 간편한 방법을 제공한다.

 

   - ModelSerializer 클래스는 일반 Serializer 클래스와 동일하지만, 다음과 같은 차이점이 있다.
      1) 모델에 기반하여 자동으로 일련의 필드를 생성해준다.
      2) unique_together와 같은 Serializer에 대한 검증자를 자동으로 생성해준다.
      3) create()와 .update()에 대한 간단한 기본 구현을 포함하고 있다.

 

   - ModelSerializer를 선언하는 방법은 다음과 같다.

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

 

   - 기본적으로, 클래스에 있는 모든 모델 필드는 해당하는 Serializer 필드에 매핑된다.

   - 모델에 있는 외래키와 같은 관계는 PrimaryKeyRelatedField에 매핑된다.

   - 역방향 관계는 serializer 관계 문서에 명시적으로 포함되지 않는 한 기본적으로 포함되지 않는다.

 

   1) ModelSerializer 검사하기

      - Serializer 클래스는 그들의 필드 상태를 완전히 검사할 수 있도록 도움이 되는 자세한 표현 문자열을 생성한다. 

      - 이는 ModelSerializers를 사용할 때, 어떤 필드와 검증자 세트가 자동으로 생성되는지 확인하고자 할 때 유용하다.

      - 이를 수행하려면, Django shell을 열고, python manage.py shell를 사용한 후, serializer 클래스를 import하고, 인스턴스화하고, 객체 표현을 출력하면 된다.

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

 

   2) 포함할 필드 지정하기

      - 만약 모델 Serializer에서 기본 필드의 일부만을 사용하고 싶다면, ModelForm에서 사용하는 것처럼 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__'

 

      - Serializer에서 제외할 필드의 목록을 exclude 속성에 설정할 수 있다.

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

 

      - 위의 예시에서, 만약 Account 모델이 account_name, users, created 세 개의 필드를 가지고 있다면, 이는 account_name과 created 필드가 직렬화됨을 의미한다. 

      - fields와 exclude 속성의 이름은 일반적으로 모델 클래스의 모델 필드에 매핑된다.

      - 또는 fields 옵션의 이름은 모델 클래스에 존재하는 인자가 없는 속성이나 메소드에 매핑될 수 있다.

class ExampleModel(models.Model):
    field1 = models.CharField(max_length=100)
    field2 = models.CharField(max_length=100)

    @property
    def combined_field(self):
        return self.field1 + self.field2 

    def method_field(self):
        return "Some calculation or logic"
class ExampleModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = ExampleModel
        fields = ['field1', 'field2', 'combined_field', 'method_field']

      - 버전 3.3.0부터는 fields 또는 exclude 속성 중 하나를 제공하는 것이 필수적이다.

 

   3) 중첩된 직렬화 지정하기

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

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

 

      - depth 옵션은 플랫한 표현으로 전환하기 전에 탐색되어야 하는 관계의 깊이를 나타내는 정수 값을 설정해야 한다.

      - 직렬화가 수행되는 방식을 사용자가 정의하고 싶다면, 필드를 직접 설정해야 한다.

 

   4) 필드를 명시적으로 지정하기

      - Serializer 클래스에서와 마찬가지로, 클래스에 필드를 선언함으로써 ModelSerializer에 추가 필드를 추가하거나 기본 필드를 재정의할 수 있다.

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

 

      - 추가 필드는 모델의 어떤 속성이나 호출 가능한 메소드에 대응될 수 있다.

 

   5) 읽기 전용 필드 지정하기

      - 여러 필드를 읽기 전용으로 지정하고 싶을 수 있다.

      - 각 필드를 read_only=True 속성으로 명시적으로 추가하는 대신, read_only_fields라는 Meta 옵션을 사용할 수 있다.\

      - 이 옵션은 필드 이름의 목록이나 튜플이어야 하며, 다음과 같이 선언된다:

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 옵션에 추가할 필요가 없습니다.

from django.db import models

class ExampleModel(models.Model):
    id = models.AutoField(primary_key=True)
    non_editable_field = models.CharField(max_length=100, editable=False)
    editable_field = models.CharField(max_length=100)
from rest_framework import serializers

class ExampleModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = ExampleModel
        fields = ['id', 'non_editable_field', 'editable_field']

      - 이 경우, read_only_fields 옵션을 명시적으로 지정하지 않아도 id 필드와 non_editable_field 필드는 읽기 전용으로 동작하며 이 필드들에 대한 수정 요청은 무시되거나 오류를 발생시킨다.

 

      - 참고: 모델 수준에서 unique_together 제약 조건의 일부인 읽기 전용 필드가 특별한 경우가 있다.

      - 이 경우, 필드는 제약 조건을 검증하기 위해 Serializer 클래스에 의해 필요하지만, 사용자가 수정할 수 없어야 한다.

      - 이를 처리하는 적절한 방법은 Serializer에 필드를 명시적으로 지정하고, read_only=True와 default=… 키워드 인수를 모두 제공하는 것이다.

       - 이의 한 예는 현재 인증된 User와 unique_together인 다른 식별자에 대한 읽기 전용 관계이다.

          - 이 경우, user 필드를 다음과 같이 선언하면 된다.

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

 

      - ex )

from django.contrib.auth.models import User
from django.db import models

class ExampleModel(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    identifier = models.CharField(max_length=100)
    
    class Meta:
        unique_together = ('user', 'identifier')

 

         - 이 경우 user와 identifier 필드가 unique_together 제약 조건을 형성하고 있다.

         - 이 모델에 대한 ModelSerializer를 작성하면서 user 필드를 읽기 전용으로 만들고, 현재 인증된 사용자를 기본값으로 설정하려면 다음과 같이 작성할 수 있다.

from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator

class ExampleModelSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )

    class Meta:
        model = ExampleModel
        fields = ['user', 'identifier']
        validators = [
            UniqueTogetherValidator(
                queryset=ExampleModel.objects.all(),
                fields=('user', 'identifier')
            )
        ]

 

         - 이 경우 user 필드는 HiddenField를 사용하여 명시적으로 선언되었고, default 인자로 CurrentUserDefault()를 사용하여 현재 인증된 사용자를 기본값으로 설정했다.

         - read_only=True는 HiddenField에 내장되어 있으므로 별도로 지정하지 않아도 된다.

         - UniqueTogetherValidator는 user와 identifier 필드의 조합이 고유하도록 검증한다.

 

      - UniqueTogetherValidator와 CurrentUserDefault 클래스에 대한 세부 정보는 Validators Documentation을 참조

         - Validators : https://www.django-rest-framework.org/api-guide/validators/

         - UniqueTogetherValidator : https://www.django-rest-framework.org/api-guide/validators/#uniquetogethervalidator

         - CurrentUserDefault : https://www.django-rest-framework.org/api-guide/validators/#currentuserdefault

 

   6) 추가 키워드 인수

      - 또한, 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

 

      - 필드가 이미 시리얼라이저 클래스에 명시적으로 선언되어 있다면, extra_kwargs 옵션은 무시된다.

 

   7) 관계형 필드

      - 모델 인스턴스를 직렬화할 때, 관계를 표현하는 데에는 여러 가지 방법을 선택할 수 있다.

      - ModelSerializer의 기본 표현 방식은 관련 인스턴스의 기본 키를 사용하는 것이다.

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

      - 자세한 내용은 시리얼라이저 관계 문서를 참조

         - https://www.django-rest-framework.org/api-guide/relations/

 

   8) 필드 매핑 커스터마이징

      - ModelSerializer 클래스는 시리얼라이저를 인스턴스화할 때 어떻게 시리얼라이저 필드가 자동으로 결정되는지 변경하기 위해 오버라이드할 수 있는 API도 제공한다. 

      - 일반적으로 ModelSerializer가 기본적으로 필요한 필드를 생성하지 않는 경우, 해당 필드를 클래스에 명시적으로 추가하거나, 단순히 일반 Serializer 클래스를 사용해야 한다.

      - 그러나 경우에 따라서는 주어진 모델에 대해 시리얼라이저 필드가 어떻게 생성되는지 정의하는 새로운 기본 클래스를 생성 할 수도 있다.

 

      1. serializer_field_mapping

         - Django 모델 필드를 REST 프레임워크 시리얼라이저 필드에 매핑한다.

         - 이 매핑을 오버라이드하여 각 모델 필드에 사용되어야 하는 기본 시리얼라이저 필드를 변경할 수 있습니다.

from django.db import models
from rest_framework import serializers

class CustomModelSerializer(serializers.ModelSerializer):
    serializer_field_mapping = {
        **serializers.ModelSerializer.serializer_field_mapping,
        models.TextField: serializers.CharField,
        models.DateTimeField: serializers.DateField,
    }

class MyModel(models.Model):
    text_field = models.TextField()
    date_field = models.DateTimeField()

class MyModelSerializer(CustomModelSerializer):
    class Meta:
        model = MyModel
        fields = ['text_field', 'date_field']

 

         - 위 코드에서 CustomModelSerializer는 ModelSerializer의 serializer_field_mapping을 오버라이드하여, TextField는 CharField로, DateTimeField는 DateField로 매핑하도록 변경하였다.

         - 따라서 MyModelSerializer를 사용하여 MyModel의 인스턴스를 직렬화하면, text_field와 date_field는 각각 CharField와 DateField의 직렬화 규칙에 따라 직렬화된다.

 

      2. serializer_related_field

         - 이 속성은 기본적으로 관계형 필드에 사용되는 시리얼라이저 필드 클래스여야 한다. 

         - ModelSerializer의 경우, 기본값은 serializers.PrimaryKeyRelatedField이다.

         - HyperlinkedModelSerializer의 경우, 기본값은 serializers.HyperlinkedRelatedField이다.

from rest_framework import serializers

class CustomModelSerializer(serializers.ModelSerializer):
    serializer_related_field = serializers.StringRelatedField

class MyModel(models.Model):
    related_field = models.ForeignKey(OtherModel, on_delete=models.CASCADE)

class MyModelSerializer(CustomModelSerializer):
    class Meta:
        model = MyModel
        fields = ['related_field']

 

         - 위 코드에서 CustomModelSerializer는 serializer_related_field를 StringRelatedField로 설정하였다.

         - StringRelatedField는 관계형 필드를 해당 객체의 문자열 표현으로 직렬화 한다.

         - 따라서 MyModelSerializer를 사용하여 MyModel의 인스턴스를 직렬화하면, related_field 필드는 StringRelatedField에 따라 문자열 형태로 직렬화된다.

         - 이는 기본적으로 설정된 PrimaryKeyRelatedField나 HyperlinkedRelatedField 대신 사용된다.

         - 이와 같이 serializer_related_field를 사용하면 관계형 필드의 직렬화 방식을 커스터마이징할 수 있다.

 

      3. serializer_url_field

         - 시리얼라이저의 어떤 URL 필드에 사용되는 시리얼라이저 필드 클래스이다.
         - 기본값은 serializers.HyperlinkedIdentityField이다.

 

      4. serializer_choice_field

         - 시리얼라이저의 어떤 선택 필드에 사용되는 시리얼라이저 필드 클래스이다.

         - 기본값은 serializers.ChoiceField이다.

from rest_framework import serializers

class CustomModelSerializer(serializers.ModelSerializer):
    serializer_choice_field = serializers.ChoiceField

class MyModel(models.Model):
    COLOR_CHOICES = [
        ('R', 'Red'),
        ('B', 'Blue'),
        ('G', 'Green'),
    ]
    color = models.CharField(choices=COLOR_CHOICES, max_length=1)

class MyModelSerializer(CustomModelSerializer):
    class Meta:
        model = MyModel
        fields = ['color']

 

      1) The field_class and field_kwargs API

         - 다음 메소드들은 시리얼라이저에 자동으로 포함되어야 하는 각 필드에 대한 클래스와 키워드 인수를 결정하기 위해 호출된다. 

         - 이들 각각의 메소드는 (field_class, field_kwargs)의 두 튜플을 반환해야 한다.

 

         - build_standard_field(self, field_name, model_field)

            - 표준 모델 필드에 매핑되는 시리얼라이저 필드를 생성하기 위해 호출된다.

            - 기본 구현은 serializer_field_mapping 속성에 기반한 시리얼라이저 클래스를 반환한다.

from rest_framework import serializers

class CustomModelSerializer(serializers.ModelSerializer):
    def build_standard_field(self, field_name, model_field):
        field_class, field_kwargs = super().build_standard_field(field_name, model_field)
        # 여기에서 필드 클래스나 필드 키워드 인수를 커스터마이징할 수 있습니다.
        return field_class, field_kwargs

class MyModel(models.Model):
    name = models.CharField(max_length=100)

class MyModelSerializer(CustomModelSerializer):
    class Meta:
        model = MyModel
        fields = ['name']

 

             - 위 코드에서 CustomModelSerializer는 build_standard_field 메서드를 재정의하였다.
             - 이 메서드는 표준 모델 필드에 매핑되는 시리얼라이저 필드를 생성하는 데 사용된다.

             - super().build_standard_field(field_name, model_field)을 호출하여 기본 구현을 가져온 후, 필요에 따라 필드 클래스나 필드 키워드 인수를 커스터마이징할 수 있다.

             - 이렇게 하면 MyModelSerializer를 사용하여 MyModel의 인스턴스를 직렬화할 때, name 필드는 커스터마이징된 방식에 따라 직렬화된다.

 

         - build_relational_field(self, field_name, relation_info)

            - 관계형 모델 필드에 매핑되는 시리얼라이저 필드를 생성하기 위해 호출된다. 

            - 기본 구현은 serializer_related_field 속성에 기반한 시리얼라이저 클래스를 반환한다.

            - relation_info 인수는 model_field, related_model, to_many, has_through_model 속성을 포함하는 튜플이다.

from rest_framework import serializers
from django.db import models

class CustomModelSerializer(serializers.ModelSerializer):
    def build_relational_field(self, field_name, relation_info):
        field_class, field_kwargs = super().build_relational_field(field_name, relation_info)
        # 여기에서 필드 클래스나 필드 키워드 인수를 커스터마이징할 수 있습니다.
        return field_class, field_kwargs

class RelatedModel(models.Model):
    name = models.CharField(max_length=100)

class MyModel(models.Model):
    related_field = models.ForeignKey(RelatedModel, on_delete=models.CASCADE)

class MyModelSerializer(CustomModelSerializer):
    class Meta:
        model = MyModel
        fields = ['related_field']

 

         - build_nested_field(self, field_name, relation_info, nested_depth)

            - depth 옵션이 설정되었을 때, 관계형 모델 필드에 매핑되는 시리얼라이저 필드를 생성하기 위해 호출된다. 

            - 기본 구현은 ModelSerializer 또는 HyperlinkedModelSerializer에 기반한 중첩된 시리얼라이저 클래스를 동적으로 생성한다.

            - nested_depth는 depth 옵션의 값에서 1을 뺀 값이 된다.

            - relation_info 인수는 model_field, related_model, to_many, has_through_model 속성을 포함하는 튜플이다.

from rest_framework import serializers
from django.db import models

class CustomModelSerializer(serializers.ModelSerializer):
    def build_nested_field(self, field_name, relation_info, nested_depth):
        # super()를 사용하여 기본적인 필드 클래스와 필드 키워드 인수를 가져옵니다.
        field_class, field_kwargs = super().build_nested_field(field_name, relation_info, nested_depth)

        # nested_depth의 값에 따라 중첩된 관계를 다르게 처리합니다.
        if nested_depth == 0:
            # nested_depth가 0이면, 중첩된 관계를 가진 필드를 문자열로 처리합니다.
            field_class = serializers.StringRelatedField
        elif nested_depth == 1:
            # nested_depth가 1이면, 중첩된 관계를 가진 필드를 주요 키로 처리합니다.
            field_class = serializers.PrimaryKeyRelatedField

        return field_class, field_kwargs

class NestedRelatedModel(models.Model):
    name = models.CharField(max_length=100)

class RelatedModel(models.Model):
    nested_related_field = models.ForeignKey(NestedRelatedModel, on_delete=models.CASCADE)

class MyModel(models.Model):
    related_field = models.ForeignKey(RelatedModel, on_delete=models.CASCADE)

class MyModelSerializer(CustomModelSerializer):
    class Meta:
        model = MyModel
        fields = ['related_field']
        depth = 2  # 이곳에서 설정한 depth 값에서 1을 뺀 값이 nested_depth가 됩니다.

 

            - 위 코드에서 build_nested_field 메서드는 nested_depth의 값에 따라 중첩된 관계를 가진 필드를 다르게 처리한다.

            - nested_depth가 0이면, 중첩된 관계를 가진 필드를 문자열로 처리하고, nested_depth가 1이면, 중첩된 관계를 가진 필드를 주요 키로 처리한다.

               -이렇게 하면 MyModelSerializer를 사용하여 MyModel의 인스턴스를 직렬화할 때, related_field 필드는 nested_depth의 값에 따라 다르게 직렬화된다.

            - 여기에서는 depth 옵션을 2로 설정하여 related_field의 중첩 관계를 2단계까지 직렬화한다.

 

         - build_property_field(self, field_name, model_class)

            - 모델 클래스에서 속성이나 인수 없는 메소드에 매핑되는 시리얼라이저 필드를 생성하기 위해 호출된다. 

            - 기본 구현은 ReadOnlyField 클래스를 반환한다.

from rest_framework import serializers
from django.db import models

class CustomModelSerializer(serializers.ModelSerializer):
    def build_property_field(self, field_name, model_class):
        # 기본 구현을 가져옵니다. 
        # 이는 ReadOnlyField 클래스를 반환합니다.
        field_class, field_kwargs = super().build_property_field(field_name, model_class)

        # 필드 이름이 'custom_property'인 경우, 필드 클래스를 CharField로 변경합니다.
        if field_name == 'custom_property':
            field_class = serializers.CharField

        return field_class, field_kwargs

class MyModel(models.Model):
    @property
    def custom_property(self):
        return 'This is a custom property'

class MyModelSerializer(CustomModelSerializer):
    class Meta:
        model = MyModel
        fields = ['custom_property']

 

         - build_url_field(self, field_name, model_class)

            - 시리얼라이저의 자체 URL 필드에 대한 시리얼라이저 필드를 생성하기 위해 호출된다.

            - 기본 구현은 HyperlinkedIdentityField 클래스를 반환한다.

 

         - build_unknown_field(self, field_name, model_class)

            - 필드 이름이 어떠한 모델 필드나 모델 속성에도 매핑되지 않았을 때 호출된다.

            - 기본 구현은 오류를 발생시키지만, 서브클래스에서는 이 동작을 커스터마이징할 수 있다.

 

 3. HyperlinkedModelSerializer

   - HyperlinkedModelSerializer 클래스는 ModelSerializer 클래스와 유사하지만 관계를 표현할 때 기본 키 대신 하이퍼링크를 사용한다.

      - 기본적으로 이 시리얼라이저는 기본 키 필드 대신 url 필드를 포함한다.

   - url 필드는 HyperlinkedIdentityField 시리얼라이저 필드를 사용하여 표현되며, 모델의 관계는 HyperlinkedRelatedField 시리얼라이저 필드를 사용하여 표현된다.

   - fields 옵션에 기본 키를 명시적으로 포함시킬 수도 있다.

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

 

   1) 절대 경로와 상대 경로 URL

      - HyperlinkedModelSerializer를 인스턴스화할 때는 시리얼라이저 컨텍스트에 현재 요청을 포함해야 한다.

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

 

      - 이렇게 하면 하이퍼링크에 적절한 호스트 이름을 포함할 수 있으므로, 결과적으로 완전한 URL을 사용하는 표현이 생성된다.

http://api.example.com/accounts/1/

 

      - 상대적인 URL이 아닌 완전한 URL을 사용한다.

/accounts/1/

 

      - 만약 상대적인 URL을 사용하려면, 시리얼라이저 컨텍스트에 {'request': None}를 명시적으로 전달해야 한다.

 

   2) 하이퍼링크된 뷰는 어떻게 결정되는가?

      - 모델 인스턴스에 대한 하이퍼링크를 생성하는 데 사용될 뷰를 결정하는 방법이 필요하다.

      - 기본적으로 하이퍼링크는 '{model_name}-detail'와 같은 스타일의 뷰 이름에 해당하는 인스턴스를 pk 키워드 인자로 조회하는 것으로 예상된다.

      - extra_kwargs 설정에서 view_name과 lookup_field 옵션 중 하나 또는 둘 다를 사용하여 URL 필드의 뷰 이름과 조회 필드를 재정의할 수 있다.

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 구성을 정확하게 매칭하는 것은 때로는 약간 까다로울 수 있다.

         - HyperlinkedModelSerializer 인스턴스의 repr을 출력하는 것은 관계가 매핑되는 뷰 이름과 조회 필드를 정확하게 확인하는 유용한 방법이다.

 

   3) URL 필드의 이름을 변경

      - URL 필드의 이름은 기본적으로 'url'로 설정되어 있다.

      - 전역적으로 이를 변경하려면 URL_FIELD_NAME 설정을 사용할 수 있다.

 

 4. ListSerializer

   - ListSerializer 클래스는 한 번에 여러 객체를 직렬화하고 유효성을 검사하는 동작을 제공한다.

   - 일반적으로 ListSerializer를 직접 사용할 필요는 없고, 대신 직렬화기를 인스턴스화할 때 many=True를 전달하면 된다.
   - 직렬화기가 인스턴스화되고 many=True가 전달되면 ListSerializer 인스턴스가 생성된다.

   - 그리고 직렬화기 클래스는 부모 ListSerializer의 하위 요소가 된다.

   - 또한 many=True가 전달되는 경우 ListSerializer 필드나 직렬화기에 다음과 같은 인수를 전달할 수도 있다.

 

   - allow_empty

      - 기본적으로 True로 설정되어 있지만, 유효한 입력으로 빈 리스트를 허용하지 않으려면 False로 설정할 수 있다.

 

   - max_length

      - 기본적으로 이 값은 None으로 설정되어 있지만, 이 값을 양의 정수로 설정하면 리스트가 이 정수 이상의 요소를 포함하지 않도록 유효성을 검사할 수 있다.

 

   - min_length

      - 기본적으로 이 값은 None으로 설정되어 있지만, 이 값을 양의 정수로 설정하면 리스트가 이 정수 이하의 요소를 포함하지 않도록 유효성을 검사할 수 있다.

 

   1) ListSerializer 동작 사용자 정의하기

      - ListSerializer 동작을 사용자 정의해야 하는 몇 가지 상황이 있을 수 있다.

         - 예를 들어, 리스트에서 요소 간 충돌이 없는지 확인하는 것과 같은 특정한 유효성 검사를 제공하고 싶을 때, 여러 개의 객체의 생성 또는 업데이트 동작을 사용자 정의하고 싶을 때, 이러한 경우에는 serializer Meta 클래스의 list_serializer_class 옵션을 사용하여 many=True이 전달될 때 사용되는 클래스를 수정할 수 있다.

from rest_framework import serializers

class CustomListSerializer(serializers.ListSerializer):
    def validate(self, data):
        # 리스트의 특정한 유효성 검사 로직을 구현합니다.
        # 예를 들어, 요소들 간의 충돌을 확인하는 등의 로직을 추가할 수 있습니다.
        # 유효성 검사를 통과하지 못하는 경우 serializers.ValidationError를 발생시킵니다.
        return data

    def create(self, validated_data):
        # 여러 개의 객체를 생성하기 위한 동작을 사용자 정의합니다.
        # validated_data를 기반으로 새로운 객체를 생성하고 반환합니다.
        # 필요에 따라 다른 로직을 추가할 수 있습니다.
        pass

    def update(self, instance, validated_data):
        # 여러 개의 객체를 업데이트하기 위한 동작을 사용자 정의합니다.
        # instance와 validated_data를 기반으로 객체를 업데이트하고 반환합니다.
        # 필요에 따라 다른 로직을 추가할 수 있습니다.
        pass

class CustomSerializer(serializers.Serializer):
    # Meta 클래스에서 list_serializer_class 옵션을 사용하여 CustomListSerializer를 지정합니다.
    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 클래스는 여러 개의 업데이트를 지원하지 않는다. 

            - 이는 삽입과 삭제에 대한 예상 동작이 모호하기 때문이다.

         - 여러 개의 업데이트를 지원하려면 명시적으로 처리해야 한다.

         - 여러 개의 업데이트 코드를 작성할 때 다음 사항을 염두해야 한다.

            - 데이터 목록의 각 항목에 대해 어떤 인스턴스를 업데이트해야 하는지 어떻게 결정는가?

            - 삽입은 어떻게 처리해야 하는가?

            - 잘못된 것으로 간주되거나 새로운 객체를 생성하는가?

            - 삭제는 어떻게 처리해야 하는가?

            - 객체 삭제를 의미하거나 관계를 제거해야 하는가?

            - 조용히 무시되어야 하거나 잘못된 것으로 간주되어야 하는가?

            - 정렬은 어떻게 처리해야 하는가?

            - 두 항목의 위치를 변경하는 것은 상태 변경을 의미하거나 무시되어야 하나요?

 

         - 인스턴스 시리얼라이저에 명시적으로 id 필드를 추가해야 한다.

         - 기본으로 암묵적으로 생성된 id 필드는 read_only로 표시되어 있다.

            - 이로 인해 업데이트 시 제거된다.

         - 명시적으로 선언하면 목록 시리얼라이저의 업데이트 메서드에서 사용할 수 있다.

class BookListSerializer(serializers.ListSerializer):
    def update(self, instance, validated_data):
        # Maps for id->instance and id->data item.
        book_mapping = {book.id: book for book in instance}
        data_mapping = {item['id']: item for item in validated_data}

        # Perform creations and updates.
        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))

        # Perform deletions.
        for book_id, book in book_mapping.items():
            if book_id not in data_mapping:
                book.delete()

        return ret

class BookSerializer(serializers.Serializer):
    # We need to identify elements in the list using their primary key,
    # so use a writable field here, rather than the default which would be read-only.
    id = serializers.IntegerField()
    ...

    class Meta:
        list_serializer_class = BookListSerializer

 

         - 3.1 버전과 함께 제공되는 서드파티 패키지에는 REST framework 2에서 제공되었던 allow_add_remove 동작과 유사한 여러 개의 업데이트 작업을 자동으로 지원하는 기능이 포함될 수 있다.

 

      - ListSerializer의 초기화를 사용자 정의하는 방법

         - many=True로 직렬화기(serializer)를 인스턴스화할 때, 자식 시리얼라이저 클래스와 부모 ListSerializer 클래스의 .init() 메서드에 전달해야 하는 인수와 키워드 인수를 결정해야 한다.

         - 기본 구현은 유효성 검사기(validators)와 사용자 정의 키워드 인수를 제외한 모든 인수를 두 클래스에 전달하는 것이다.

            - 이는 모두 자식 시리얼라이저 클래스를 위한 것으로 가정된다.

            - 가끔씩 many=True가 전달될 때 자식 및 부모 클래스가 어떻게 인스턴스화되어야 하는지 명시적으로 지정해야 할 수도 있다.

            - 이를 위해 many_init 클래스 메서드를 사용할 수 있\다.

    @classmethod
    def many_init(cls, *args, **kwargs):
        # Instantiate the child serializer.
        kwargs['child'] = cls()
        # Instantiate the parent list serializer.
        return CustomListSerializer(*args, **kwargs)
from rest_framework import serializers

class ChildSerializer(serializers.Serializer):
    def __init__(self, *args, **kwargs):
        # 자식 시리얼라이저 클래스의 초기화 메서드
        super().__init__(*args, **kwargs)
        # 추가적인 초기화 작업 수행

class ParentListSerializer(serializers.ListSerializer):
    def __init__(self, *args, **kwargs):
        # 부모 ListSerializer 클래스의 초기화 메서드
        super().__init__(*args, **kwargs)
        # 추가적인 초기화 작업 수행

class MySerializer(serializers.Serializer):
    children = ChildSerializer(many=True)

    @classmethod
    def many_init(cls, *args, **kwargs):
        # many=True가 전달될 때 자식 및 부모 클래스의 초기화 방법을 명시적으로 지정하는 메서드
        # 필요에 따라 인수와 키워드 인수를 조정할 수 있습니다.
        kwargs['child'] = ChildSerializer(*args, **kwargs)
        kwargs['child'].bind(field_name='', parent=self)
        return ParentListSerializer(*args, **kwargs)

 

         - 위의 코드에서 MySerializer 클래스는 ChildSerializer를 many=True로 설정하여 사용하고 있다.

         - MySerializer 클래스에 many_init 클래스 메서드를 구현하여 자식 및 부모 클래스의 초기화 방법을 명시적으로 지정할 수 있다.

            - 필요한 경우 child 인수와 ParentListSerializer를 인스턴스화할 때 필요한 다른 인수 및 키워드 인수를 조정할 수 있다.

            - 이 코드는 many=True로 직렬화기를 인스턴스화할 때 자식 및 부모 클래스의 초기화 방법을 사용자 정의할 수 있는 방법을 보여주는 예시이다.

 

 5. BaseSerializer

   - BaseSerializer는 특정 상황에서 매우 유용하게 사용될 수 있다.

   - 그러나 실제로는 대부분의 경우 Serializer 또는 ModelSerializer가 더 흔히 사용되는 편이다.

   - 이는 이 두 클래스가 Django 모과의 통합이 잘 되어 있고, 필드 유효성 검증, HTML 폼 생성 등의 추가 기능을 제공하기 때문이다.

   - 그럼에도 불구하고, BaseSerializer를 사용하는 경우는 다음과 같다:
      1) 복잡한 데이터 구조를 다루는 경우: BaseSerializer 복잡한 데이터 구조를 처리하는 데 특히 유용하다.

         - 예를 들어, 일반적인 필드 구조로는 표현하기 어려운 복잡한 JSON 데이터를 직렬화하거나 역직렬화해야 하는 경우에 이 클래스를 사용할 수 있다.

      2) 특정 직렬화 스타일을 구현하려는 경우: BaseSerializer는 커스텀 직렬화 로직을 구현하는 데 유용하다. 

         - 이 클래스를 상속받아 .to_representation() 및 .to_internal_value() 메소드를 오버라이드으로써 고유한 직렬화 및 역직렬화 방식을 정의할 있다.
      3) 대체 저장 백엔드와 통합하려는 경우: BaseSerializer는 Django 모델에 의존하지 않기 때문에, Django 모델이 아닌 다른 저장 백엔드와 통합하는 데 사용할 수 있다.

 

   - BaseSerializer 클래스는 다양한 직렬화 및 역직렬화 스타일을 쉽게 지원할 수 있도록 사용될 수 있다.

   - 이 클래스는 Serializer 클래스와 동일한 기본 API를 구현한다.

      - .data - 나가는 기본 표현을 반환한다.
      - .is_valid() - 들어오는 데이터를 역직렬화하고 유효성을 검사한다.
      - .validated_data - 검증된 들어온 데이터를 반환한다.
      - .errors - 유효성 검사 중 발생한 모든 오류를 반환한다.
      - .save() - 검증된 데이터를 객체 인스턴스에 저장한다.

   - 예시 :

from rest_framework import serializers

class PersonSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        # Return outgoing primitive representation
        return {
            'name': instance.name,
            'age': instance.age
        }
        
    def to_internal_value(self, data):
        # Deserialize and validate incoming data
        if not isinstance(data, dict):
            raise serializers.ValidationError("Invalid data. Expected a dictionary.")
            
        if 'name' not in data or 'age' not in data:
            raise serializers.ValidationError("Invalid data. 'name' and 'age' are required.")

        # You can add more validations here, e.g., for age to be a positive integer

        return data

    def create(self, validated_data):
        # Persist the validated data into a Person object instance
        return Person.objects.create(**validated_data)
        
    def update(self, instance, validated_data):
        # Update the Person object instance with validated data
        instance.name = validated_data.get('name', instance.name)
        instance.age = validated_data.get('age', instance.age)
        instance.save()
        return instance

# Usage
serializer = PersonSerializer(data={'name': 'John Doe', 'age': 30})

if serializer.is_valid():
    person = serializer.save()
    print(serializer.data)  # Returns the outgoing primitive representation
    print(serializer.validated_data)  # Returns the validated incoming data
else:
    print(serializer.errors)  # Returns any errors during validation

 

   - 시리얼라이저 클래스가 지원하길 원하는 기능에 따라 오버라이드(재정의)할 수 있는 네 가지 메소드가 있다.

      - .to_representation() - 읽기 작업을 위한 직렬화를 지원하려면 이를 오버라이드한다.
      - .to_internal_value() - 쓰기 작업을 위한 역직렬화를 지원하려면 이를 오버라이드한다.
      - .create() 와 .update() - 인스턴스 저장을 지원하려면 이 둘 중 하나 혹은 둘 다를 오버라이드한다.

   - 예시

from rest_framework import serializers
from datetime import datetime

class BookSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        # Support serialization, for read operations
        '''
         이 메소드는 주로 '읽기' 작업에 사용됩니다. 
         이 메소드는 Django 모델 인스턴스를 받아서 
         클라이언트에게 전송될 수 있는 Python 원시 타입으로 변환합니다. 
         예를 들어, Book 모델 인스턴스를 받아서 딕셔너리 형태로 변환하고 
         이를 JSON으로 직렬화하여 클라이언트에게 보낼 수 있다.
        '''
        return {
            'title': instance.title,
            'author': instance.author,
            'publication_date': instance.publication_date.strftime('%Y-%m-%d')
        }
        
    def to_internal_value(self, data):
        # Support deserialization, for write operations
        '''
         이 메소드는 주로 '쓰기' 작업에 사용됩니다. 
         클라이언트로부터 받은 데이터를 Django 모델 인스턴스를 생성하거나 
         업데이트하는 데 필요한 Python 원시 타입으로 변환합니다. 
         예를 들어, 클라이언트로부터 받은 JSON 데이터를 파이썬 딕셔너리로 역직렬화하고, 
         이를 Book 모델 인스턴스를 생성하거나 업데이트하는 데 사용한다.
        '''
        if not isinstance(data, dict):
            raise serializers.ValidationError("Invalid data. Expected a dictionary.")
            
        if 'title' not in data or 'author' not in data or 'publication_date' not in data:
            raise serializers.ValidationError("Invalid data. 'title', 'author', and 'publication_date' are required.")
        
        # Convert the date string to a date object
        data['publication_date'] = datetime.strptime(data['publication_date'], '%Y-%m-%d').date()

        return data

    def create(self, validated_data):
        # Support saving instances, for create operations
        return Book.objects.create(**validated_data)
        
    def update(self, instance, validated_data):
        # Support saving instances, for update operations
        instance.title = validated_data.get('title', instance.title)
        instance.author = validated_data.get('author', instance.author)
        instance.publication_date = validated_data.get('publication_date', instance.publication_date)
        instance.save()
        return instance

# Usage
serializer = BookSerializer(data={'title': 'The Great Gatsby', 'author': 'F. Scott Fitzgerald', 'publication_date': '1925-04-10'})

if serializer.is_valid():
    book = serializer.save()
    print(serializer.data)  # Returns the serialized representation
    print(serializer.validated_data)  # Returns the validated incoming data
else:
    print(serializer.errors)  # Returns validation errors

 

   - 이 클래스는 Serializer 클래스와 동일한 인터페이스를 제공하기 때문에, 일반 Serializer 또는 ModelSerializer를 사용하는 것처럼 기존의 제네릭 클래스 기반 뷰와 함께 사용할 수 있다.

      - 그러나 이를 사용할 때 주의할 점은, BaseSerializer 클래스는 브라우저블 API에서 HTML 양식을 생성하지 않는다는 것이다.

      - 이는 그들이 반환하는 데이터가 각 필드를 적절한 HTML 입력으로 렌더링할 수 있을만큼 모든 필드 정보를 포함하지 않기 때문이다.

   - BaseSerializer는 Serializer와 사용 방법은 동일하지만, 웹 브라우저에서 확인 가능한 API 화면에 HTML 폼을 표시하지 않는다는 차이점이 있다.

      -이는 BaseSerializer가 복잡한 데이터 구조를 다루는 데 적합하게 설계되었기 때문에, 각 필드를 HTML 입력으로 표현하는 것이 적합하지 않을 수 있기 때문이다.

 

   1) Read-only BaseSerializer classes

      - BaseSerializer 클래스를 사용하여 읽기 전용 시리얼라이저를 구현하려면, .to_representation() 메소드를 오버라이드하면 된다.

      - 간단한 Django 모델을 사용한 예시를 살펴보자

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

 

      - HighScore 인스턴스를 원시 데이터 타입으로 변환하는 읽기 전용 시리얼라이저를 생성하는 것은 간단하다.

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)

 

      - 또는 이를 사용하여 여러 인스턴스를 직렬화할 수도 있다.

@api_view(['GET'])
def all_high_scores(request):
    queryset = HighScore.objects.order_by('-score')
    serializer = HighScoreSerializer(queryset, many=True)
    return Response(serializer.data)

 

   2) Read-write BaseSerializer classes

      - 읽기-쓰기 시리얼라이저를 생성하려면 먼저 .to_internal_value() 메소드를 구현해야 한다.

      - 이 메소드는 객체 인스턴스를 구성하는 데 사용될 검증된 값을 반환하며, 제공된 데이터가 잘못된 형식인 경우 serializers.ValidationError를 발생시킬 수 있다.

    

      - .to_internal_value()를 구현하면 기본 검증 API가 시리얼라이저에 사용 가능해지고, .is_valid(), .validated_data 및 .errors를 사용할 수 있게 된다.

 

      - .save()를 지원하려면 .create()와 .update() 메소드 중 하나 또는 둘 다를 구현해야 한다.

 

      - 다음은 이전의 HighScoreSerializer를 업데이트하여 읽기 및 쓰기 작업을 모두 지원하도록 한 완전한 예시이다.

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

        # Perform the data validation.
        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 the validated values. This will be available as
        # the `.validated_data` property.
        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)

 

   3) Creating new base classes

      - BaseSerializer 클래스는 특정 직렬화 스타일을 다루거나, 대체 저장 백엔드와 통합하는 새로운 일반 시리얼라이저 클래스를 구현하려는 경우에도 유용하다. 

      - 다음 클래스는 임의의 복잡한 객체를 원시 표현으로 강제하는 일반 시리얼라이저의 예시이다.

class ObjectSerializer(serializers.BaseSerializer):
    """
    A read-only serializer that coerces arbitrary complex objects
    into primitive representations.
    """
    def to_representation(self, instance):
        output = {}
        for attribute_name in dir(instance):
            attribute = getattr(instance, attribute_name)
            if attribute_name.startswith('_'):
                # Ignore private attributes.
                pass
            elif hasattr(attribute, '__call__'):
                # Ignore methods and other callables.
                pass
            elif isinstance(attribute, (str, int, bool, float, type(None))):
                # Primitive types can be passed through unmodified.
                output[attribute_name] = attribute
            elif isinstance(attribute, list):
                # Recursively deal with items in lists.
                output[attribute_name] = [
                    self.to_representation(item) for item in attribute
                ]
            elif isinstance(attribute, dict):
                # Recursively deal with items in dictionaries.
                output[attribute_name] = {
                    str(key): self.to_representation(value)
                    for key, value in attribute.items()
                }
            else:
                # Force anything else to its string representation.
                output[attribute_name] = str(attribute)
        return output

 

 6. Advanced serializer usage

   1) 직렬화와 역직렬화 동작 오버라이딩

      - 시리얼라이저 클래스의 직렬화 또는 역직렬화 동작을 변경해야 하는 경우, .to_representation() 또는 .to_internal_value() 메소드를 오버라이드하여 그렇게 할 수 있다.

 

      - 이것이 유용할 수 있는 몇 가지 이유는 다음과 같다.

         - 새로운 시리얼라이저 기본 클래스에 대한 새로운 동작 추가.
         - 기존 클래스에 대한 동작을 약간 수정.
         - 많은 데이터를 반환하는 자주 접근되는 API 엔드포인트의 직렬화 성능 향상.

 

      - 이러한 메소드들의 시그니처는 다음과 같다.

         - to_representation(self, instance)

            - 직렬화가 필요한 객체 인스턴스를 받아 원시 표현을 반환해야 한다. 

            - 일반적으로 이는 내장된 파이썬 데이터 타입의 구조를 반환하는 것을 의미한다.

            - 처리할 수 있는 정확한 타입은 API에 설정된 렌더 클래스에 따라 달라진다.

 

            - 표현 스타일을 수정하기 위해 오버라이드할 수 있다

def to_representation(self, instance):
    """Convert `username` to lowercase."""
    ret = super().to_representation(instance)
    ret['username'] = ret['username'].lower()
    return ret
from rest_framework import serializers

class PersonSerializer(serializers.Serializer):
    first_name = serializers.CharField(max_length=100)
    last_name = serializers.CharField(max_length=100)

    def to_representation(self, instance):
        # 여기에 원하는 사용자 정의 표현을 구현합니다.
        # 예를 들어, 이름과 성을 하나의 문자열로 합치고 싶다면 다음과 같이 할 수 있습니다.
        full_name = f"{instance.first_name} {instance.last_name}"
        return full_name

 

         - to_internal_value(self, data)

            - 이 메소드는 검증되지 않은 들어오는 데이터를 입력으로 받아, serializer.validated_data로 사용될 검증된 데이터를 반환해야 한다.

            - 반환 값은 또한 시리얼라이저 클래스에 .save()가 호출되면 .create() 또는 .update() 메소드로 전달된다.

 

            - 어떠한 검증이 실패하면, 이 메소드는 serializers.ValidationError(errors)를 발생시켜야 한다.

            - errors 인자는 필드 이름 (또는 settings.NON_FIELD_ERRORS_KEY)을 에러 메시지의 리스트에 매핑하는 사전이어야 한다.

            - 역직렬화 동작을 변경할 필요가 없고 대신 객체 수준의 검증을 제공하려는 경우, .validate() 메소드를 오버라이드하는 것이 권장된다.

 

            - 이 메소드에 전달된 data 인자는 일반적으로 request.data의 값이므로, 제공하는 데이터 타입은 API에 설정된 파서 클래스에 따라 달라진다.

from rest_framework import serializers

class PersonSerializer(serializers.Serializer):
    first_name = serializers.CharField(max_length=100)
    last_name = serializers.CharField(max_length=100)

    def to_internal_value(self, data):
        # 데이터 검증을 위해 to_internal_value를 오버라이드합니다.
        # 첫 번째로, 부모 클래스의 to_internal_value를 호출하여 기본 검증을 수행합니다.
        validated_data = super().to_internal_value(data)

        # 이후, 추가적인 검증을 수행할 수 있습니다.
        # 예를 들어, 이름과 성이 모두 제공되었는지 확인합니다.
        if 'first_name' not in validated_data or 'last_name' not in validated_data:
            raise serializers.ValidationError("Both first name and last name are required.")

        # 모든 검증이 통과하면, 검증된 데이터를 반환합니다.
        return validated_data

 

   2) 시리얼라이저 상속

      - Django 폼과 유사하게, 상속을 통해 시리얼라이저를 확장하고 재사용할 수 있다.

      - 이를 통해 여러 시리얼라이저에서 사용할 수 있는 공통적인 필드나 메소드를 부모 클래스에 선언할 수 있다.

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

    def validate_my_field(self, value):
        ...

class MySerializer(MyBaseSerializer):
    ...

 

      - Django의 Model과 ModelForm 클래스와 같이, 시리얼라이저의 내부 Meta 클래스는 부모의 내부 Meta 클래스로부터 암시적으로 상속받지 않는다. 

      - 만약 Meta 클래스가 부모 클래스로부터 상속을 받길 원한다면, 이를 명시적으로 해야 한다.

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

 

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

      - 추가로, 시리얼라이저 상속에 대해 다음과 같은 주의점들이 적용된다

         - 일반적인 파이썬 이름 해석 규칙이 적용된다. 

         - 만약 여러 기본 클래스가 Meta 내부 클래스를 선언한 경우, 첫 번째 클래스만 사용된다.

            - 이는 자식의 Meta가 존재한다면 그것을, 그렇지 않다면 첫 번째 부모의 Meta를 사용한다는 의미이다.
         - 부모 클래스로부터 상속받은 필드를 서브클래스에서 이름을 None으로 설정함으로써 선언적으로 제거하는 것이 가능하다.

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

class MySerializer(MyBaseSerializer):
    my_field = None

 

      - 그러나, 이 기법은 부모 클래스가 선언적으로 정의한 필드에서만 사용할 수 있다.

      - 이는 ModelSerializer가 기본 필드를 생성하는 것을 방지하지 않는다.

      - 기본 필드에서 선택적으로 제외하려면, 포함할 필드 지정을 참조해야 한다. 

 

      - 이 기법은 부모 클래스에서 선언적으로 정의한 필드를 선택적으로 제외하는 데 사용할 수 있다.

         - 하지만 이렇게 해도 ModelSerializer가 기본 필드를 생성하는 것은 막을 수 없다.

      - 기본 필드를 제외하려면, 어떤 필드를 포함할 것인지를 명시해야 한다.

      - 예를 들어, 부모 클래스에서 선언적으로 정의한 필드를 제외하려면 다음과 같이 할 수 있다.

from rest_framework import serializers

class ParentSerializer(serializers.Serializer):
    field1 = serializers.CharField(max_length=100)
    field2 = serializers.CharField(max_length=100)

class ChildSerializer(ParentSerializer):
    field1 = None


      - 위의 코드에서 ChildSerializer는 ParentSerializer의 field1을 상속받지 않는다.

      - 하지만 이렇게 해도 ModelSerializer가 기본 필드를 생성하는 것은 막을 수 없다.

      - 예를 들어, 모델에 field3이라는 필드가 있고 이를 ModelSerializer가 자동으로 생성하도록 설정되어 있다면, field3은 여전히 ChildSerializer에 포함된다.

      - 이를 제외하려면 다음과 같이 Meta 클래스에서 fields 옵션을 명시적으로 설정해야 한다.

class ChildSerializer(ParentSerializer):
    class Meta:
        fields = ['field2']

 

      - 이 코드는 ChildSerializer가 field2만 포함하도록 설정한다.

      - 따라서 ModelSerializer가 자동으로 생성하는 field3는 제외된다.

 

   3) 동적으로 필드 수정하기

      - 시리얼라이저가 초기화되면, 시리얼라이저에 설정된 필드의 사전은 .fields 속성을 사용하여 접근할 수 있다.

      - 이 속성에 접근하고 수정하면 시리얼라이저를 동적으로 수정할 수 있다.

 

      - 필드 인자를 직접 수정하면, 시리얼라이저를 선언하는 시점이 아니라 런타임에 시리얼라이저 필드의 인자를 변경하는 등 흥미로운 일을 할 수 있습니다.

 

      - 예시

         - 예를 들어, 시리얼라이저가 초기화될 때 어떤 필드를 사용할지 설정하고 싶다면, 다음과 같이 시리얼라이저 클래스를 생성할 수 있다.

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    """
    A ModelSerializer that takes an additional `fields` argument that
    controls which fields should be displayed.
    """

    def __init__(self, *args, **kwargs):
        # Don't pass the 'fields' arg up to the superclass
        fields = kwargs.pop('fields', None)

        # Instantiate the superclass normally
        super().__init__(*args, **kwargs)

        if fields is not None:
            # Drop any fields that are not specified in the `fields` argument.
            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'}

 

         - 예를 들어, 사용자 프로필을 관리하는 시스템에서 관리자는 모든 필드를 볼 수 있어야 하지만, 일반 사용자는 일부 필드만 볼 수 있어야 한다고 가정한다.

         - 위 코드에서 UserSerializer는 사용자의 'id', 'name', 'email', 'password' 필드를 가지고 있다. 그러나 __init__ 메소드에서, 요청을 보낸 사용자가 관리자가 아닌 경우 'email'과 'password' 필드를 제거하고 있다. 따라서 관리자는 모든 필드를 볼 수 있지만, 일반 사용자는 'id'와 'name' 필드만 볼 수 있게 된다.

from rest_framework import serializers

class UserSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=100)
    email = serializers.EmailField()
    password = serializers.CharField(max_length=100)

    def __init__(self, *args, **kwargs):
        user = kwargs['context']['request'].user
        super(UserSerializer, self).__init__(*args, **kwargs)

        if not user.is_admin:
            self.fields.pop('email')
            self.fields.pop('password')

 

   4) 기본 필드 커스터마이징하기

      - REST 프레임워크 2는 개발자가 ModelSerializer 클래스가 기본 필드 세트를 자동으로 생성하는 방식을 재정의할 수 있는 API를 제공하다.

      - 이 API에는 .get_field(), .get_pk_field() 및 기타 메소드가 포함되어 있었다.

      - 그러나 3.0으로 시리얼라이저가 기본적으로 재설계되었기 때문에 이 API는 더 이상 존재하지 않는다.

      - 여전히 생성되는 필드를 수정할 수는 있지만, 소스 코드를 참조해야 하며, 변경사항이 비공개 API에 반한다면 변경될 수 있음을 알아두어야 한다.

 

 7. Third party packages

   - jango REST marshmallow
      - django-rest-marshmallow 패키지는 python marshmallow 라이브러리를 사용한 시리얼라이저에 대한 대체 구현을 제공합니다. 이는 REST 프레임워크 시리얼라이저와 동일한 API를 노출하며, 일부 사용 사례에서 드롭인 교체로 사용할 수 있습니다.

   - Serpy
      - serpy 패키지는 속도를 위해 만들어진 시리얼라이저에 대한 대체 구현입니다. Serpy는 복잡한 데이터 타입을 간단한 기본 타입으로 직렬화합니다. 이 기본 타입은 쉽게 JSON이나 다른 필요한 형식으로 변환할 수 있습니다.

   - MongoengineModelSerializer
      - django-rest-framework-mongoengine 패키지는 Django REST 프레임워크의 저장 계층으로 MongoDB를 사용하는 것을 지원하는 MongoEngineModelSerializer 시리얼라이저 클래스를 제공합니다.

   - GeoFeatureModelSerializer
      - django-rest-framework-gis 패키지는 읽기와 쓰기 작업 모두에 대해 GeoJSON을 지원하는 GeoFeatureModelSerializer 시리얼라이저 클래스를 제공합니다.

   - HStoreSerializer
      - django-rest-framework-hstore 패키지는 django-hstore DictionaryField 모델 필드와 그 스키마 모드 기능을 지원하는 HStoreSerializer를 제공합니다.

   - Dynamic REST
      - dynamic-rest 패키지는 ModelSerializer와 ModelViewSet 인터페이스를 확장하며, 필터링, 정렬, 그리고 시리얼라이저에 의해 정의된 모든 필드와 관계를 포함/제외하는 API 쿼리 매개 변수를 추가합니다.

   - Dynamic Fields Mixin
      - drf-dynamic-fields 패키지는 URL 매개 변수로 지정된 부분 집합으로 시리얼라이저 당 필드를 동적으로 제한하는 믹스인을 제공합니다.

   - DRF FlexFields
      - drf-flex-fields 패키지는 ModelSerializer와 ModelViewSet를 확장하여 필드를 동적으로 설정하고 원시 필드를 중첩 모델로 확장하는 일반적으로 사용되는 기능을 제공합니다. 이는 URL 매개 변수 및 시리얼라이저 클래스 정의 모두에서 가능합니다.

   - Serializer Extensions
      - django-rest-framework-serializer-extensions 패키지는 필드가 뷰/요청 기반으로 정의되도록 허용함으로써 시리얼라이저를 DRY(Do not Repeat Yourself)하게 만드는 도구 모음을 제공합니다. 필드는 화이트리스트에 올릴 수 있으며, 블랙리스트에 올릴 수 있으며, 자식 시리얼라이저는 선택적으로 확장될 수 있습니다.

   - HTML JSON Forms
      - html-json-forms 패키지는 (비활성) HTML JSON Form 사양에 따른 <form> 제출을 처리하기 위한 알고리즘과 시리얼라이저를 제공합니다. 시리얼라이저는 HTML 내에서 임의로 중첩된 JSON 구조의 처리를 용이하게 합니다. 예를 들어, <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를 통해 직렬화된 데이터를 암호화하는 데 도움이 되는 패키지입니다. 또한 데이터를 암호화하는 데 도움이 되는 몇 가지 도우미 함수를 포함하고 있습니다.

 

 - 공식 사이트 문서 :

https://www.django-rest-framework.org/api-guide/serializers/#serializers

 

Serializers - Django REST framework

 

www.django-rest-framework.org

https://www.django-rest-framework.org/api-guide/fields/

 

Serializer fields - Django REST framework

 

www.django-rest-framework.org

https://www.django-rest-framework.org/api-guide/relations/

 

Serializer relations - Django REST framework

relations.py Data structures, not algorithms, are central to programming. — Rob Pike Relational fields are used to represent model relationships. They can be applied to ForeignKey, ManyToManyField and OneToOneField relationships, as well as to reverse re

www.django-rest-framework.org

https://velog.io/@mynghn/%EC%A0%9C%EB%84%A4%EB%A6%AD-%EB%B7%B0%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-API-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0