본문 바로가기
Django/Django REST framework

9-3 Serializer-3

by hyun-am 2021. 7. 15.

HyperlinkedModelSerializer

HyperlinkedModelSerializer 클래스는 기본 키가 아닌 하이퍼링크를 사용하여 관계를 나타내는 것을 제외하고 ModelSerializer 클래스와 유사합니다.

 

기본적으로 시리얼라이저는 기본 키 필드 대신 URL필드를 포함합니다.

 

url 필드는 HyperlinkedIdentityField 시리얼라이저 필드를 사용하여 표시되고 모델의 모든 관계는 HyperlinkedRelatedField 시리얼라이저 필드를 사용하여 표시됩니다.

 

다음과 같이 필드 옵션에 기본키를 추가하여 명시적으로 기본 키를 포함할 수 있습니다.

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

Absolute and relative URLs

HyperlinkedModelSerializer를 인스턴스화할 때 시리얼라이저 컨텍스트에 현재 요청을 포함해야 합니다. 예를들면 다음과 같습니다.

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

이렇게 하면 하이퍼링크에 적절한 호스트 이름이 포함될 수 있으므로 결과 표현이 다음과 같은 정규화된 URL을 사용할 수 있습니다.

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

다음과 같은 상대적인 URL 대신 사용할 수 있는 예시

/accounts/1/

상대경로 URL을 사용하려면 시리얼라이저 컨텍스트에서 {'request' : None}를 명시적으로 전달해야합니다.

How hyperlinked views are determined

모델 인스턴스에 대한 하이퍼링크에 사용할 view를 결정하는 방법이 필요합니다.

기본적으로 하이퍼링크는 '{model_name}-detail' 스타일과 일치하는 view이름에 해당해야 하며 pk 키워드 아규먼트로 인스턴스를 조회합니다.

 

다음과 같이 extra_kwargs 설정에서 view_name 및 lookup_field옵션 중 하나 또는 둘 다를 사용하여 URL 필드 view 이름 및 lookup_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 구성을 적절하게 일치시키는 것은 때때로 약간 까다로울 수 있습니다. HyperlinkedModelSerializer 인스턴스의 repr을 인쇄하는 것은 관계가 매핑될 것으로 예상되는 view 이름과 lookup_field를 정확히 검사하는 데 특히 유용한 방법입니다.

 

Changing the URL field name

URL필드의 일므은 기본적으로 'url'입니다. URL_FIELD_NAME 설정을 사용하여 전역적으로 재정의할 수 있습니다.

ListSerializer

ListSerializer 클래스는 한 번에 여러 개체를 직렬화하고 유효성을 검사하기 위한 동작을 제공합니다. 일반적으로 ListSeraializer를 직접 사용할 필요는 없지만 시리얼라이저를 인스턴스화할 때 대신 many=True를 전달해야 합니다.

 

allow_empty

이것은 기본적으로 True이지만 빈 리스트를 유요한 인풋으로 허용하지 않으려면 False로 설정하면 됩니다.

Customizing ListSerializer behavior

ListSerializer동작을 커스터마이징 하려는 몇 가지 사용 예시가 있습니다. 예시는 다음과 같습니다.

한 요소가 목록의 다른 요소와 충돌하지 않는지 확인하는 것과 같이 list의 특정 벨리데이션을 제공하려고 합니다.

여러 개체의 create 또는 update 동작을 커스터마이징 하려고합니다.

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

예시는 다음과 같습니다.

class CustomListSerializer(serializers.ListSerializer):
    ...

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

Customizing multiple create

다중 객체를 create를 위한 기본 구현은 단순히 list의 각 항목에 대해 .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

Customizing multiple update

기본적으로 ListSerializer클래스는 다중 update를 지원하지 않습니다. insert 및 delete에 대해 예상해야 하는 동작이 모호하기 때문입니다.

여러 update를 지원하려면 명시적으로 지원해야 합니다. 다중 업데이트 코드를 작성할 때 다음 사항을 염두해야합니다.

  • 데이터 list의 각 항목에 대해 update해야 하는 인스턴스를 어떻게 결정합니까?
  • insert는 어떻게 처리해야 합니까? 유효하지 않거나 새 객체를 생성합니까?
  • delete는 어떻게 처리해야 합니까? 객체 삭제 또는 관계 제거를 의미 합니까? 조용히 무시해야합니까? 아니면 유효하지 않습니까?
  • 순서는 어떻게 처리해야 하나요? 두 항목의 위치 변경이 상대 변경을 의미합니까 아니면 무시됩니까?

인스턴스를 시리얼라이저에 명시적 id 필드를 추가해야합니다. 암시적으로 생성된 기본 id 필드는 read_only로 표시됩니다. 이로 인해 업데이트 시 제거됩니다. 명시적으로 선언하면 listserializer update 메서드에서 사용할 수 있습니다.

 

다음은 여러 업데이트를 구현하도록 선택할 수 있는 방법의 예시입니다.

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

DRF2버전에 있던 allow_add_remove 동작과 유사한 여러 업데이트 작업에 대한 일부 자동 지원을 제공하는 3.1 릴리스와 함께 타사 패키지가 포함될 수 있습니다.

Customizing ListSerializer initialization

many=True인 시리얼라이저가 인스턴스화되면 자식 Serializer 클래스와 부모 ListSerializer 클래스 모두에 대해 .init()메서드에 전달해야 하는 아규먼트와 키워드 아규먼트를 결정해야 합니다.

경우에 따라 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)

BaseSerializer

대체 직렬화 및 역직렬화 스타일을 쉽게 지원하는 데 사용할 수 있는 BaseSerializer클래스입니다.

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

  • .data - Returns the outgoing primitive representation.
  • .is_valid() - Deserializes and validates incoming data.
  • .validated_data - Returns the validated incoming data.
  • .errors - Returns any errors during validation.
  • .save() - Persists the validated data into an object instance.

시리얼라이저에서 지원하려는 기능에 따라 재정의할 수 있는 네가지 메서드가 있습니다.

  • .to_representation() - Override this to support serialization, for read operations.
  • .to_internal_value() - Override this to support deserialization, for write operations.
  • .create() and .update() - Override either or both of these to support saving instances.

이 클래스는 Serializer 클래스와 동일한 인터페이스를 제공하므로 일반 Serializer 또는 ModelSerializer에서와 동일하게 기존 CBV와 함께 사용할 수 있습니다.

그렇게 할 때 알 수 있는 유일한 차이점은 BaseSerializer 클래스가 탐색 가능한 API에서 HTML 양식을 생성하지 않는다는 것입니다. 이는 반환하는 데이터에 각 필드를 적절한 HTML 인풋으로 렌더링할 수 있는 모든 필드 정보가 포함되어 있지 않기 때문입니다.

 

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)

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)

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

Advanced serializer usage

Overriding serialization and deserialization behavior

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

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

  • 새 시리얼라이저 기본 클래스에 대한 새 동작을 추가합니다.
  • 기존 클래스의 동작을 약간 수정합니다.
  • 많은 데이터를 반환하고 자주 액세스 하는 API 엔드포인트에 대한 직렬화 성능을 개선합니다.

이러한 메서드의 signatures는 다음과 같습니다.

 

.to_representation(self, instance)

직렬화가 필요한 개체 인스턴스를 가져오고 기본 표현을 반환해야 합니다. 일반적으로 이것은 내장된 python 데이터 유형의 구조를 반환하는 것을 의미합니다. 처리할 수 있는 정확한 유형은 API에 대해 구성한 렌더 클래스에 따라 다릅니다.

표현 스타일을 수정하기 위해 재정의될 수 있습니다. 예시는 다음과 같습니다.

def to_representation(self, instance):
    """Convert `username` to lowercase."""
    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)를 발생시켜야 합니다. errors 아규먼트는 필드 이름(또는 settings.NON_FIELD_ERRORS_KEY)을 오류 메시지 목록에 매핑하는 딕셔너리여야 합니다. 역직렬화 동작을 변경할 필요가 없고 대신 객체 수준 유효성 검사를 제공하려는 경우 대신 .validate()메서드를 재정의하는 것이 좋습니다.

이 메소드에 전달된 데이터 아규먼트는 일반적으로 request.data 값이므로 제공하는 데이터 유형은 API에 대해 구성한 파서 클래스에 따라 다릅니다.

Serializer Inheritance

Django 형식과 유사하게 상속을 통해 시리얼라이저를 확장하고 재사용할 수 있습니다. 이를 통해 여러 시리얼라이저에서 사용할 수 있는 상위 클래스의 공통 필드 또는 메서드 집합을 선언할 수 있습니다. 예를 들면 다음과 같습니다.

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

    def validate_my_field(self, value):
        ...

class MySerializer(MyBaseSerializer):
    ...

Django의 Model 및 ModelForm 클래스와 마찬가지로 시리얼라이저의 내부 Meta 클래스는 부모의 내부 Meta 클래스를 암시적으로 상속하지 않습니다. 메타 클래스가 상위 클래스에서 상속받도록 하려면 명시적으로 그렇게 해야합니다.

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

일반적으로 내부 메타 클래스에서 상속을 사용하지 않고 모든 옵션을 명시적으로 선언하는 것이 좋습니다.

또한 시리얼라이저 상속에는 다음 주의 사항이 적용됩니다.

  • 일반적인 Python 이름 확인 규칙이 적용됩니다. Meta 내부 클래스를 선언하는 기본 클래스가 여러 개인 경우 첫 번째 클래스만 사용됩니다. 이는 자식의 Meta(존재하는 경우), 그렇지 않은 경우 첫 번째 부모의 Meta 등을 의미합니다.
  • 하위 클래스에서 이름을 None으로 설정하여 상위 클래스에서 상속된 Field를 선언적으로 제거할 수 있습니다.
class MyBaseSerializer(ModelSerializer):
    my_field = serializers.CharField()

class MySerializer(MyBaseSerializer):
    my_field = None

그러나 이 기술은 부모 클래스에 의해 선언적으로 정의된 필드에서 옵트아웃하는 경우에만 사용할 수 있습니다. ModelSerializer가 기본 필드를 생성하는 것을 막지는 않습니다. 기본 필드에서 옵트아웃하려면 포함할 필드 지정을 참조하십시오.

 

9-2 Serializers-2

ModelSerializer 종종 Django 모델 정의와 밀접하게 매핑되는 serializer 클래스를 원할 것입니다. ModelSerializer 클래스는 모델 필드에 해당하는 필드가 있는 Serializer 클래스를 자동으로 만들 수 있는 short..

hyun-am-coding.tistory.com

Dynamically modifying fields

시리얼라이저가 초기화되면 시리얼라이저에 설정된 필드 딕셔너리는 .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(DynamicFieldsModelSerializer, self).__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'}

 

Customizing the default fields

DRF2는 개발자가 ModelSerializer 클래스가 기본 필드 셋을 자동으로 생성하는 방법을 재정의할 수 있는 API를 제공했습니다.

이 API에는 .get_field(), .get_pk_field() 및 기타 메서드가 포함되어 있습니다.

시리얼라이저가 3.0으로 근본적으로 재설계되었기 때문에 이 API는 더이상 존재하지 않습니다. 생성되는 필드를 계속 수정할 수 있지만 소스코드를 참조해야 하며 변경 사항이 API의 비공개 비트에 대한 변경인 경우 변경될 수 있습니다.

 

참고링크 : https://www.django-rest-framework.org/api-guide/serializers/

 

Serializers - Django REST framework

 

www.django-rest-framework.org

 

'Django > Django REST framework' 카테고리의 다른 글

11. Serializer relations  (0) 2021.07.18
10. Serializer fields  (0) 2021.07.16
9-2 Serializers-2  (0) 2021.07.15
9-1. Serializers  (0) 2021.07.15
8. Renderers  (0) 2021.07.07

댓글