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를 중복해 만들지 않고 깔끔하게 해결할 수 있게 되었다.
'Back-End > Django' 카테고리의 다른 글
[Django] ModelViewSet에서 Retrieve의 동작 원리 (0) | 2022.01.16 |
---|---|
[Django] DRF 구조 이해 #1 (APIView) (0) | 2022.01.11 |
[Django] django 기본 인증 시스템 커스텀(아이디, 이메일 로그인) (0) | 2022.01.03 |
[Django] DRF를 사용한 JWT Authentication #2 (0) | 2022.01.03 |
[Django] DRF를 사용한 JWT Authentication #1 (0) | 2022.01.03 |