Henu
개발냥발
Henu
전체 방문자
오늘
어제
  • 분류 전체보기 (411)
    • DevOps (52)
      • Kubernetes (19)
      • Docker (14)
      • AWS (3)
      • Nginx (4)
      • Linux (4)
      • ArgoCD (1)
      • CN (2)
      • NATS (0)
      • Git (5)
    • Back-End (30)
      • Django (18)
      • Spring (5)
      • JPA (1)
      • MSA (5)
    • CS (87)
      • SystemSoftware (20)
      • OS (25)
      • Computer Architecture (16)
      • Network (23)
      • Database (2)
    • Lang (21)
      • Java (9)
      • Python (4)
      • C# (8)
    • Life (12)
    • 블록체인 (2)
    • Algorithm (204)
      • BOJ (160)
      • 프로그래머스 (19)
      • LeetCode (4)
      • SWEA (1)
      • 알고리즘 문제 해결 전략 (8)
      • DS, algorithms (7)
      • Checkio (5)
    • IT (2)

블로그 메뉴

  • GitHub
  • 글쓰기
  • 관리자

공지사항

  • Free!

인기 글

태그

  • 프로그래머스
  • Network
  • docker
  • 다이나믹 프로그래밍
  • 백트래킹
  • django
  • boj
  • DFS
  • Kubernetes
  • BFS

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Henu

개발냥발

Back-End/Django

[Django] Serializer와 validate (TIP!)

2022. 1. 15. 17:00

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
    'Back-End/Django' 카테고리의 다른 글
    • [Django] ModelViewSet에서 Retrieve의 동작 원리
    • [Django] DRF 구조 이해 #1 (APIView)
    • [Django] django 기본 인증 시스템 커스텀(아이디, 이메일 로그인)
    • [Django] DRF를 사용한 JWT Authentication #2

    티스토리툴바