본문 바로가기
Django/Django REST framework

12-Validators

by hyun-am 2021. 8. 18.

Validators

DRF에서 유효성 검사를 처리하는 대부분의 경우 단순히 기본 필드 유효성 검사에 의존하거나 serialzier 또는 필드 클래스에 대한 명시적 유효성 검사 메서드를 작성합니다.

그러나 때로는 유효성 검사 로직을 재사용 가능한 구성 요소에 배치하여 코드베이스 전체에서 쉽게 재사용할 수 있도록 하고 싶을 때가 있습니다. 이것은 벨리데이터 함수와 벨리데이터 클래스를 사용하여 해결할 수 있습니다.

Validation in REST framework

DRF에서 serializer의 벨리데이션은 Django의 ModelForm 클래스에서 벨리데이션이 작동하는 방식과 약간 다르게 처리됩니다.

ModelForm을 사용하면 벨리데이션 폼에서 부분적(partial)으로 실행되고 모델 인스턴스에서 부분적으로 수행됩니다. DRF를 사용하면 벨리데이션이 전적으로 serializer클래스에서 수행됩니다. 이는 다음과 같은 이유로 유리합니다.

  • 문제를 적절하게 분리하여 코드 동작을 보다 명확하게 만듭니다.
  • shortcut ModelSerializer 클래스 사용과 명시적 Serializer 클래스 사용 사이를 쉽게 전환할 수 있습니다. ModelSerialzier에 사용되는 모든 벨리데이션은 복제하기 쉽습니다.
  • Serializer 인스턴스의 repr을 인쇄하면 적용되는 벨리데이션 규칙이 정확히 표시됩니다. 모델 인스턴스에서 추가로 숨겨진 벨리데이션 동작이 호출되지 않습니다.

ModelSerializer를 사용하면 이 모든 것이 자동으로 처리됩니다. 대신 Serializer 클래스를 사용하도록 DroupDown하려면 벨리데이션 규칙을 명시적으로 정의해야 합니다.

예시는 다음과 같습니다.

DRF가 명시적 벨리데이션을 사용하는 방법의 예시로 고유성(unique)조건이 있는 필드가 있는 간단한 모델 클래스를 사용합니다.

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

python manage.py shell을 통해 쉘을 실행하면 다음과 같은 것을 확인할 수 있습니다.

>>> 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 필드입니다.유니크 제약 조건이 serializer필드의 벨리데이션에 의해 명시적으로 적용되고 있음을 알 수 있습니다.

이 보다 명시적인 스타일 때문에 DRF에는 핵심 Django에서 사용할 수 없는 몇 가지 벨리데이션 클래스가 포함되어 있습니다. 이러한 클래스는 아래에 자세히 설명되어 있습니다.

UniqueValidator

이 유효성 벨리데이터를 사용하여 모델 필드에 unique=True 제약 조건을 적용할 수 있습니다. 하나의 필수 아규먼트와 선택적 메세지 아규먼트가 필요합니다.

  • queryset : (필수) 이것은 고유성(unique)를 적용해야 하는 쿼리셋입니다.
  • message : 벨리데이션이 실패할 때 사용해야 하는 오류 메시지입니다.
  • lookup : 값의 유효성을 검사하는 기존 인스턴스를 찾는 데 사용되는 lookup입니다. 기본값은 'exact'입니다.

이 벨리데이터는 다음과 같이 시리얼라이저 필드에 적용해야 합니다.

from rest_framework.validators import UniqueValidator

slug = SlugField(
    max_length=100,
    validators=[UniqueValidator(queryset=BlogPost.objects.all())]
)

UniqueTogetherValidator

이 벨리데이터를 사용하여 모델 인스턴스에 unique_together 제약 조건을 적용할 수 있습니다. 두 개의 팔수 아규먼트와 단일 선택적 메시지 아규먼트가 있습니다.

  • queryset : (필수) 이것은 고유성(unique)를 적용해야 하는 쿼리셋입니다.
  • fields : (필수) 고유한 set을 만들어야 하는 필드 이름의 목록 또는 튜플입니다. 이것들은 serializer 클래스의 필드로 존재해야 합니다.
  • message : 벨리데이션이 실패할 때 사용해야 하는 오류 메시지입니다.

이 벨리데이터는 다음과 같이 시리얼라이저 클래스에 적용해야 합니다.

from rest_framework.validators import UniqueTogetherValidator

class ExampleSerializer(serializers.Serializer):
    # ...
    class Meta:
        # ToDo items belong to a parent list, and have an ordering defined
        # by the 'position' field. No two items in a given list may share
        # the same position.
        validators = [
            UniqueTogetherValidator(
                queryset=ToDoItem.objects.all(),
                fields=['list', 'position']
            )
        ]

참고 : UniqueTogetherValidator클래스는 적용되는 모든 필드가 항상 필수로 처리된다는 암시적 제약 조건을 항상 부과합니다. 기본값이 있는 필드는 사용자 입력에서 생략된 경우에도 항상 값을 제공하므로 이에 대한 예외입니다.

UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator

이러한 벨리데이터를 사용하여 모델 인스턴스에 unique_for_date, unique_for_month 및 unique_for_year제약 조건을 적용할 수 있습니다. 그들은 다음과 같은 아규먼트를 취합니다.

  • queryset : (필수) 이것은 고유성(unique)를 적용해야 하는 쿼리셋입니다.
  • field : (필수) 지정된 날짜 범위의 고유성을 검증할 필드 이름입니다. 이 것은 serializer의 필드로 존재해야 합니다.
  • date_field : (필수) 고유성 제약 조건의 날짜 범위를 결정하는 데 사용할 필드 이름입니다. 이것은 serializer의 필드로 존재해야합니다.

이 벨리데이터는 다음과 같이 시리얼라이저 클래스에 적용해야 합니다.

from rest_framework.validators import UniqueForYearValidator

class ExampleSerializer(serializers.Serializer):
    # ...
    class Meta:
        # Blog posts should have a slug that is unique for the current year.
        validators = [
            UniqueForYearValidator(
                queryset=BlogPostItem.objects.all(),
                field='slug',
                date_field='published'
            )
        ]

벨리데이션에 사용되는 날짜 필드는 항상 serializer 클래스에 있어야 합니다. 기본값으로 사용되는 값은 벨리데이션이 실행될 때까지 생성되지 않기 때문에 모델 클래스 default=...에 단순히 의존할 수 없습니다.

API의 작동 방식에 따라 사용할 수 있는 몇가지 스타일이 있습니다. ModelSerializer를 사용하는 경우 DRF가 자동으로 생성하는 기본값에 의존할 수 있지만 Serialzier를 사용하거나 단순히 더 명시적인 제어를 원하는 경우 아래에 설명된 스타일 중 하나를 사용할 수 있습니다.

Using with a writable date field.

날짜 필드가 쓰기 가능하도록 하려면 기본 인수를 설정하거나 required=True를 설정하여 입력 데이터에서 항상 사용할 수 있는지를 확인해야 합니다.

published = serializers.DateTimeField(required=True)

Using with a read-only date field.

날짜 필드를 표시하지만 사용자가 편집할 수 없도록 하려면 read_only=True를 설정하고 default=... 아규먼트를 추가로 설정하면 되겠습니다.

published = serializers.DateTimeField(read_only=True, default=timezone.now)

Using with a hidden date field.

날짜 필드를 사용자에게 완전히 숨기려면 HiddenField를 사용하면됩니다. 이 필드 유형은 사용자 입력을 허용하지 않지만 serializer의 validated_data에 기본값을 반환합니다.

published = serializers.HiddenField(default=timezone.now)

참고 : UniqueFor<Range>Validator 클래스는 적용되는 필드가 항상 필수로 처리된다는 암시적 제약을 부과합니다. 기본값이 있는 필드는 사용자 입력에서 생략된 경우에도 항상 값을 제공하므로 이에 대한 예외입니다.

Advanced field defaults

serializer의 여러 필드에 적용되는 validator는 API 클라이언트에서 제공해서는 안 되지만 validator에 대한 입력으로 사용할 수 있는 필드 입력이 필요할 수 있습니다.

이러한 종류의 validation에 사용할 수 있는 두가지 패턴은 다음과 같습니다.

  • HiddenField사용하기, 이 필드는 validated_data에 있지만 serializer 아웃풋 표현에서는 사용되지 않습니다.
  • read_only=True와 함께 표준 필드를 사용하지만 여기에는 default=... 아규먼트도 포함됩니다. 이 필드는 serializer에서 사용되지만 사용자가 직접 설정할 수는 없습니다.

DRF에는 이 context에서 유용할 수 있는 몇가지 기본값들이 있습니다. 그것들은 다음과 같습니다.

CurrentUserDefault

현재 사용자를 나타내는 데 사용할 수 있는 기본 클래스입니다. 이를 사용하려면 serializer을 인스턴스화할 때 context dictionary의 일부로 'request'가 제공되어야 합니다.

owner = serializers.HiddenField(
    default=serializers.CurrentUserDefault()
)

CreateOnlyDefault

생성 작업 중에 기본 아규먼트를 설정하는 데만 사용할 수 있는 기본 클래스입니다. update하는 중에는 일부 필드가 생략됩니다.

생성 작업 중에 사용해야 하는 기본값 또는 호출 가능한 단일 아규먼트를 사용합니다.

created_at = serializers.DateTimeField(
    default=serializers.CreateOnlyDefault(timezone.now)
)

Limitations of validators(벨리데이터의 한계)

ModelSerializer가 생성하는 default serializer 클래스에 의존하는 대신 명시적으로 validation을 처리해야 하는 모호한 경우가 있습니다.

이러한 경우 serializer에 Meta.validators 속성에 대해 빈 리스트를 지정하여 자동으로 생성된 validator를 비활성화 할 수 있습니다.

Optional fields

기본적으로 "unique together" 유효성 검사는 모든 필드가 required=True가 되도록 합니다. 경우에 따라 필드 중 하나에 required=False를 명시적으로 적용할 수 있습니다. 이 경우 원하는 validation 동작이 모호할 수 있습니다.

이 경우 일반적으로 serialzier 클래스에서 validator를 제외하고 대신 .validate()메서드 또는 뷰에서 명시적으로 validation logic을 작성해야 합니다.

예시는 다음과 같습니다.

class BillingRecordSerializer(serializers.ModelSerializer):
    def validate(self, attrs):
        # Apply custom validation either here, or in the view.

    class Meta:
        fields = ['client', 'date', 'amount']
        extra_kwargs = {'client': {'required': False}}
        validators = []  # Remove a default "unique together" constraint.

Updating nested serializers

기존 인스턴스에 업데이트를 적용할 때 unique validator는 현재 인스턴스를 unique 검사에서 제외합니다. 현재 인스턴스는 serialzier를 인스턴스화할 때 instance = ...를 사용하여 처음에 전달된 serialzier의 속성으로 존재하기 때문에 unique 검사 컨텍스트에서 사용할 수 있습니다.

중첩된(nested) serialzier에 대한 update 작업의 경우 인스턴스를 사용할 수 없기 때문에 이 exclusion을 적용할 방법이 없습니다.

다시 말하자면, serialzier 클래스에서 validator를 명시적으로 제거하고, .validate() 메서드 또는 view에서 명시적으로 validation 제약 조건에 대한 코드를 작성하고 싶을 것입니다.

Debugging complex cases

ModelSerialzier 클래스가 생성할 동작이 정확히 무엇인지 확실하지 않은 경우 일반적으로 manage.py shell을 실행하고 serialzier의 인스턴스를 print하는 것이 좋습니다. 그러면 자동으로 생성되는 필드와 validator를 검사할 수 있습니다.

>>> serializer = MyComplexModelSerializer()
>>> print(serializer)
class MyComplexModelSerializer:
    my_fields = ...

또한 복잡한 경우에는 기본 ModelSerialzier 동작에 의존하는 것보다 serializer 클래스를 명시적으로 정의하는 것이 종종 더 나을 수 있습니다. 여기에는 코드가 조금 더 포함될 수 있어도 동작이 좀 더 투명해질 수 있습니다.

Writing custom validators

Django의 기존 validator를 사용하거나 사용자 정의 validator를 작성할 수 있습니다.

Function based

validator는 실패 시 serialziers.ValidationError를 발생시키는 raise를 모두 호출합니다.

def even_number(value):
    if value % 2 != 0:
        raise serializers.ValidationError('This field must be an even number.')

Field-level validation

serialzier 하위 클래스에 .validate_<field_name> 메서드를 추가하여 사용자 지정 field-level validation을 지정할 수 있습니다. 이것은 serializer 문서에 설명되어 있습니다.

Class-based

클래스 기반 validator를 작성하려면 call 메서드를 사용하면 됩니다. 클래스 기반 validator는 동작을 매개변수화하고 재사용할 수 있으므로 유용합니다.

class MultipleOf:
    def __init__(self, base):
        self.base = base

    def __call__(self, value):
        if value % self.base != 0:
            message = 'This field must be a multiple of %d.' % self.base
            raise serializers.ValidationError(message)

Accessing the context

일부 advanced 경우에는 validator가 추가 context로 사용되는 serializer 필드를 전달하기를 원할 수 있습니다. validator에서 requires_context = True 속성을 설정하여 그렇게 할 수 있습니다. 그런 다음 __call__메서드는 serialzier_field 또는 serialzier를 추가 인수로 사용하여 호출됩니다.

requires_context = True

def __call__(self, value, serializer_field):
    ...

 

공식 문서 원본은 아래 주소를 참고하면 되겠습니다.

https://www.django-rest-framework.org/api-guide/validators/#validators

 

Validators - Django REST framework

 

www.django-rest-framework.org

 

'Django > Django REST framework' 카테고리의 다른 글

14-Authentication  (0) 2021.09.26
13-Testing  (0) 2021.08.24
11. Serializer relations  (0) 2021.07.18
10. Serializer fields  (0) 2021.07.16
9-3 Serializer-3  (0) 2021.07.15

댓글