[ PROJECT ] 카카오프렌즈샵 클론코딩 - #06 게시물 관련 세부 기능 구현하기! (댓글 작성, 좋아요)

Hailee·2020년 12월 27일
1

[ PROJECT ]

목록 보기
10/16
post-thumbnail
댓글 조회 (query string) 	--> http -v localhost:8000/board/comment board_id==3 sort==created_at page==1
			--> http -v localhost:8000/board/main 'Authorization:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtZW1iZXJfaWQiOjE2fQ.jyJ1cC-7P-bItiq5dixHGzTj_aIfZSIGDYjM_Ql1xAg'
댓글 작성 		--> http -v localhost:8000/board/comment board_id=3 member_id=4 content='안녕 모두 좋은 하루입니다'
대댓글 작성 		--> http -v localhost:8000/board/addselfcomment board_id=3 member_id=4 comment_id=17 content='wow wow wow'

게시물 좋아요 		--> http -v POST localhost:8000/board/like board_id=3 member_id=9
게시물 좋아요 취소 		--> http -v POST localhost:8000/board/unlike board_id=3 member_id=9

댓글 좋아요 		--> http -v POST localhost:8000/board/likecomment comment_id=7 member_id=10
댓글 좋아요 취소 		--> http -v POST localhost:8000/board/unlikecomment comment_id=7 member_id=10

페이징할 때 참고한 블로그! 👉🏻 django로 pagination 구현하기

1. 댓글 조회, 작성은 같은 클래스 내에 작성하기!

원래는 각 클래스 하위에 존재했던 메서드들(GetCommentView, AddCommentView). 생각해보니 각각 get, post메서드이기 때문에
굳이 다른 클래스 내에 존재하면서 자원 낭비를 할 필요가 없다!

class CommentView(View):
    ## 1. 댓글 목록 호출(GET)
    def get(self, request):
        try:
            board_id    = request.GET.get('board_id', None)
            sort        = request.GET.get('sort', None)
            page        = int(request.GET.get("page", 1) or 1)
            page_size = 2
            limit = int(page_size * page)
            offset = int(limit - page_size) 

            if Comment.objects.filter(board_id = board_id).exists(): 
                data = Comment.objects.filter(board_id=board_id).order_by(sort).values()[offset:limit]
                comment_data = [{
                    "id"        : comment['id'],
                    "writer"    : Member.objects.get(id=comment['writer_id']).nickname,
                    "content"   : comment['content'],
                    "is_liked"  : CommentLike.objects.filter(comment_id = comment['id'], is_like = 1).count(), 
                    "created_at": comment['created_at']
                } for comment in data]
            else:
                comment_data = ''
            return JsonResponse({'message' : 'SUCCESS', 'comment_data' : comment_data, 'page' : page}, status = 200)
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
        except ValueError:
            return JsonResponse({'message' : 'DECODE_ERROR'}, status = 400)
        except Comment.DoesNotExist:
            return JsonResponse({'message' : 'NO_EXIST_DATA'}, status = 500)
        except Comment.MultipleObjectsReturned:
            return JsonResponse({'message' : 'TOO_MANY_DATA'}, status = 500)   

    ## 2. 댓글 작성(POST)
    #@login_decorator
    def post(self, request):
        try:
            data        = json.loads(request.body)
            board_id    = data['board_id']
            member_id   = data['member_id']
            content     = data['content']
            if board_id and member_id:
                if content == "":
                    return JsonResponse({'message' : 'COMMENT_REQUIRED'}, status=400)
                else:
                    Comment.objects.create(
                        content         = content,
                        created_at      = datetime.datetime.now(),
                        board_id        = board_id,
                        writer_id       = member_id
                    )
            return JsonResponse({'message' : 'SUCCESS'}, status = 201)
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
        except ValueError:
            return JsonResponse({'message' : 'DECODE_ERROR'}, status = 400)    

이렇게 CommentView라는 한 클래스 내에 존재하도록 수정!


2. 좋아요 기능 클래스는 더 간략하게!

# 4. 게시물 좋아요
class LikeBoardView(View):
    #@login_decorator
    def post(self, request):
        try:
            data        = json.loads(request.body)
            board_id    = data['board_id']
            member_id   = data['member_id']

            if board_id and member_id:
                if BoardLike.objects.filter(board_id=board_id, member_id=member_id).exists():
                    return JsonResponse({'message' : 'DUPLICATED_LIKE'}, status=400)
                else:
                    BoardLike.objects.create(
                        is_like     = 1,
                        board_id    = board_id,
                        member_id   = member_id
                    )
            return JsonResponse({'message' : 'SUCCESS'}, status = 201)
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
        except ValueError:
            return JsonResponse({'message' : 'DECODE_ERROR'}, status = 400)
        except Board.DoesNotExist:
            return JsonResponse({'message' : 'NO_EXIST_DATA'}, status = 500)
        except Board.MultipleObjectsReturned:
            return JsonResponse({'message' : 'TOO_MANY_DATA'}, status = 500)   


# 5. 게시물 좋아요 취소
class UnLikeBoardView(View):
    #@login_decorator
    def post(self, request):
        try:
            data        = json.loads(request.body)
            board_id    = data['board_id']
            member_id   = data['member_id']

            if board_id and member_id:
                if BoardLike.objects.filter(board_id=board_id, member_id=member_id).exists():
                    BoardLike.objects.filter(board_id=board_id, member_id=member_id).delete()
                else:
                    return JsonResponse({'message' : 'NO_LIKE_EXISTS'}, status=400)
                    
            return JsonResponse({'message' : 'SUCCESS'}, status = 201)
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
        except ValueError:
            return JsonResponse({'message' : 'DECODE_ERROR'}, status = 400)
        except Board.DoesNotExist:
            return JsonResponse({'message' : 'NO_EXIST_DATA'}, status = 500)
        except Board.MultipleObjectsReturned:
            return JsonResponse({'message' : 'TOO_MANY_DATA'}, status = 500) 

현재 내가 작성한 로직은 좋아요, 좋아요 취소에 따라 각 클래스가 존재한다.
하지만 애초에 우리가 모델링을 할 때에 좋아요 여부를 tinyint로 해둔 것은 create, delete를 하려는 것이 아닌 create, update를 하며 데이터 관리를 하려고 했기 때문!

좋아요를 취소 할 때마다 데이터를 확인한 뒤 삭제하는건 애초에 우리가 설계했던 방향과 다르다.
(그리고 update보다 delete가 더 실행되는데 시간도 걸리고? 그렇다고 알고있다.
많은 포털들이 삭제버튼을 누른다고 해서 정말 delete를 하지는 않고, 비활성화 된 상태로 바꾼다고 들었따. - by 옛사수
그래서 삭제한다고 기록이 없어지는건 아니라는 말이 나온거지.. ㅎㅅㅎ)


그리고 내가 작성한 코드에는 말이 안되는 부분이 있다.

def post(self, request):
        try:
            data        = json.loads(request.body)
            board_id    = data['board_id']
            member_id   = data['member_id']

            if board_id and member_id:
            .
            .
            return JsonResponse({'message' : 'SUCCESS'}, status = 201)
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)

지금처럼 request.body에서 원하는 값을 가져오고 싶을 때, data라는 dictionary에서 가져오도록 코드를 작성하면
원하는 값이 없을 때 알아서 KeyError부분으로 예외처리가 된다.

if board_id and member_id라고 작성한 부분은
data.get('member_id')이런식으로 작성해둬야만 의미가 있다.
만약 get해올 값이 없다면 각 변수의 값은 None일 것이고, 그럼 저 if구문이 의미를 갖게된다.

하지만 지금 상황에서는? 두 방법의 짬뽕일 뿐! (아무것도 모르고 그냥 작성한 코드인거지)

아무튼! 그래서 수정하자면

class LikeBoardView(View):
    #@login_decorator
    def post(self, request):
        try:
            data        = json.loads(request.body)
            board_id    = data['board_id']
            member_id   = data['member_id']

            if BoardLike.objects.filter(board_id=board_id, member_id=member_id).exists():
                if BoardLike.objects.filter(board_id=board_id, member_id=member_id)[0].is_like == 1:
                    BoardLike.objects.filter(board_id=board_id, member_id=member_id).update(is_like=0)
                    return JsonResponse({'message' : 'SUCCESS', 'member_id' : member_id, 'like' : False}, status = 200)
                else:
                    BoardLike.objects.filter(board_id=board_id, member_id=member_id).update(is_like=1)
                    return JsonResponse({'message' : 'SUCCESS', 'member_id' : member_id, 'like' : True}, status = 200)
            else:
                BoardLike.objects.create(
                    is_like     = 1,
                    board_id    = board_id,
                    member_id   = member_id
                )
                return JsonResponse({'message' : 'SUCCESS', 'member_id' : member_id, 'like' : True}, status = 201)
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
        except ValueError:
            return JsonResponse({'message' : 'DECODE_ERROR'}, status = 400)
        except Board.DoesNotExist:
            return JsonResponse({'message' : 'NO_EXIST_DATA'}, status = 500)
        except Board.MultipleObjectsReturned:
            return JsonResponse({'message' : 'TOO_MANY_DATA'}, status = 500)   

3. 로그인 decorator 적용하기

인증, 인가에 대해서 생각해보는 시간!
user의 권한을 확인할 수 있도록 토큰을 부여하고(인증), 이 토큰을 활용해서 권한별 기능을 사용할 수 있도록 해주는(인가) 로그인 데코레이터가 이미 구현되어있었다.

하지만 내 경우는 약간 달랐던게, 비회원도 게시물, 댓글 조회는 가능하다.
하지만 작성 및 좋아요에 있어서는 권한이 있어야만 하는 것!

def login_check(func):
    def wrapper(self, request, *args, **kwargs):
        access_token = request.headers.get('Authorization', None)
        try:
            if access_token == None:
                request.user = None
            else:
                payload = jwt.decode(access_token, SECRET_KEY, algorithm=ALGORITHM)
                user = Member.objects.get(id=payload['member_id'])
                request.user = user
            
        except Member.DoesNotExist:
            return JsonResponse({"message" : "INVALID_USER"}, status=400)
        except jwt.exceptions.DecodeError:
            return JsonResponse({"message" : "INVALID_TOKEN"}, status=400)
        return func(self, request, *args, **kwargs)
        
    return wrapper

그래서 기존의 로그인 데코레이터와 약간 다른 모양의 데코레이터를 새로 생성했다.

# 2-1. 게시물 상세조회 - 게시물 호출        
class GetBoardView(View):
    @login_check
    def get(self, request, board_id):
        try:
            if request.user != None:
                member_id  = request.user.id

            data = Board.objects.filter(id = board_id)[0]
            
            # 리턴보낼 board_data 준비
            board_data = {
                "id"            : data.id,
                "uploader"      : data.uploader,
                "theme"         : data.theme,
                "title"         : data.title,
                "content"       : data.content,
                "created_at"    : data.created_at,
                "thumb_image"   : BoardImage.objects.filter(board_id = board_id).values_list('image_url', flat=True)[0],
                "board_images"  : list(BoardImage.objects.filter(board_id = board_id).values_list('image_url', flat=True)[1:]),
                "board_likes"   : BoardLike.objects.filter(board_id = board_id).count(),
                "if_i_liked"    : check_boardLike(self, request, board_id)
            }
            return JsonResponse({'message' : 'SUCCESS', 'board_list' : board_data}, status = 200)
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
        except ValueError:
            return JsonResponse({'message' : 'DECODE_ERROR'}, status = 400)
        except Board.DoesNotExist:
            return JsonResponse({'message' : 'NO_EXIST_DATA'}, status = 500)  

초기에는 이렇게 각 권한에 대해서 if문으로 member_id가 존재하는지 여부를 체크했지만
이미 데코레이터를 거쳐서 저 과정이 이루어지기 때문에 굳이 저렇게 하지 않기로 했다.

class GetBoardView(View):
    @login_check
    def get(self, request, board_id):
        try:
            data = Board.objects.get(id = board_id)
            
            # 리턴보낼 board_data 준비
            board_data = {
                "id"            : data.id,
                "uploader"      : data.uploader,
                "theme"         : data.theme,
                "title"         : data.title,
                "content"       : data.content,
                "created_at"    : data.created_at,
                "thumb_image"   : BoardImage.objects.filter(board_id = board_id).values_list('image_url', flat=True)[0],
                "board_images"  : list(BoardImage.objects.filter(board_id = board_id).values_list('image_url', flat=True)[1:]),
                "board_likes"   : BoardLike.objects.filter(board_id = board_id).count(),
                "member_liked"    : check_boardLike(self, request, board_id)
            }
            return JsonResponse({'message' : 'SUCCESS', 'board_list' : board_data}, status = 200)
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)
        except ValueError:
            return JsonResponse({'message' : 'DECODE_ERROR'}, status = 400)
        except Board.DoesNotExist:
            return JsonResponse({'message' : 'NO_EXIST_DATA'}, status = 404)    

너무 예쁜 내 최종 코드!

if문으로 다시한번 로그인 여부를 확인하는 대신, member_liked 관련 부분을 메서드화해서 True, False로 받아주기로 했다.

로그인 여부와는 별개로, 각 사용자가 어떠한 게시물의 좋아요를 눌렀을 경우 특정 게시물에만 좋아요 표기를 해주어야 했기 때문!
이렇게 되면 frontend에서는 true, false 여부만 받아서 만약 true일 경우 좋아요 표시를 한 채로 보여주면 된다.

# 로그인 여부에 따른 게시물 좋아요 체크
def check_boardLike(self, request, board_id):
    result = False
    if request.user:
        result = BoardLike.objects.filter(board_id= board_id, member_id = request.user.id).exists()
    return result

# 로그인 여부에 따른 댓글 좋아요 체크
def check_commentLike(self, request, comment_id):
    result = False
    if request.user:
        result = CommentLike.objects.filter(comment_id= comment_id, member_id = request.user.id).exists()
    return result

4. 좋아요 기능

이건 위의 경우와 비슷한듯 다르다.
인가에 대한 확실한 예제인듯!
좋아요 기능을 실행하기 위해서는 확실히 유저여아만 하기 때문에,
request에 user의 정보가 담겨있지 않다면, 바로 400 코드를 프론트로 리턴한다.

class LikeBoardView(View):
    @login_check
    def post(self, request):
        try:
            if request.user == None:
                return JsonResponse({"message" : "INVALID_USER"}, status=400) 
            
            data        = json.loads(request.body)
            board_id    = data['board_id']

            if BoardLike.objects.filter(board_id=board_id, member_id=request.user.id).exists():
                if BoardLike.objects.get(board_id=board_id, member_id=request.user.id).is_like == 1:
                    BoardLike.objects.filter(board_id=board_id, member_id=request.user.id).update(is_like=0)
                    return JsonResponse(
                        {
                            'message' : 'SUCCESS', 
                            'member_id' : request.user.id, 
                            'like' : False
                        }, status = 200)
                
                BoardLike.objects.filter(board_id=board_id, member_id=request.user.id).update(is_like=1)
                return JsonResponse(
                    {
                        'message' : 'SUCCESS', 
                        'member_id' : request.user.id, 
                        'like' : True
                    }, status = 200)
            
            BoardLike.objects.create(
                is_like     = 1,
                board_id    = board_id,
                member_id   = request.user.id
            )
            return JsonResponse(
                {
                    'message' : 'SUCCESS', 
                    'member_id' : request.user.id, 
                    'like' : True
                }, status = 201)
        except KeyError:
            return JsonResponse({'message' : 'KEY_ERROR'}, status = 400)

번외) 데이터베이스 내 특정값 찾아서 update 하기

프론트에서 <br> 태그를 줄내림으로 변환하는 작업을 할 줄 알고 미리 데이터를 넣어두었는데
그럴 시간적 여유가 없어서 그냥 해당 단어들을 삭제하기로 했다.

update boards set content=replace(content, '<br>', '') where content like '%<br>%';

입력된 데이터들 중, 나처럼 특정 단어(<br>)만 업데이트 하고싶다면?
특정 단어를 바꾸어주는 update 쿼리를 쓰면 된다.


이 모든 코드를 하루만에 작성한 나, 칭찬해 ><
(물론 멘토님께 첨삭받고 수정하느라 최종까지는 시간이 좀 걸렸지만.. 그래두.. ㅎㅅㅎ)

profile
웹 개발 🐷😎👊🏻🔥

0개의 댓글