REST 프레임워크에서 유효성 검사를 처리하는 대부분의 경우 단순히 기본 필드 유효성 검사에 의존하거나 직렬 변환기 또는 필드 클래스에 대한 명시적 유효성 검사 방법을 작성합니다.
Serializer
는 BaseSerializer
를 상속받습니다.
""" Serializer """
# rest_framework/serializers.py
class BaseSerializer(Field):
def __init__(self, instance=None, data=empty, **kwargs)
class Serializer(BaseSerializer):
pass
다음과 같은 절차를 거칩니다.
serializer.is_valid()
호출 시,serializer.initial_data
에 data 값을 넣어주고serializer.validated_data
를 통해 유효성 검증에 통과한 값들이 serializer.save()
시에 사용됩니다.serializer.errors
에는 유효성 검증 후 오류 내역들이 저장되고serializer.data
에는 유효성 검증 후 갱신된 인스턴스에 값이 사전형으로 저장됩니다.def save(self, **kwargs):
pass
serializer.save(**kwargs)
를 호출을 하면
DB에 저장한 관련 인스턴스를 리턴해주고
.validated_data
와 kwargs
사전을 합친 데이터를 DB로 저장하기 위한 시도를 합니다. 저장 방식은 self.instance가 어떤가에 따라 다릅니다.
›› self.instance 인자를 지정했을 때 : .update()
를 통해 저장
›› self.instance 인자를 지정하지 않았을 때 : .create()
를 통해 저장
rest_framework.validators
Serializer
에서 제공하는 validator
는 다음과 같습니다. 값에 대한 유효성 검사를 수행하는 django 기본 validator
와 더불어 DRF는 유일성 체크를 도와주는 validator
도 제공하고 있습니다.
""" UniqueValidator 예시 """
""" UniqueValidator 의 경우 모델 필드에 unique=True를 지정하면 동작하게 되어있습니다. """
# queryset (필수) : 대상 쿼리셋의 범위
# message : 유효성 검사 실패 시의 에러 메시지
# lookup : 디폴트 'exact'
from rest_framework.validators import UniqueValidator
slug = SlugField(
max_length = 100,
validators = [UniqueValidator(queryset=BlogPost.objects.all())]
유효성 검사 함수를 커스텀 하고 싶을 경우 위의 예시 처럼 Serializer 필드에 직접 validators
지정을 하지만, 💡 가급적이면 model 에서 유효성 검사 함수를 지정하는 것을 추천합니다.
rest_framework.exceptions.ValidationError
유효성 검사에 실패하면 ValidationError
예외를 발생시킵니다. django 기본에서는 django.forms.exceptions.ValidationError
를 썼다면, DRF 에서는 APIException
을 상속받는 rest_framework.exceptions.ValidationError
를 사용합니다.
class ValidationError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Invalid input.')
default_code = 'invalid'
def __init__(self, detail=None, code=None):
if detail is None:
detail = self.default_detail
if code is None:
code = self.default_code
if not isinstance(detail, dict) and not isinstance(detail, list):
detail = [detail]
self.detail = _get_error_details(detail, code)
그러면 Serializer
에서 유효성 검사를 하는 방법을 예시를 통해 알아봅시다. 두 가지 방법이 있습니다.
(1) Field Level 검사 : 필드 정의 시에 validator
지정
validator_필드명
을 구현해서 유효성 검사를 할 수 있습니다.
# serializers.py
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
class PostSerializer(serializers.Serializser):
title = serializers.CharField(max_length=100)
def validte_title(self, value):
if '제목' not in value:
raise ValidationError('제목에 필히 django 라는 말이 포함되어야합니다.')
return value
(2) Object Level 검사 : 클래스에 Meta.validator
지정
validate
함수를 사용해서 2개 이상의 필드에 대해 유효성 검사를 할 수 있습니다.
class PostSerializer(serializer.Serializer):
title = serializers.CharField(max_length=100)
def validate(self, data):
if '제목' not in value:
raise ValidationError('제목에 필히 django 라는 말이 포함되어야합니다.')
return data
APIView
의 create / update / destory 멤버함수의 실질적인 DB 처리 반영은 mixins
의 CreateModelMixin
인의 perform_
함수를 통해 이루어 집니다.
# rest_framework/mixins.py
class CreateModelMixin(object):
def create(self, request, *args **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
사용자로부터 form 을 통해 정보를 입력 받은 후, 그 이외의 추가적인 정보를 저장하고 싶을 때 perform_
함수를 이용하면 우리가 원하는 정보를 얻을 수 있도록 커스텀할 수 있습니다. 대표적인 예로 ip가 있습니다. ip 정보를 사용자보고 입력하라고 하는건 곤란하겠죠?
# models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
ip = models.GenericIPAddressField()
# serializers.py
from rest_framework.serializers import ModelSerializer
from .models import Post
class PostSerializer(ModelSerializer):
class Meta:
model = Post
fields = ['title']
# views.py
from rest_framework.viewsets import ModelViewSet
from .models import Post
from .serializers import PostSerializer
class PostViewSet(ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def perform_create(self, serializer):
serializer.save(ip=self.request.META['REMOTE_ADDR'])
""" Form """
# django/forms/forms.py
class BaseModelForm:
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, instance=None, use_required_attribute=None):
class Form(BaseForm):
pass
""" Serializer """
# rest_framework/serializers.py
class BaseSerializer(Field):
def __init__(self, instance=None, data=empty, **kwargs)
class Serializer(BaseSerializer):
pass
""" Form """
class ProcessFormView(View):
def get(self, request, *args, **kwargs):
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
form = self.get_form() # POST 데이터를 통해 Form 객체 생성.
if form.is_valid(): # 유효성 검사 수행. 실패하면 False 반환.
return self.form_valid(form) # DB로의 저장 수행.
else:
return self.form_invalid(form) # 오류 HTML 응답.
""" Serializer """
# rest_framework/mixins.py
class CreateModelMixin(object):
def create(self, request, *args, **kwargs): # POST요청 -> CREATE 요청이 들어오면
serializer = self.get_serializer(data=request.data) # POST 데이터를 통해 Serializer 인스턴스를 생성하고
serializer.is_valid(raise_exception=True) # 유효성 검사를 수행. 실패하면 예외발생.
self.perform_create(serializer) # DB로의 저장 수행.
headers = self.get_success_headers(serializer.data) # 필요한 헤더를 뽑고,
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) # 응답을 한다.
def perform_create(self, serializer):
serializer.save()
""" Form """
from django import forms
class PostForm(forms.Form)
title = forms.CharField()
def clean_title(self):
value = self.cleaned_data.get('title', '')
if 'django' not in value:
raise forms.ValidationError('에러')
return value
""" Serializer """
from rest_framework.exceptions import ValidationError
class PostSerializer(serializers.Serializer):
title = serializers(serializers.Serializer):
def validate_title(self, value):
if 'django' not in value:
raise forms.ValidationError('에러')
return value