validator는 서로 다른 유형의 필드 간에 유효성 검사 로직을 재사용할 때 유용할 수 있습니다.
-Django 문서
대부분의 경우 REST 프레임워크에서의 유효성 검사는 기본 필드 유효성 검사에 의존하거나, 직렬화기(serializer) 또는 필드 클래스에서 명시적인 유효성 검사 메서드를 작성하는 방식으로 처리됩니다.
그러나 때로는 유효성 검사 로직을 재사용 가능한 컴포넌트로 만들어 코드베이스 전체에서 쉽게 재사용하기를 원할 수도 있습니다. 이를 위해 유효성 검사 함수 또는 유효성 검사 클래스를 사용할 수 있습니다.
Django의 ModelForm 클래스에서의 유효성 검사와는 달리, Django REST 프레임워크 직렬화기에서의 유효성 검사는 약간 다르게 처리됩니다.
ModelForm에서는 유효성 검사가 일부는 form에서, 일부는 모델 인스턴스에서 수행됩니다. 하지만 REST 프레임워크에서는 유효성 검사가 오로지 직렬화기 클래스에서 수행됩니다. 이러한 방식은 다음과 같은 이유로 유리합니다:
repr을 출력하면 적용된 유효성 검사 규칙을 정확하게 보여줍니다. 모델 인스턴스에 대해 호출되는 숨겨진 유효성 검사 동작이 없습니다.ModelSerializer를 사용할 때는 이러한 작업이 모두 자동으로 처리됩니다. 대신 Serializer 클래스를 사용하고 싶다면 유효성 검사 규칙을 명시적으로 정의해야 합니다.
예시
REST 프레임워크가 명시적인 유효성 검사를 사용하는 방식의 예로, 고유성 제약이 있는 필드를 가진 간단한 모델 클래스를 살펴보겠습니다.
class CustomerReportRecord(models.Model):
time_raised = models.DateTimeField(default=timezone.now, editable=False)
reference = models.CharField(unique=True, max_length=20)
description = models.TextField()
CustomerReportRecord의 인스턴스를 생성하거나 업데이트하는 데 사용할 수 있는 기본적인 ModelSerializer는 다음과 같습니다:
class CustomerReportSerializer(serializers.ModelSerializer):
class Meta:
model = CustomerReportRecord
manage.py shell을 사용하여 Django 셸을 열고 다음을 실행하면
>>> from project.example.serializers import CustomerReportSerializer
>>> serializer = CustomerReportSerializer()
>>> print(repr(serializer))
CustomerReportSerializer():
id = IntegerField(label='ID', read_only=True)
time_raised = DateTimeField(read_only=True)
reference = CharField(max_length=20, validators=[<UniqueValidator(queryset=CustomerReportRecord.objects.all())>])
description = CharField(style={'type': 'textarea'})
여기서 흥미로운 부분은 reference 필드입니다. validator가 직렬화기 필드에서 고유성 제약을 명시적으로 적용하고 있음을 볼 수 있습니다.
이러한 명시적인 스타일 덕분에 REST 프레임워크에는 Django의 핵심에 없는 몇 가지 유효성 검사 클래스가 포함되어 있습니다. 이 클래스들은 아래에 자세히 설명되어 있습니다. Django와 마찬가지로 REST 프레임워크 validator 클래스들은 __eq__ 메서드를 구현하여 인스턴스 비교를 가능하게 합니다.
이 validator는 모델 필드에서 unique=True 제약을 적용하는 데 사용할 수 있습니다. 필수 인수 하나와 선택적인 messages 인수를 받습니다:
queryset (필수): 고유성이 적용될 queryset입니다.message: 유효성 검사가 실패할 때 사용될 오류 메시지입니다.lookup: 유효성 검사가 적용되는 값과 기존 인스턴스를 찾을 때 사용할 조회 방식입니다. 기본값은 'exact'입니다.이 유효성 검사는 직렬화기 필드에 적용되어야 하며, 예시는 다음과 같습니다:
from rest_framework.validators import UniqueValidator
slug = SlugField(
max_length=100,
validators=[UniqueValidator(queryset=BlogPost.objects.all())]
)
이 validator는 모델 인스턴스에서 unique_together 제약을 적용하는 데 사용할 수 있습니다. 필수 인수 두 개와 선택적인 message 인수를 받습니다:
queryset (필수): 고유성이 적용될 queryset입니다.fields (필수): 고유한 세트를 구성할 필드 이름들의 리스트 또는 튜플입니다. 이 필드들은 직렬화기 클래스에서 필드로 존재해야 합니다.message: 유효성 검사가 실패할 때 사용될 오류 메시지입니다.이 유효성 검사는 직렬화기 클래스에 적용되어야 하며, 예시는 다음과 같습니다:
from rest_framework.validators import UniqueTogetherValidator
class ExampleSerializer(serializers.Serializer):
# ...
class Meta:
# ToDo 항목들은 부모 목록에 속하며, 'position' 필드에 의해 정렬됩니다.
# 주어진 목록에서 두 항목이 같은 위치를 가질 수 없습니다.
validators = [
UniqueTogetherValidator(
queryset=ToDoItem.objects.all(),
fields=['list', 'position']
)
]
참고: UniqueTogetherValidator 클래스는 적용된 모든 필드들이 항상 필수로 처리된다는 암묵적인 제약을 강제합니다. 기본값이 있는 필드들은 예외이며, 기본값이 제공되기 때문에 사용자 입력에서 생략되더라도 값이 자동으로 공급됩니다.
이 validator들은 모델 인스턴스에서 unique_for_date, unique_for_month, unique_for_year 제약을 적용하는 데 사용할 수 있습니다. 다음과 같은 인수를 받습니다:
queryset (필수): 고유성이 적용될 queryset입니다.field (필수): 해당 날짜 범위에서 고유성이 검증될 필드 이름입니다. 이 필드는 직렬화기 클래스에 존재해야 합니다.date_field (필수): 고유성 제약을 위한 날짜 범위를 결정하는 데 사용할 필드 이름입니다. 이 필드는 직렬화기 클래스에 존재해야 합니다.message: 유효성 검사가 실패할 때 사용될 오류 메시지입니다.이 유효성 검사는 직렬화기 클래스에 적용되어야 하며, 예시는 다음과 같습니다:
from rest_framework.validators import UniqueForYearValidator
class ExampleSerializer(serializers.Serializer):
# ...
class Meta:
# 블로그 게시물은 현재 연도에 대해 고유한 슬러그를 가져야 합니다.
validators = [
UniqueForYearValidator(
queryset=BlogPostItem.objects.all(),
field='slug',
date_field='published'
)
]
유효성 검사에 사용되는 날짜 필드는 직렬화기 클래스에 반드시 있어야 합니다. 모델 클래스에서 default=...을 사용할 수는 없습니다. 이는 기본값이 유효성 검사 실행 후에 생성되기 때문입니다.
쓰기 가능한 날짜 필드 사용
날짜 필드를 쓰기 가능하게 하려면, 입력 데이터에 항상 포함되도록 기본 인수를 설정하거나 required=True로 설정해야 합니다.
published = serializers.DateTimeField(required=True)
읽기 전용 날짜 필드 사용
날짜 필드를 사용자가 수정할 수 없게 하고 싶다면, read_only=True를 설정하고 추가로 default=... 인수를 설정합니다.
published = serializers.DateTimeField(read_only=True, default=timezone.now)
숨김 날짜 필드 사용
날짜 필드를 사용자로부터 완전히 숨기고 싶다면, HiddenField를 사용합니다. 이 필드 유형은 사용자 입력을 받지 않지만 직렬화기에서 validated_data로 항상 기본값을 반환합니다.
published = serializers.HiddenField(default=timezone.now)
참고: UniqueFor<Range>Validator 클래스는 적용된 필드들이 항상 필수로 처리된다는 암묵적인 제약을 강제합니다. 기본값이 있는 필드들은 예외이며, 기본값이 제공되기 때문에 사용자 입력에서 생략되더라도 값이 자동으로 공급됩니다.
참고: HiddenField()는 partial=True 직렬화기(예: PATCH 요청 시)에서 나타나지 않습니다. 이 동작은 변경될 수 있으며, GitHub 논의를 통해 최신 정보를 확인할 수 있습니다.
validator가 serializer의 여러 필드에 적용될 때, API 클라이언트에서 제공하지 않아야 하지만, validator에 입력으로 제공되어야 하는 필드 입력이 필요할 때가 있습니다. 이를 위해 HiddenField를 사용할 수 있습니다. 이 필드는 validated_data에 포함되지만, serializer의 출력 표현에서는 사용되지 않습니다.
참고: read_only=True로 설정된 필드는 쓰기 가능한 필드에서 제외되므로, default=… 인자를 사용하지 않습니다. 이에 대한 자세한 내용은 3.8 버전 발표에서 확인할 수 있습니다.
REST framework에는 이와 관련하여 유용하게 사용할 수 있는 몇 가지 기본값이 포함되어 있습니다.
현재 사용자를 나타내는 데 사용할 수 있는 기본값 클래스입니다. 이를 사용하려면 request 객체가 serializer를 초기화할 때 context 딕셔너리의 일부로 제공되어야 합니다.
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
이 기본값 클래스는 생성 작업 중에만 기본값 인자를 설정하는 데 사용됩니다. 업데이트 중에는 해당 필드가 생략됩니다.
이 클래스는 기본값 또는 생성 작업 중에 사용될 callable을 인자로 받습니다.
created_at = serializers.DateTimeField(
default=serializers.CreateOnlyDefault(timezone.now)
)
일부 애매한 경우에는 기본 serializer 클래스에서 생성된 validator에 의존하기보다는 명시적으로 검증을 처리해야 할 때가 있습니다.
이 경우 Meta.validators 속성에 빈 리스트를 지정하여 자동으로 생성된 validator를 비활성화할 수 있습니다.
기본적으로 "unique together" 검증은 모든 필드가 required=True로 설정되도록 강제합니다. 하지만 경우에 따라 특정 필드에 required=False를 명시적으로 적용하고자 할 때도 있습니다. 이 경우 검증의 원하는 동작이 명확하지 않습니다.
이러한 경우, 일반적으로 serializer 클래스에서 validator를 제외하고, .validate() 메서드나 view에서 명시적으로 검증 로직을 작성해야 합니다.
예시:
class BillingRecordSerializer(serializers.ModelSerializer):
def validate(self, attrs):
# 여기 또는 view에서 사용자 정의 검증을 적용합니다.
class Meta:
fields = ['client', 'date', 'amount']
extra_kwargs = {'client': {'required': False}}
validators = [] # 기본 "unique together" 제약을 제거합니다.
기존 인스턴스에 대한 업데이트를 적용할 때, 고유성 validator는 현재 인스턴스를 고유성 검사에서 제외합니다. 현재 인스턴스는 serializer에서 instance=...로 전달되어 인스턴스로 제공되므로 고유성 검사의 맥락에서 사용할 수 있습니다.
하지만 중첩된 serializer에서 업데이트 작업을 수행할 때는 해당 인스턴스를 사용할 수 없어, 이러한 제외 처리를 할 수 없습니다.
이 경우에도 serializer 클래스에서 validator를 명시적으로 제거하고 .validate() 메서드나 view에서 검증 제약을 직접 작성하는 것이 좋습니다.
ModelSerializer 클래스가 생성할 동작이 명확하지 않을 때는 manage.py shell을 실행하고, serializer 인스턴스를 출력해 필드와 자동 생성된 validator를 확인하는 것이 좋습니다.
>>> serializer = MyComplexModelSerializer()
>>> print(serializer)
class MyComplexModelSerializer:
my_fields = ...
복잡한 경우에는 기본 ModelSerializer 동작에 의존하기보다는 명시적으로 serializer 클래스를 정의하는 것이 더 나을 수 있습니다. 이는 약간의 추가 코드를 요구하지만, 그 결과 동작이 더 명확해집니다.
Django의 기존 validator를 사용할 수 있을 뿐만 아니라, 커스텀 validator를 작성할 수도 있습니다.
validator는 실패 시 serializers.ValidationError를 발생시키는 모든 callable이 될 수 있습니다.
def even_number(value):
if value % 2 != 0:
raise serializers.ValidationError('이 필드는 짝수여야 합니다.')
필드 수준의 사용자 정의 검증을 지정하려면 Serializer 서브클래스에 .validate_<field_name> 메서드를 추가하면 됩니다. 이 내용은 Serializer 문서에서 설명되어 있습니다.
클래스 기반의 validator를 작성하려면 __call__ 메서드를 사용합니다. 클래스 기반 validator는 매개변수를 받아 동작을 재사용할 수 있기 때문에 유용합니다.
class MultipleOf:
def __init__(self, base):
self.base = base
def __call__(self, value):
if value % self.base != 0:
message = '이 필드는 %d의 배수여야 합니다.' % self.base
raise serializers.ValidationError(message)
고급 사례에서는 validator에 추가적인 컨텍스트로 사용 중인 serializer 필드를 전달해야 할 수도 있습니다. 이를 위해 requires_context = True 속성을 validator 클래스에 설정할 수 있습니다. 그러면 __call__ 메서드가 추가 인자로 serializer_field 또는 serializer와 함께 호출됩니다.
class MultipleOf:
requires_context = True
def __call__(self, value, serializer_field):
...
이렇게 하면 필드나 serializer에 대한 정보를 바탕으로 검증 로직을 작성할 수 있습니다.