Serializer는 입력값을 검증하는 역할을 할 수 있다. 이 글에서는 Serializer의 Validation 기능에 대해 알아보도록 하겠다.
가령 request 내부에 포함된 데이터를 이용해 유저를 만드는 경우를 생각해보자. UserCreateSerializer는 아래와 같이 작성 할 수 있다. 물론 ModelSerializer를 이용해 검증하는 방법도 있지만 동작을 확인하기 위해 Serializer를 사용하도록 하자.
class UserCreateSerializer(serializers.Serializer):
username = serializers.CharField() # ID
password = serializers.CharField() # 비밀번호
password2 = serializers.CharField() # 비밀번호 확인
하나의 필드를 검증 할 때
먼저 값 검증 로직을 작성하기전 어떤 값 검증이 있을 수 있는지 생각해보자. 첫번째로 필드 하나만을 이용하는 값이 있다. 예를 들어 username의 글자 수가 10자 이하로 제한한다고 하면 아래처럼 검증 로직을 추가할 수 있다.
class UserCreateSerializer(serializers.Serializer):
username = serializers.CharField() # ID
password = serializers.CharField() # 비밀번호
password2 = serializers.CharField() # 비밀번호 확인
def validate_username(self, username: str):
""" 유저 이름 검증 10자 초과시 ValidationError """
if len(username) > 10:
raise ValidationError("아이디는 10자 이내여야 합니다.")
return username
위 코드에서 본 것처럼 validate_{field name} 형식을 이용해 해당 필드의 검증 로직을 추가할 수 있다. 한편 이런 방식이 지저분하고 10자 이내로 작성하는 로직을 자주 사용한다면 아래처럼 validator를 이용할 수 도 있다. 이런식으로 작성했을 때 장점은 검증로직을 재사용할 수 있을뿐만 아니라 검증 로직을 다양하게 조합해서 커스텀화된 검증로직을 쉽게 만들 수 있다는 점이다. 물론 이 방법이 재사용성 측면에서는 좋은 방법이지만 항상 생각해야하는 건 삼진아웃 법칙에 따라 행동해야한다는 것이다. (3번 반복되었을 때 재사용성을 고려해서 리팩토링하는 방법)
def below_ten_character(value: str):
""" 유저 이름 검증 10자 초과시 ValidationError """
if len(value) > 10:
raise ValidationError("10자 이내여야 합니다.")
class UserCreateSerializer(serializers.Serializer):
username = serializers.CharField(validators=[below_ten_character]) # ID
password = serializers.CharField() # 비밀번호
password2 = serializers.CharField() # 비밀번호 확인
여러 필드를 조합해서 검증할 때
두번쨰로는 어떤 검증이 있을 수 있을까? 바로 여러 필드를 조합해서 검증할 수 도 있다. 가령 위의 예제에서 password와 password2가 동일해야하는 조건을 검증하려면 아래와 같이 작성할 수 있다.
class UserCreateSerializer(serializers.Serializer):
username = serializers.CharField() # ID
password = serializers.CharField() # 비밀번호
password2 = serializers.CharField() # 비밀번호 확인
def validate(self, attrs):
password = attrs['password']
passwords = attrs['password2']
if password != pasword2:
raise ValidationError("비밀번호를 확인해 주세요")
return username
위와 같이 작성할 수 있으며 validate 내부에서 여러 필드를 가져와 값을 검증 할 수 있다.
ValidationError
값 검증에 대한 이야기는 여기서 그만하고 이제 ValidationError에 대해 생각해보자. 위의 예제에서 우리가 정한 기준에 맞지 않으면 raise statement를 통해 예외를 발생시키도록 해놓았다. 그렇다면 이 부분은 어떤식으로 동작하는 걸까?
먼저 이 부분을 알기 위해선 view단에서 시리얼라이저가 어떤식으로 사용되는지 알아야한다.
아래 코드처럼 serializer내부에 request.data를 넣은 뒤 is_valid를 호출하면
시리얼라이저 내부에 validate 함수들을 실행하고
만약 실행도중 ValidationError가 발생하면 False를 반환하고 에러 메세지는 serializer.errors에서 확인할 수 있다.
def user_create(request):
serializer = UserCreateSerializer(data=request.data)
if serializer.is_valid():
user = serializer.create()
return Response({"message": "User Create Successfully"}, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)
serializer.errors를 살펴보면 만약 ValidationError가 여러 필드를 확인하는 validate 함수에서 발생했다면 아래와 같이 에러를 반환한다.
{"non_field_error" : "This is Error Message"}
만약 필드관련된 에러라면 아래처럼 반납한다.
{'username' : "10 words"}
만약 validation에 실패했을 때 신경쓰고 싶지 않다면 아래처럼도 가능하다.
def user_create(request):
serializer = UserCreateSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
user = serializer.create()
return Response({"message": "User Create Successfully"}, status=status.HTTP_200_OK)
raise_exception은 True일 때 HTTP_400에러와 함께 에러 메세지를 자동으로 반환하는 인자이다.
Serializer의 책임
DRF를 사용하다보면 Serializer의 책임은 어디까지인가? 라는 물음을 항상 갖는다. 실제로 Serializer는 다양하게 사용할 수 있지만 view단의 모든 로직을 serializer에 욱여 넣을 수는 없다. 내가 serializer를 책임을 정하는 기준은 상태 코드에 있다. raise_exception을 True로 주면 400이 status로 자동으로 지정된다. 400의 정의는 다음과 같다
400 Bad Request 응답 상태 코드는 서버가 클라이언트 오류(예: 잘못된 요청 구문, 유효하지 않은 요청 메시지 프레이밍, 또는 변조된 요청 라우팅) 를 감지해 요청을 처리할 수 없거나, 하지 않는다는 것을 의미합니다.
출처 : MDN
즉 Serializer의 책임은 클라이언트단의 잘못된 요청을 검증해주고 값을 적절히 조작해 응답하는데에 있다. 사실상 모든일을 한다고 생각하는 사람들도 있겠지만 가령 외부 API 서비스를 호출시 발생한 에러들은 424나 503을 반환하는데 이런 외부 API 호출 로직들은 serializer 내부에 넣지 않는게 DRF의 설계 방향이라고 생각한다.
내 생각
DRF에서 serializer를 만든 이유는 view단에 값 검증에 대한 부분을 추상화해서 관리하기 위함이라고 생각한다. serializer에서는 주로 ValidationError를 사용하고 응답역시 400을 반환한다. view만 보고는 serializer에서 어떤 구체적인 에러가 발생헀는지 알지 못하고 발생한 모든 에러가 클라이언트단에서 잘못된 요청으로 관리하는 것을 보고 이러한 점을 알 수 있다.
'Web Programming > Django' 카테고리의 다른 글
[Django] Django MySQL Fulltext Index 설정 (0) | 2021.11.07 |
---|---|
[pytest] Trouble Shooting (0) | 2021.11.06 |
[Django] Aggregate와 Annotate (5) | 2021.07.14 |
[Django] python manage.py를 하면 무슨 일이?(runserver 편) (0) | 2021.05.20 |
[장고백서] 필드에서 작성한 validator가 작동을 안할 때 (0) | 2021.04.09 |