[Django REST Framework] Serializers 정리

19341 단어 djangodrfdjango

Serializers: 쿼리셋이나 모델 instance 등 복잡한 데이터를 JSON, XML 등의 content type들로 변환해주며, 역으로 JSON, XML 등의 데이터를 다시 쿼리셋, 모델 인스턴스로 변환해준다.(deserialize)

serializer.is_valid()

create, update 와 같이 deserialize 과정이 필요할 때 유효성 검사를 위해 호출해야 하는 함수

  • 모델에서 지정한 필드 타입과 필드 조건(Unique=True, blank=False 등)에 충족하는 데이터인지 검사
  • 조건을 충족하면 validated_data에 유효성을 통과한 데이터를 넣어준다.
  • 이후 serializer.save()를 수행하면 리소스가 생성되거나 업데이트 된다.

serializer.is_valid() 에러 만들기

데이터가 유효성 검사를 통과하지 못하면 에러 응답을 해야 하는데 단순히 다음과 같이 만들면 유효성 검사를 통과하지 못할 경우 메시지 없이 500(내부 서버 오류) 상태 코드를 반환하게 된다.

serializer = RegisterSerializer(data=request.data)
serializer.is_valid()
serializer.save()
return Response({'message': message.REGISTER_SUCCESS}, status=status.HTTP_201_CREATED)
  • if 문을 사용한 에러 처리

serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
	serializer.save()
	return Response({'message': message.REGISTER_SUCCESS}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

위 코드와 같이 if문을 사용하여 유효성 검사가 False일 때 에러 처리를 할 수 있다.
serializer.errors를 통해 key, value 형태로 유효성을 통과하지 못한 필드와 그 이유(ex. "user with this email already exist")가 담겨 응답 메시지로 보내진다.

  • raise_exception을 사용한 에러 처리
serializer = RegisterSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({'message': message.REGISTER_SUCCESS}, status=status.HTTP_201_CREATED)

위 코드처럼 serializer.is_valid(raise_exception=True)를 사용하면 코드를 줄이고 에러 처리를 할 수 있다. 유효성 검사를 통과 못하면 알아서 serializer.errors와 함께 상태 코드 역시 따로 지정할 필요없이 400 코드를 반환하게 된다.

serializer.save()

유효성 검사를 통과한 데이터(validated_data)는 serializer.save()로 넘겨지는데 리소스가 없는 경우 create(), 있는 경우 update()를 하게 된다.

# serializer.save() 내부 코드 중 일부
# 기존에 존재하는 리소스이면 update, 아니면 create로 처리

        if self.instance is not None:
            self.instance = self.update(self.instance, validated_data)
            assert self.instance is not None, (
                '`update()` did not return an object instance.'
            )
        else:
            self.instance = self.create(validated_data)
            assert self.instance is not None, (
                '`create()` did not return an object instance.'
            )

create, update 함수는 오버라이딩을 통해 조건을 추가할 수 있다.

from django.contrib.auth.hashers import make_password

class RegisterSerializer(serializers.ModelSerializer):
    security_code = SecurityCodeSerializer(read_only=True)
    password2 = serializers.CharField(max_length=128, read_only=True)
    class Meta:
        model = User
        fields = [
            'id',
            'email',
            'password',
            'password2',
            'nickname',
            'name',
            'phone',
            'security_code'
        ]
    def create(self, validated_data):
        validated_data['password'] = make_password(validated_data['password'])
        return super().create(validated_data)

위와 같이 리소스를 생성할 때 serializers 클래스에서 create 함수를 작성하고 조건을 추가해준다.(여기서는 password가 해싱되어 저장하도록 함), update 함수도 동일한 방식으로 수정.

  • 차이점
    • create()는 validated_data 1개의 인자를 받는다.
    • update()는 instance와 validated_data 2개의 인자를 받고 다시 instance를 반환한다.(기존에 존재하는 리소스를 업데이트 하는 것이기 때문에 해당 instance가 필요함)
    
    #update 예시
    def update(self, instance, validated_data):
        new_password = make_password(validated_data['password'])
        instance.password = new_password
        instance.save()
        return instance

validation

is_valid()를 통한 유효성 검사 이외에도 사용자 정의에 의한 유효성 검사를 할 수 있다. 순서상으로는 모델에서 지정한 필드 타입/조건에 대한 유효성 검사를 먼저 진행한 다음 사용자 정의 유효성 검사가 진행된다.

object_level validation

여러 필드에 대한 접근이 필요할 때 사용하는 validation으로 validate() 함수를 통해 만들 수 있다.

class ResetPasswordSerializer(serializers.ModelSerializer):
   security_code = SecurityCodeSerializer(read_only=True)
   password2 = serializers.CharField(read_only=True)
   class Meta:
       model = User
       fields = [
           'security_code',
           'email',
           'password',
           'password2'
       ]

   def validate(self, data):
       input_password = data['password']
       input_email = data['email'].split("@")[0]
       nickname = User.objects.get(email=data['email']).nickname
       if not 7 < len(input_password) < 13:
           raise serializers.ValidationError(message.PASSWORD_LENGTH_ERROR)

       if not any(char.isdigit() for char in input_password):
           raise serializers.ValidationError(message.PASSWORD_COMBINATION_ERROR)

       if not any(char.isalpha() for char in input_password):
           raise serializers.ValidationError(message.PASSWORD_COMBINATION_ERROR)

       if input_password in input_email \
               or input_email in input_password:
           raise serializers.ValidationError(message.PASSWORD_UNIQUE_ERROR)

       if input_password in nickname \
               or nickname in input_password:
           raise serializers.ValidationError(message.PASSWORD_UNIQUE_ERROR)
       return data

위와 같이 비밀번호 재설정을 위한 유효성 검사가 필요하고 여러 필드에 대한 접근이 필요할 때 validate()함수로 object-level의 유효성 검사를 만들 수 있다.

field_level validation

하나의 필드에 대한 유효성 검사를 진행할 때는 validate_필드명(self, value) 형식으로 field-level 유효성 검사를 할 수 있다.

class PhoneVerifySerializer(serializers.ModelSerializer):
   class Meta:
       model = PhoneVerification
       fields = [
           'id',
           'phone',
           'security_code'
       ]

   def validate_phone(self, value):
       if any(num.isalpha() for num in value):
           raise serializers.ValidationError(message.PHONE_NUMBER_ERROR)
       return value

field-level이기 때문에 인자로 받는 value는 phone 데이터이다.

좋은 웹페이지 즐겨찾기