개발기록장

[Django] 인스타그램 클론 코딩(7) - 대댓글 기능 구현 본문

TIL/Django

[Django] 인스타그램 클론 코딩(7) - 대댓글 기능 구현

yangahh 2021. 2. 10. 16:28

 

[이전 글]

[Django] 인스타그램 클론 코딩(6) - 팔로우(follow) 기능 구현

 

[Django] 인스타그램 클론 코딩(6) - 팔로우(follow) 기능 구현

[이전 글] [Django] 인스타그램 클론 코딩(5) - 좋아요(Like) 기능 구현 [Django] 인스타그램 클론 코딩(5) - 좋아요(Like) 기능 구현 [이전 글] [Django] 인스타그램 클론 코딩(4) - 게시물과 댓글 C.R.U.D (+ RE..

devvvyang.tistory.com

 

인스타그램에서 대댓글은 상위 댓글과 1:M 관계이다.

나는 대댓글 모델(테이블)을 따로 두지 않고 기존 Comment 모델에 parent라는 attribute를 추가하여 ForeignKey로 자기 자신을 참조하게 하였다.

 

1. post/models.py

from django.db   import models

from user.models import User

class Comment(models.Model):
    user       = models.ForeignKey('user.User', on_delete=models.CASCADE)
    post       = models.ForeignKey('Post', on_delete=models.CASCADE)
    parent     = models.ForeignKey('self', on_delete=models.CASCADE, null=True)
    content    = models.CharField(max_length=500)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'comments'
  • 기존 Comment모델에서 parent라는 attribute를 추가하였다. 
  • 여기에 ForeignKey로 참조하는 테이블을 'self'로 지정하여 1:M 셀프 참조를 구현하였다.
  • 대댓글일 경우에 parent 값으로 상위 댓글의 id(PK)가 들어오게 된다.
  • 가장 최상위 댓글은 parent에 Null이 되므로 null=True 옵션을 설정해주었다.

 

 

2. post/views.py (댓글 및 대댓글 CRUD)

import json
from json.decoder import JSONDecodeError

from django.views import View
from django.http  import JsonResponse

from .models      import Post, Comment, Like
from user.models  import User
from user.utils   import login_decorator


class CommentView(View):
    @login_decorator
    def post(self, request):
        data      = json.loads(request.body)
        user      = request.user
        post_id   = data.get('post', None)
        parent_id = data.get('parent', None)
        content   = data.get('content', None)
        
        # KEY_ERROR check
        if not (post_id and content):
            return JsonResponse({'message': 'KEY_ERROR'}, status=400)
        
        # valid post check
        if not Post.objects.filter(id=post_id).exists():
            return JsonResponse({'message': 'INVALID_POST'}, status=400)

        # valid comment check
        if parent_id and not Comment.objects.filter(id=parent_id).exists():
            return JsonResponse({'message': 'INVALID_COMMENT'}, status=400)

        Comment.objects.create(
            user      = user,
            post      = Post.objects.get(id=post_id),
            parent_id = parent_id,
            content   = content
        )
        return JsonResponse({'message': 'SUCCESS'}, status=200)


class CommentDetailView(View):
    # update
    @login_decorator
    def post(self, request, comment_id):
        try:
            data    = json.loads(request.body)
            content = data.get('content', None)

            # KEY_ERROR check
            if not content:
                return JsonResponse({'message': 'KEY_ERROR'}, status=400)

            # vaild comment check
            if not Comment.objects.filter(id=comment_id).exists():
                return JsonResponse({'message': 'INVALID_COMMENT'}, status=400)
            
            comment = Comment.objects.get(id=comment_id)
            
            # valid user check
            if comment.user != request.user:
                return JsonResponse({'message': 'INVALID_USER'}, status=401)

            comment.content = content 
            comment.save()
            return JsonResponse({'message': 'SUCCESS'}, status=200)

        except JSONDecodeError:
            return JsonResponse({'message': 'REQUEST_BOBY_DOES_NOT_EXISTS'}, status=400)

    @login_decorator
    def delete(self, request, comment_id):
        # vaild comment check
        if not Comment.objects.filter(id=comment_id).exists():
            return JsonResponse({'message': 'INVALID_COMMENT'}, status=400)
        
        comment = Comment.objects.get(id=comment_id)
        
        # valid user check
        if comment.user != request.user:
            return JsonResponse({'message': 'INVALID_USER'}, status=401)

        comment.delete()
        return JsonResponse({'message': 'SUCCESS'}, status=200)


class PostDetailView(View):
    def get(self, request, post_id):
        # valid post check
        if not Post.objects.filter(id=post_id).exists():
            return JsonResponse({'message': 'INVALID_POST'}, status=404)
        
        context = {}
        # post 정보
        post = Post.objects.get(id=post_id)

        context['user']       = post.user.name
        context['image_url']  = post.image_url
        context['content']    = post.content
        context['created_at'] = post.created_at

        # comment 정보
        comments = Comment.objects.filter(post=post)
        if comments:
            comment_list = []
            for comment in comments:
            	# 대댓글이 있을 경우와 없을 경우를 나눠서 처리
                if comment.parent:
                    parent_id = comment.parent.id
                else:
                    parent_id = None

                comment_list.append({
                    'user'      : comment.user.name,
                    'parent_id' : parent_id,
                    'content'   : comment.content,
                    'created_at': comment.created_at
                })
            context['comment_list'] = comment_list

        return JsonResponse({'data': context}, status=200)

** 기존 Comment 관련 View 설명 보러가기

  •  CommentView

    • post 메소드 : 기존에 댓글 작성과 동일. 대댓글일 경우, request body에서 parent 댓글의 id를 받아온다고 가정해서 코드를 작성하였다. 대댓글이 아닐 경우에는 parent_id에 None 값이 들어감으로써 그냥 댓글과 대댓글을 구분할 수 있게 하였다.

  • CommentDetailView

    • post 메소드 : 기존 댓글 수정과 동일

    • delete 메소드 : 기존 댓글 삭제와 동일

  • PostDetailView

    • get 메소드 : 특정 게시물과 그 게시물에 달린 댓글, 대댓글 데이터까지 보여주는 view이다. 댓글이 있을 경우 또는 대댓글이 있을 경우에만 response로 넘겨준다.