[DRF] 좋아요 기능1 - PATCH 메서드 사용

Jinhyung Rhee·2022년 8월 12일
0

LIKE 기능 구현하기

  • 템플릿 장고 URL : /api/like/99

    • GET 메서드로 구현되었음
  • DRF 장고 URL : /api2/post/99/like/

    • Post 테이블 내에 like 컬럼이 존재하여, 특정 레코드에 대한 like 컬럼의 값을 1 증가시키는 기능!
    • 여러 컬럼에 접근하는 것이 아니라 like 컬럼 하나만 신경쓰기 때문에 PUT보다는 PATCH가 적합함!

    코드

  • urls.py

    from django.urls import path
    from . import views
    
    ## url 패턴이 list나 detail이 아니면 name을 직관적으로 작성
    urlpatterns = [
     path('post/', views.PostListAPIView.as_view(), name='post-list'),
     path('post/<int:pk>/', views.PostRetrieveAPIView.as_view(), name='post-detail'),
     path('comment/', views.CommentCreateAPIView.as_view(), name='comment-create'),
     path('post/<int:pk>/like/', views.PostLikeAPIView.as_view(), name='post_like'),
    ]
  • views.py

    from rest_framework.generics import ListAPIView, RetrieveAPIView, CreateAPIView, UpdateAPIView
    from blog.models import Post, Comment
    from .serializers import CommentSerializer, PostListSerializer, PostRetrieveSerializer
    
    ...
    
    class PostLikeAPIView(UpdateAPIView):
      queryset = Post.objects.all()
      serializer_class = PostListSerializer
    • 우선 5개의 필드만을 가져오는 PostListSerializer를 사용함
  • Browsable API에서 PATCH 테스트

    • 빈 데이터로 PUT 요청을 보내면 에러 발생
    • 빈 데이터로 PATCH 요청 보내면 정상 처리
      • but, 아직 좋아요 수를 증가하는 로직을 구현하지 않았기 때문에 개수는 그대로임
      • TODO : PATCH 메서드 요청 시 like 값 1 증가시키는 로직 구현

사용하는 Serializer의 모델에서 필수적인 필드가 무엇인지 체크

  • Post 모델 체크

    class Post(models.Model):
        category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=True, null=True)
        tags = models.ManyToManyField('Tag', blank=True)
        title = models.CharField('TITLE', max_length=50)
        description = models.CharField('DESCRIPTION', max_length=100, blank=True, help_text='simple one-line text.')
        image = models.ImageField('IMAGE', upload_to='blog/%Y/%m/', blank=True, null=True)
        content = models.TextField('CONTENT')
        create_dt = models.DateTimeField('CREATE DT', auto_now_add=True)
        update_dt = models.DateTimeField('UPDATE DT', auto_now=True)
        like = models.PositiveSmallIntegerField('LIKE', default=0)
    • titlecontent만 필수임!
  • django shell에서 serializer 확인

    $ python manage.py shell
    
    >>> from api2.serializers import *
    >>> 
    >>> PostListSerializer()
    PostListSerializer():
        id = IntegerField(label='ID', read_only=True)
        title = CharField(label='TITLE', max_length=50)
        image = ImageField(allow_null=True, label='IMAGE', max_length=100, required=False)
        like = IntegerField(label='LIKE', required=False)
        category = PrimaryKeyRelatedField(allow_null=True, queryset=Category.objects.all(), required=False)
    >>>
    • serializer로 보여지는 값 중에서는 title만 필수값임!

TODO : like + 1 기능 구현

  • UpdateAPIView 로직 확인(cdrf) : patch -> partial_update -> partial인자를 True로 변경 -> update

    • 이 로직을 잘 따라가면서 적절히 메서드 오버라이딩 필요!
    • 로직
      • partial = kwargs.pop('partial', True) : (1)부분 수정 True로 설정
      • instance = self.get_object() : (2)테이블로부터 인스턴스 가져옴
      • serializer = self.get_serializer(instance, data=request.data, partial=partial) : (3)serializer 준비
      • serializer.is_valid(raise exception=True) : (4)serializer에서 유효성 체크
      • self.perform_update(serializer) : (5)DB에 update하여 저장함
      • if getattr(~): : 'prefetch_related'라는 쿼리문을 사용했을 때 동작하는 코드 -> 현재는 무시!
      • return Response(serializer.data) : (6)최종적으로 직렬화된 데이터를 client에게 응답
    • 내부에서 오버라이딩할만한 적당한 메서드를 찾기 어려울 때는 해당 메서드 자체(전체)를 가져와서 오버라이딩함!
  • update 메서드 오버라이딩(override)

    class PostLikeAPIView(UpdateAPIView):
      queryset = Post.objects.all()
      serializer_class = PostListSerializer
    
      # update 메서드 오버라이딩
      def update(self, request, *args, **kwargs):
            partial = kwargs.pop('partial', False)
            instance = self.get_object()
            # 내부에 like + 1 로직 추가 (이후 serializer의 data변수에 dict 형태로 넣어줌)
            data = {'like' : instance.like + 1}
            serializer = self.get_serializer(instance, data=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)
  • 결과

  • 문제점

    • 기존 응답 값 : 단순 숫자(like 값) 1개
    • 현재 응답 값 : 필드 5개(serializer에 지정한 필드)
  • TODO : 기존 응답처럼 like 값 숫자 하나만 응답으로 내려주도록 변경

Like 필드 하나만 내려주는 PostLikeSerializer를 정의하여 사용

  • views.py

    class PostLikeAPIView(UpdateAPIView):
      queryset = Post.objects.all()
      serializer_class = PostLikeSerializer
    
      # update 메서드 오버라이딩
      def update(self, request, *args, **kwargs):
            partial = kwargs.pop('partial', False)
            instance = self.get_object()
            # 내부에 like + 1 로직 추가 (이후 serializer의 data변수에 dict 형태로 넣어줌)
            data = {'like' : instance.like + 1}
            serializer = self.get_serializer(instance, data=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)
  • serializers.py

    class PostLikeSerializer(serializers.ModelSerializer):
        class Meta:
            model = Post
            fields = ['like']
  • Browsable API로 테스트

    • 의도했던 대로 달랑 숫자 하나만 오는 것이 아니라 key:value 형태로 리턴됨 -> 추가 수정 필요

Response로 숫자 하나만 보내고 싶은 경우

방법1: 단순히 data 값을 dict에서 int로 변경하기

  • views.py

    class PostLikeAPIView(UpdateAPIView):
      queryset = Post.objects.all()
      serializer_class = PostLikeSerializer
    
      # update 메서드 오버라이딩
      def update(self, request, *args, **kwargs):
            partial = kwargs.pop('partial', False)
            instance = self.get_object()
            # 내부에 like + 1 로직 추가 (이후 serializer의 data변수에 dict 형태로 넣어줌)
            # data = {'like' : instance.like + 1}
            data = instance.like + 1
            serializer = self.get_serializer(instance, data=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)
    • data = instance.like + 1 : dict 자료형이 아니라 int 형으로 data를 만들어서 반환하면?
    • 결과
      • 에러 발생!
      • DRF Serializer는 dict 자료형을 기반(dict-like)으로 동작하는 클래스임
      • dict가 아닌 값을 data로 넣었기 때문에 에러가 발생했던 것!

방법2: Serializer 대신 Response값 변경하기

class PostLikeAPIView(UpdateAPIView):
  queryset = Post.objects.all()
  serializer_class = PostLikeSerializer

  # update 메서드 오버라이딩
  def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        # 내부에 like + 1 로직 추가 (이후 serializer의 data변수에 dict 형태로 넣어줌)
        data = {'like' : instance.like + 1}
        # data = instance.like + 1
        serializer = self.get_serializer(instance, data=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)
        return Response(data['like'])
  • Response 클래스에 dict 값(serializer.data)이 아니라 int 값(data['like'])을 넣어줌!
  • 결과

Reference

https://www.inflearn.com/course/%EC%9E%A5%EA%B3%A0-drf/dashboard

profile
기록하는 습관

0개의 댓글