본문 바로가기
  • 실행력이 모든걸 결정한다
Django Rest Framework

[DRF] 공식 문서 요약(1) - Serializers

by 김코더 김주역 2023. 5. 7.
반응형

1. Serializers

1) Serializer란?

- Queryset과 Model 인스턴스와 같은 복잡한 타입을 JSON, XML 등으로 쉽게 렌더링될 수 있는 고유의 파이썬 데이터 타입으로 변환해준다. 이를 직렬화라고 한다.

- 파싱된 데이터를 Queryset과 Model 인스턴스와 같은 복잡한 타입으로 변환해주는 역직렬화도 지원한다.

- DRF에서 제공하는 Serializer 클래스는 응답의 출력을 제어할 수 있는 강력하고 일반적인 방법을 제공한다. 또, Queryset과 Model 인스턴스들을 다루는 serializer인 ModelSerializer도 추가로 제공한다.

 

 

2) Serializer 선언하기

- Serializer 클래스를 상속받는 식으로 간단하게 만들 수 있다. 폼이나 모델과 유사한 모습이다.

from rest_framework import serializers

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

 

 

3) 직렬화하기

- 오브젝트를 시리얼라이저의 instance 파라미터로 넣어 시리얼라이저를 생성한다. instance는 기본 파라미터기 때문에 키워드는 생략 가능하다.

- 오브젝트를 렌더링한 결과는 Serializer의 data 속성에 있다.

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

그래서 최종적으로 다음과 같이 클라이언트에게 응답을 줄 수 있다.

return Response(serializer.data)

 

 

4) 역직렬화하기

- 직렬화된 데이터를 시리얼라이저의 data 파라미터로 넣어 시리얼라이저를 생성한다.

serializer = CommentSerializer(data=data)
serializer.is_valid() # True
serializer.validated_data # {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}

- is_valid()는 필드들의 유효성 검사를 수행해주는 메소드로 필수적으로 필요하다. 유효성 검사에 실패하면 오류 내용이 시리얼라이저의 errors 속성에 담기고, 유효성 검사에 통과한 데이터들은 validated_data 속성에 담기게 된다.

 

 

5) 인스턴스 저장하기

- 시리얼라이저를 통해 인스턴스를 생성하고 싶다면 create() 메소드를 구현하고, 인스턴스의 수정이 필요하다면 update() 메소드를 구현한다. create()와 update() 메소드는 둘 다 선택적으로 구현하면 된다.

- 유효성 검사(is_valid)에 통과한 데이터들이 담긴 validated_data로 인스턴스의 속성을 채워넣는 방식이다.

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

    def create(self, validated_data):
        return Comment(**validated_data)

    def update(self, instance, validated_data):
        instance.email = validated_data.get('email', instance.email)
        instance.content = validated_data.get('content', instance.content)
        instance.created = validated_data.get('created', instance.created)
        return instance

이후에 시리얼라이저의 save() 메소드를 호출해서 인스턴스를 저장하고 반환받을 수 있으며,

comment = serializer.save()

기존 인스턴스가 시리얼라이저를 통과했는지 여부에 따라 create(), update() 호출이 결정된다.

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

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

- 인스턴스를 저장하는 시점에 추가적인 데이터를 주입하고 싶다면 save() 메소드에 넣을 수도 있다. 추가된 키워드 인자는 create() 또는 update() 메소드가 호출될 때 validated_data에 포함된다.

serializer.save(owner=request.user)

- 인스턴스를 생성하거나 수정할 필요가 없는 경우에는 save() 메소드를 오버라이딩할 수 있다. 이 경우에는 self를 통해 validated_data에 직접 접근할 수 있다.

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

 

 

6) 검증

(1) 검증 결과

- 유효성 검사에 통과한 데이터들은 시리얼라이저의 validated_data에 저장되며, 인스턴스를 저장할 때는 validated_data에 담긴 데이터가 사용된다고 언급했다. 그래서 역직렬화시에는 반드시 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.']}

errors 딕셔너리의 각 키는 일반적으로 필드의 이름이고, 딕셔너리의 값은 해당 필드에 대한 에러 메시지 리스트다. 특정 필드 하나만의 에러가 아닌 경우에는 기본적으로 "non-field-errors"이 키 값이 되는데, 이 값을 커스터마이징 하고 싶다면 settings.py에서 NON_FIELD_ERRORS_KEY 항목을 지정하면 된다.

REST_FRAMEWORK = {
    'NON_FIELD_ERRORS_KEY': 'your_key_here',
}

 

(2) Field-level 검증

- validate_<field_name>() 메소드를 추가함으로써 검증을 요하는 특정 필드에 대한 검증 로직을 추가할 수 있다. 이 메소드는 검증된 값을 반환하거나 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

- errors의 키 값은 자동으로 필드의 이름으로 세팅된다. 

 

(3) Object-level 검증

- 특정 필드 하나에 대한 검증이 아닌 경우에는, 검증 로직을 validate() 메소드에 작성하면 된다. 

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

- errors의 키 값은 기본적으로 자동으로 "non-field-errors"으로 세팅된다.

 

(4) Validators

- 시리얼라이저의 각 필드에 validator들을 넣는 방법도 있다.

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

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

- 내부 Meta 클래스에 validator들을 넣는 방법도 있다.

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

DRF에서 제공하는 더 많은 Validator 클래스들을 알고 싶다면 아래 공식문서에서 확인하면 된다. 조만간 한글화를 해서 블로그에 올리도록 하겠다.

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

 

Validators - Django REST framework

 

www.django-rest-framework.org

 

 

7) 인스턴스의 초기 데이터에 접근하기

- 시리얼라이저를 생성할 때 파라미터로 instance를 지정했다면 시리얼라이저의 instance 속성을 통해 인스턴스에 접근할 수 있다. create() 메소드에서는 self.instance 값이 None이라는 사실 또한 알 수 있을 것이다.

- 시리얼라이저를 생성할 때 파라미터로 data를 지정했다면 시리얼라이저의 initial_data 속성을 통해 초기 데이터에 접근할 수 있다.

 

 

8) 일부 필드 수정하기

- 기본적으로 시리얼라이저는 필수 필드들을 넘겨받지 못하면 에러를 발생시킨다. partial=True로 지정함으로써 일부 필드를 수정하도록 허용할 수 있다.

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

 

 

9) 관계형 필드 다루기

- Serializer 클래스는 그 자체로 필드가 될 수 있다. 다음과 같이 중첩 관계를 표현할 수 있다.

class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField(max_length=100)

class CommentSerializer(serializers.Serializer):
    user = UserSerializer()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

- 자식 시리얼라이저에 required=False 옵션을 줌으로써 None도 허용 가능하다.

user = UserSerializer(required=False)  # May be an anonymous user.

- 자식 시리얼라이저가 여러 오브젝트를 나타내는 경우에는 many=True 옵션을 주면 된다.

edits = EditItemSerializer(many=True)  # A nested list of 'edit' items.

 

 

10) 쓰기 가능한 중첩 표현

- 중첩 시리얼라이저는 중첩된 데이터 구조로 주고 받는다. validated_data 속성 역시 중첩된 데이터 구조다.

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

- create(), update()에서는 다음과 같이 pop()을 활용하여 객체를 따로 생성하거나 수정하는 방식을 사용한다. 

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

 

 

    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

- 부모, 자식 오브젝트가 동시에 생성된다는 것을 효과적으로 캡슐화하기 위해 관리자 클래스에서 create() 메소드를 작성할 수도 있다.

class UserManager(models.Manager):
    ...

    def create(self, username, email, is_premium_member=False, has_support_contract=False):
        user = User(username=username, email=email)
        user.save()
        profile = Profile(
            user=user,
            is_premium_member=is_premium_member,
            has_support_contract=has_support_contract
        )
        profile.save()
        return user

이제 시리얼라이저의 create() 메소드에서는 관리 클래스에서 작성한 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']
    )

 

 

11) 다중 오브젝트 다루기

- Serializer 클래스는 오브젝트 리스트를 직렬화, 역직렬화할 수 있다.

 

(1) 다중 오브젝트 직렬화

- queryset이나 오브젝트 리스트를 다루기 위해 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을 참고하자.

 

 

12) 추가 context 포함하기

- 직렬화 외에도 추가적인 컨텍스트를 제공하고 싶다면 다음과 같이 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'}

- 컨텍스트 딕셔너리는 to_representation() 메소드 등 모든 직렬화 필드 로직 내에서 self.context로 접근 가능하다.

 

 

 

2. ModelSerializer

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

- 모델 정의와 밀접하게 연결되는 Serializer 클래스로, 모델을 기반으로 필드 집합과 validator들을 자동으로 생성해준다.

- create(), update()의 단순한 기본 구현이 포함되어 있다.

- 외래 키와 같은 모든 관계형 필드는 PrimaryKeyRelatedField에 매핑된다. 역방향 관계는 기본적으로 포함되지 않는다.

 

 

1) ModelSerializer 검사하기

- 필드의 상태를 검사하기 위해 다음과 같이 상세 표현 문자열을 확인해볼 수 있다. 

>>> 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) 포함할 필드 명시하기

- 모델 시리얼라이저에서 사용할 필드들을 명시하기 위해 fields 또는 exclude 옵션을 사용해야 한다. fields를 통해 직렬화해야 하는 모든 필드를 명시적으로 설정하는 방식이, 모델이 변경될 때 의도치 않게 데이터가 노출될 가능성이 줄어든다.

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

 

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

- '__all__'라는 특별한 값을 통해 모델의 모든 필드를 사용하도록 할 수 있다.

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

- fields 옵션안에 있는 이름은 인자를 필요로 하지 않는 속성이나 메소드와 매핑될 수 있다.

 

 

3) 중첩 직렬화 명시하기

- depth 옵션을 통해 중첩 표현을 쉽게 생성할 수 있다. depth 옵션은 직렬화할 때 관련 모델의 관계를 몇 단계 깊이까지 직렬화할지를 지정한다.

class BookSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True)

    class Meta:
        model = Book
        fields = ['id', 'title', 'author']
        depth = 1

만약에 지정된 depth까지 도달하지 않는 중첩 시리얼라이저의 경우에는 기본키(단일값)로 직렬화한다.

 

 

4) 추가 필드 명시하기

- 추가 필드를 명시하거나 기본 필드들을 오버라이딩할 수 있다.

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

source 옵션은 시리얼라이저 필드가 데이터를 어디서 가져와야 하는지를 지정하는 옵션으로, 위의 예시에서는 모델에 있는 get_absolute_url() 메소드의 반값을 사용한다.

- 추가 필드는 모델의 속성이나 호출 가능한 모든 항목과 연결될 수 있다.

 

 

5) 읽기 전용 필드들 명시하기

- 동시에 여러개의 read-only 필드를 지정하고 싶다면 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로 세팅된다.

- read-only 필드가 unique_together 제약조건의 일부인 경우가 있다. 이러한 특수한 경우에는 제약 조건의 검사를 위해 이 필드는 필수가 되며, 사용자에 의해 수정될 수도 없다. 이 경우를 다루기 위한 올바른 방법은 시리얼라이저에 해당 필드를 read_only=True, default=... 두 옵션을 포함하여 명시하는 것이다.

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

 

 

6) 추가 키워드 인자

- Meta 클래스의 extra_kwargs 딕셔너리 옵션을 통해 필드에 추가적인 키워드 인자들을 지정할 수 있다. 필드에 추가적인 옵션을 주기 위해 시리얼라이저에 추가적인 필드를 선언할 필요가 없게 된다.

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

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

- 시리얼라이저 필드가 이미 명시적으로 선언되어 있는 경우에는 extra_kwargs 옵션은 무시된다.

 

 

7) 관계형 필드

- 모델 인스턴스를 직렬화할 때 관계 표현을 위한 여러가지 방법이 있다. 기본적으로는 관계 인스턴스의 기본키를 사용한다. 대체 표현으로는 하이퍼링크, 완전한 중첩 표현, 사용자 정의 표현이 있다.

 

 

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

- 시리얼라이저를 인스턴스화할 때 시리얼라이저 필드가 자동으로 결정되는 방식을 변경하기 위해 재정의 할 수 있는 API를 제공한다.

- 일반적으로 ModelSerializer가 원하는 필드를 기본적으로 생성하지 않는 경우에는 클래스에 필드를 명시적으로 추가하거나 정규 Serializer 클래스를 사용해야 하지만, 모델에 대해 시리얼라이저 필드가 생성되는 방법을 정의하는 새 베이스 클래스를 만들 수 있다.

 

(1) 필드 클래스 커스터마이징

● serializer_field_mapping

- 모델 필드를 시리얼라이저 필드에 매핑한다. 이 매핑을 재정의하여 각 모델 필드에 사용할 기본 시리얼라이저 필드를 변경할 수 있다.

class MyModelSerializer(serializers.ModelSerializer):
    serializer_field_mapping = {
        models.IntegerField: serializers.IntegerField,
        models.CharField: serializers.CharField,
        models.BooleanField: serializers.BooleanField,
        # ...
    }

● serializer_related_field

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

serializer_related_field = PrimaryKeyRelatedField

- ModelSerializer의 경우 기본값으로 serializers.PrimaryKeyRelatedField를 사용하고, HyperlinkedModelSerializer의 경우 기본값으로 serializers.HyperlinkedRelatedField를 사용한다.

● serializer_url_field

- 시리얼라이저의 모든 url 필드에 사용될 시리얼라이저 필드 클래스로, 기본값으로 serializers.HyperlinkedIdentityField를 사용한다.

● serializer_choice_field

- 시리얼라이저의 모든 선택 필드에 사용될 시리얼라이저 필드 클래스로, 기본값으로 serializers.ChoiceField를 사용한다.

 

(2) 필드 클래스 및 키워드 인자 커스터마이징

다음 메소드를 통해 시리얼라이저에 자동으로 포함되어야 하는 필드 클래스 및 키워드 인자를 결정한다. 각 메소드는 두 개의 튜플 (field_class, field_kwargs)을 반환해야 한다.

● build_standard_field(self, field_name, model_field)

- 표준 모델 필드에 매핑되는 시리얼라이저 필드를 생성하기 위해 호출된다. 기본적으로 serializer_field_mapping 속성을 기반으로한 시리얼라이저 클래스를 반환하도록 구현되어있다.

def build_standard_field(self, field_name, model_field):
    # Make all fields optional
    model_field.blank = True
    return super().build_standard_field(field_name, model_field)

● 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을 기반으로한 중첩 시리얼라이저 클래스를 동적으로 생성하도록 구현되어있다.

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

- 필드 이름이 모델 필드나 모델 속성에 매핑되지 않은 경우 호출된다. 하위 클래스가 이 동작을 커스터마이징할 수 있지만 기본 구현에서는 오류가 발생한다.

 

 

 

3. HyperlinkedModelSerializer

(작성중)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

댓글