[엉박사] 1.5 serializers.py

impala·2023년 1월 12일
0
post-thumbnail

1.5 serializers.py

장고와 비교했을 때 DRF의 가장 큰 특징은 바로 Response의 형식이 JSON이라는 점이다.
장고는 풀스택 프레임워크이기 때문에 클라이언트가 요청을 하면 최종적으로 HTML파일을 만들어 보내주지만, DRF는 백엔드 API서버를 위한 프레임워크이기 때문에 클라이언트의 요청에 JSON형식으로 응답한다.

이를 위해서는 서버에서 사용하는 인스턴스를 JSON형식으로 변환하거나, JSON형식으로 들어온 데이터를 인스턴스로 변환할 수 있어야 하는데, 이 작업을 담당하는 것이 Serializer클래스이다. 즉, 시리얼라이저란 DRF API서버가 클라이언트와 데이터를 주고받기 위해 중간에서 통역을 해주는 클래스라고 볼 수 있다.

1.5.1 Serializer

DRF의 기본 Serializer class로 직렬화/역직렬화할 필드들을 선언하고 기본적인 메소드들을 제공한다. 단, save() 메소드를 사용하기 위해서는 create() 메소드와 update() 메소드를 오버라이딩하거나 save() 메소드를 직접 오버라이딩하여 구현해야 한다.

class CommentSerializer(serializers.Serializer):
    # 직렬화/역직렬화할 필드 선언
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

    # save()메소드를 사용하기 위해 오버라이딩하여 구현
    # 새로운 인스턴스 생성시 호출
    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

만약 요청으로 들어온 데이터를 역직렬화하는 경우, 반드시 is_valid() 메소드를 통해 데이터가 유효한지 검증한 후 사용해야 한다.
isvalid()메소드는 내부적으로 **validate<필드명>(self, value) 이나 validate(self, data) 메소드를 호출하는데 기본적으로 제공되지만 커스터마이징이 필요한 경우 오버라이딩하여 직접 구현할 수 있다.
다른 방법으로는
Meta class 의 validators** 필드에 필요한 validator를 선언하는 방법도 있다.

# views.py
# 역직렬화시 반드시 is_valid()메소드를 호출해야 함
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()

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

    # validate_<필드명>
    def validate_title(self, value):
        if 'django' not in value.lower():
            raise serializers.ValidationError("Blog post is not about Django")
        return value

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

    # validatea메소드를 직접 오버라이딩
    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
# ex3)
class EventSerializer(serializers.Serializer):
    name = serializers.CharField()
    room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
    date = serializers.DateField()

    # Meta class에 필요한 validator 선언
    class Meta:
        # Each room only has one event per day.
        validators = [
            UniqueTogetherValidator(
                queryset=Event.objects.all(),
                fields=['room_number', 'date']
            )
        ]

1.5.2 ModelSerializer

대부분의 서비스에서 모델 그 자체를 직렬화/역직렬화하는 작업이 필요하기 때문에 DRF에서는 이 작업을 ModelSerialiser를 통해 편리하게 제공한다.

ModelSerializer의 역할은 일반 Serializer와 같지만 다음과 같은 특징이 있다.

  • 모델에 기반하여 자동으로 직렬화/역직렬화 할 필드를 만들어준다
  • 자동으로 기본적인 validator를 만들어 제공한다
  • 자동으로 기본적인 create(), update()메소드를 구현해준다

ModelSerialiser를 사용하는 방법은 아래와 같다.

class AccountSerializer(serializers.ModelSerializer):

    # Meta class안에 직렬화/역직렬화에 필요한 옵션을 작성
    class Meta:
        model = Account
        fields = '__all__'
        # exclude, depth, read_only_fields, validators, extra_kwargs등 여러 옵션이 있음
    
    # 커스터마이징이 필요한 경우 기본 메소드들을 오버라이딩하여 구현

1.5.3 BaseSerializer

DRF의 Serializer는 BaseSerializer를 상속받아 구현된 클래스이다. 따라서 보다 다양한 커스터마이징이 필요하다면 Serializer의 부모 클래스인 BaseSerializer를 상속받아 필요한 기능들을 직접 구현할 수 있다.

BaseSerializer에 정의된 기본 필드 및 메소드들은 다음과 같다.

fields, methods설명
.data직렬화 된 데이터를 반환
.validated_data유효성 검사가 완료된 데이터를 반환
.errors유효성 검사중 발생한 오류를 반환
.is_valid()들어오는 데이터를 역직렬화하고 유효한지 검사함
.create()새로운 인스턴스를 저장
.update()기존 인스턴스를 수정하여 저장
.save()유효성 검사가 완료된 데이터를 저장
.to_representation()읽기 작업에 대해 직렬화
.to_internal_value()쓰기 작업에 대해 역직렬화

1.5.4 Serializer Customizing

이번 프로젝트에서는 데이터가 들어오고 나갈 때 시리얼라이저에서 추가적인 로직을 처리하도록 구현하였기 때문에 시리얼라이저를 커스터마이징할 필요가 많이 있었는데, 주로 Serializer나 BaseSerializer클래스를 상속받아 아래 두 메소드를 오버라이딩하여 구현하였다.

to_representation(self, instance)

직렬화가 필요한 인스턴스를 입력받아 직렬화 된 데이터를 반환한다. 직렬화된 데이터는 JSON형식으로 각각은 Python의 기본 datatype이어야 한다.

def to_representation(self, instance):
    # 부모 클래스의 to_representation의 결과를 수정하여 반환
    ret = super().to_representation(instance)
    ret['username'] = ret['username'].lower()
    return ret

to_initial_value(self, data)

validation되지 않은 JSON형식의 데이터를 입력받아 validated_data를 반환한다.
유효성 검사가 완료된 후 역직렬화된 데이터는 .validated_data, .create(), .update(), .save()에 전달된다.
만약 유효성 검사에 실패하면 .errors에 오류메세지가 매핑되어야 한다.

    def to_internal_value(self, data):
        # data의 일부만 역직렬화가 필요한 경우 
        # 필요한 데이터를 추출하여 부모클래스의 to_internal_value를 호출
        resource_data = data['resource']
        return super().to_internal_value(resource_data)

위의 두 메소드를 오버라이딩하여 커스터마이징하는 경우 아래와 같은 장점이 있다.

  • 기존 serializer에 새로운 작업을 추가
  • 기존 serializer의 작업을 수정
  • 기존 serializer의 성능 개선

0개의 댓글