파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.
Serializer를 이용해 유효성 검사를 하고 데이터베이스로 저장하는 과정 및 Serializer의 관심사가 아닌 필드는 어떤식으로 뷰에서 처리하는지 알아본다.
Serializer는 Django 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
# rest_framework/serializers.py
class BaseSerializer(Field):
def __init__(self, instance=None, data=empty, **kwargs):
# instance -> 직렬화 목적
# data -> 유효성 검사
class Serializer(BaseSerializer):
pass
.is_valid()가 호출되고 나서야
# Form에서의 save코드
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.ip = request.META['REMOTE_ADDR']
post.save()
# Serializer에서의 save코드
serializer.is_valid(...) # raise
serializer.save(author=request.user, ip=request.META['REMOTE_ADDR'])
https://github.com/encode/django-rest-framework/blob/master/rest_framework/validators.py
값에 대한 유효성 검사를 수행하는 호출 가능한 객체
DRF에서는 유일성 체크를 도와주는 Validators 제공
- UniqueValidator : 지정 1개 필드가 지정 QuerySet범위에서의 유일성 여부 체크
- UniqueTogetherValidator : UniqueValidator의 다수 필드 버전
- BaseUniqueForValidator
- UniqueForDateValidator (BaseUniqueValidator) : 지정 날짜 범위에서 유일성 여부 체크
- UniqueForMonthValidator (BaseUniqueValidator) : 지정 월 범위에서의 유일성 여부 체크
- UniqueForYearValidator (BaseUniqueValidator) : 지정 년 범위에서 유일성 여부 체크
Serializer 필드에 직접 validators 지정 가능
from rest_framework.validators import UniqueValidator
slug = SlugField(
max_length=100,
validators=[UniqueValidator(queryset=BlogPost.objects.all())]
)
⇒ 가급적이면 모델필드에 unique=True를 지정함으로써 모델 Serilizer에서 UniqueValidator가 추가되도록 하는것을 추천
모델 Meta에 unique_together이 지정된다면, 자동 지정
- queryset (필수) : 대상 쿼리셋의 범위
- field (필수)
- message
from rest_framework.validators import UniqueTogetherValidator
class ExampleSerializer(serializers.Serializer):
# ...
class Meta:
validators = [
UniqueTogetherValidator(
queryset=ToDoItem.objects.all(),
fields=['list', 'position']
)
]
특정 쿼리 셋에서 → 특정 날짜 범위 내에서 → 특정 필드가 유니크한지 검사
- queryset (필수) : 대상 쿼리셋의 범위
- field (필수)
- date_field (필수) : Date, Month, Year값이 참조 할 날짜 필드 (필드 상에서 연/월/일 중 Validator에 맞게 참조한다.)
- message
from rest_framework.validators import UniqueTogetherValidator
class ExampleSerializer(serializers.Serializer):
# ...
class Meta:
validators = [
UniqueForYearValidator(
queryset=BlogPostItem.objects.all(),
field='slug',
date_field='published'
)
]
https://github.com/encode/django-rest-framework/blob/3.10.1/rest_framework/exceptions.py#L138
필히 rest_framework.exception.ValidationError 사용
장고 기본에서는 django.forms.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)
필드 정의시에 validators 지정하거나, 클래스 Meta.validators 지정
class PostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
def validate_title(self, value): # validator_필드명을 구현해서 유효성 검사를 진행할 수 있다. (form에서는 clean_필드명)
if 'django' not in value:
raise ValidationError('제목에 필히 django가 포함되어야합니다.') # 조건에 맞지않으면 rest_framework의 validationError를 발생시킨다. (form에 있는 validationError랑 헷갈리면 안됨)
return value
class PostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
def validate(self, data): # 2개 이상의 필드에 대해 유효성 검사 로직은 validate를 구현하면 된다.(form에서는 clean)
if 'django' not in data['title']:
raise ValidationError('제목에 필히 django가 포함되어야합니다.')
return data
APIView의 create/update/destroy 멤버함수에서 실질적인 DB처리 로직은 다음과 같다. (믹스인에 정의되어있다.)
class CreateModelMixin:
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() # 모델 객체가 만들어지고, 모델 객체의 값들이 할당이 다 된 model.save()가 호출이 되는 것이다.
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
# CreateModelMixin에서 상속받은 뷰에서 아래와 같이 사용한다.
def perform_create(self, serializer): # 실제 APIView구현에서
serializer.save(ip=self.request.META['REMOTE_ADDR'])