Serializers: 쿼리셋이나 모델 instance 등 복잡한 데이터를 JSON, XML 등의 content type들로 변환해주며, 역으로 JSON, XML 등의 데이터를 다시 쿼리셋, 모델 인스턴스로 변환해준다.(deserialize)
create, update 와 같이 deserialize 과정이 필요할 때 유효성 검사를 위해 호출해야 하는 함수
데이터가 유효성 검사를 통과하지 못하면 에러 응답을 해야 하는데 단순히 다음과 같이 만들면 유효성 검사를 통과하지 못할 경우 메시지 없이 500(내부 서버 오류) 상태 코드를 반환하게 된다.
serializer = RegisterSerializer(data=request.data)
serializer.is_valid()
serializer.save()
return Response({'message': message.REGISTER_SUCCESS}, status=status.HTTP_201_CREATED)
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")가 담겨 응답 메시지로 보내진다.
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 코드를 반환하게 된다.
유효성 검사를 통과한 데이터(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 함수도 동일한 방식으로 수정.
#update 예시
def update(self, instance, validated_data):
new_password = make_password(validated_data['password'])
instance.password = new_password
instance.save()
return instance
is_valid()를 통한 유효성 검사 이외에도 사용자 정의에 의한 유효성 검사를 할 수 있다. 순서상으로는 모델에서 지정한 필드 타입/조건에 대한 유효성 검사를 먼저 진행한 다음 사용자 정의 유효성 검사가 진행된다.
여러 필드에 대한 접근이 필요할 때 사용하는 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의 유효성 검사를 만들 수 있다.
하나의 필드에 대한 유효성 검사를 진행할 때는 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 데이터이다.