[Django] Serializer와 validate (TIP!)
POST method로 새로운 글을 만들 때는 제목이 중복되면 안된다.
중복되면 안되는 필드가 있다고 생각하면 된다.
하지만 해당 글을 수정하고 저장할 때의 제목 중복여부는 수정 이전의 제목과 같을때만 허용해야 할 것이다.
serailizer에서 중복을 허용하지 않기 위해서 다음과 같은 간단한 유효성 검사 메서드를 만들 수 있다.
def validate(self, attrs):
if Category.objects.filter(title=attrs['title']).exists():
raise ValidationError(_("Duplicated title."))
return super().validate(attrs)
여기서
validate는 serializer.is_valid()가 호출이 되면 실행이되는 메서드이다.
따라서 validate메서드를 위 코드 처럼 커스터마이징 해주면 우리가 원하는대로 유효성 검사를 할 수 있다.
# rest_framework/serializers.py
# class Serializer의 메서드
def validate(self, attrs):
return attrs
사실 애초에 serializer에는 validate가 세부적으로 구현되어 있지 않다.
그래서 사용자가 입맛대로 직접 구현하면 된다.
중요한 점은 위 validate함수는 Viewset에서 create, update 메서드를 실행할 때마다 항상 실행된다.
아래 코드의 create, update 메서드를 보면 알 수 있다.
# rest_framework/mixins.py
class CreateModelMixin:
"""
Create a model instance.
"""
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()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
class UpdateModelMixin:
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
UpdateModelMixin, CreateModelMixin 클래스를 보면 create와 update메서드에 serializer.is_valid() 메서드를 실행시키는게 보일 것이다.
serializer.is_valid() 메서드에서 run_validation()메서드를 호출하고
run_validation() 메서드에서 최종적으로 validate() 메서드를 호출한다.
코드는 rest_framework/serializers.py에 있다.
문제점은 create와 update를 할 때 모두 같은 validate() 메서드를 호출한다는 것이다.
그러면 어떻게 해결해야 할까?
perform_create와 perform_update를 할 때마다 유효성 검사를 따로 넣어주어야 할 지,
아니면 validate만 다른 Serializer를 따로 만들어야 할 지 고민이 될 수 있다.
하지만 정말 간단하게 해결할 수 있다.
serializer에서 save()를 호출했을 때 객체 인스턴스가 없다면 create를 실행하고, 객체 인스턴스가 있다면 update를 실행한다.
Django의 객체지향적인 구조를 활용해서 현재 Serializer 객체에 저장된 instance가 있는지 없는지 확인할 수 있다.
# rest_framework/serializers.py
class BaseSerializer(Field):
...
def save(self, **kwargs):
...
validated_data = {**self.validated_data, **kwargs}
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
return self.instance
Serializer의 save() 메서드 코드이다.
if self.instance is not None: 부분을 보면 객체가 있는 경우에 update메서드를 실행하는 것을 볼 수 있다.
이제 문제가 있던 validate 메서드를 어떻게 수정하면 깔끔할지 느낌이 올 것이다.
def validate(self, attrs):
if self.instance is not None:
return super().validate(attrs)
if Category.objects.filter(title=attrs['title']).exists():
raise ValidationError(_("Duplicated title."))
return super().validate(attrs)
메서드의 중복을 피하고, serializer를 중복해 만들지 않고 깔끔하게 해결할 수 있게 되었다.