TIL 31 | Westagram Like & Follow

임종성·2021년 7월 25일
1

Django

목록 보기
12/17
post-thumbnail

Westagram Project를 진행하며 회원가입, 로그인, 게시물과 댓글 등록 기능까지 구현해봤다. 이번엔 게시물에 대한 좋아요 기능과 사용자끼리의 팔로우 기능을 추가해봤다.

Like & Follow Model

ERD

기존 Database Model에 Like와 Follow Table을 추가하여 AQueryTool을 이용해 ERD를 구상해봤다.

  • Like Table의 경우 user와 post의 id만 field로 지정하면 User와 Post의 M2M 관계 중간테이블처럼 느껴져서 좋아요를 표현할만한 Field를 추가할까 고민했는데, 검색해보니 굳이 그럴 필요가 없다고 하여 단순하게 만들었다.
  • Follow Table은 User를 참조하는 filed 2개만 추가했다.

Model

Field 수가 적어서 Model은 금방 작성할 수 있었다. Follow Class의 follow는 팔로우 하는 사람, followed는 팔로우 받은 사람으로 정의했다.


class Like(models.Model):
    user          = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='likes')
    post          = models.ForeignKey('Post', on_delete=models.CASCADE, related_name='likes')

    class Meta:
        db_table  = 'likes'

class Follow(models.Model):
    follow   	  = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='follow')
    followed 	  = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='followed')

    class Meta:
        db_table  = 'follows'

위의 Model에서는 Follow Class에서 related_name을 사용했지만, 사용하지 않고 하나의 클래스에서 같은 객체를 참조할 경우 다음과 같은 에러가 발생했다.

만약 Follow Class에 User를 참조하는 ForeignKey가 follow 하나였다면, 특정 Follow를 이행한 User의 QuerySet은 Follow.follow, 특정 User가 Follow한 Follow QuerySet은 User.follow_set으로 표현했을 것이다.

그러나 Follow처럼 한 클래스에서 follow, followed 2개의 Field가 같은 User 객체를 참조하므로, 역참조인 User.follow_set으로 QuerySet을 불렀을때 유저가 팔로우 한 Instance들을 부르는 건지 유저를 팔로우 한 Instance들을 불러온 건지 구분할 수 없다.

따라서 related_name으로 역참조시 User를 참조하는 Field들을 구분해준다.

Like & Follow View

LikeView, FollowView는 게시물과 사용자, 사용자와 사용자의 데이터를 입력받아, 관계성을 테이블에 저장하고 만약 데이터가 이미 존재한다면 Delete 해주는 것이다.

좋아요 기능

  • 좋아요를 표현하기 위해 등록된 게시물과 가입된 사용자가 필요하다.
  • 이미 좋아요한 게시물에 또 다시 좋아요를 할 경우 좋아요를 취소한다.

팔로우 기능

  • 팔로우를 하기 위해 팔로우 하는 유저, 팔로우 당하는 유저가 필요하다.
  • 이미 팔로우한 사람을 다시 팔로우할 경우 팔로우를 취소한다.

그 외의 제대로 Data를 입력받지 못할 경우 적절한 에러를 반환하도록 View를 작성해봤다.

class LikeView(View):
    def post(self, request):
        try:
            data = json.loads(request.body)

            if not User.objects.filter(email=data['email']).exists():
                return JsonResponse({"MESSAGE":"INVALID_USER"}, status=400)
            
            if not Post.objects.filter(id = data['post']).exists():
                return JsonResponse({"MESSAGE":"NO_POST"}, status=400)

            like, is_like = Like.objects.get_or_create(
                user = User.objects.get(email=data['email']),
                post_id = data['post']
            )

            if not is_like:
                like.delete()
                return JsonResponse({"MESSAGE":"DELETE"}, status=200)
            
            return JsonResponse({"MESSAGE":"SUCCESS"}, status=201)
        except KeyError:
            return JsonResponse({"MESSAGE":"KEY_ERROR"}, status=400)

class FollowView(View):
    def post(self, request):
        try:
            data = json.loads(request.body)

            if not User.objects.filter(email=data['follow']).exists():
                return JsonResponse({"MESSAGE":"INVALID_USER"}, status=400)

            if not User.objects.filter(email=data['follow']).exists():
                return JsonResponse({"MESSAGE":"INVALID_USER"}, status=400)
            
            follow, is_follow = Follow.objects.get_or_create(
                follow   = User.objects.get(email=data['follow']),
                followed = User.objects.get(email=data['followed'])
            )

            if not is_follow:
                follow.delete()
                return JsonResponse({"MESSAGE":"DELETE"}, status=200)

            return JsonResponse({"MESSAGE":"SUCCESS"}, status=201)
        except KeyError:
            return JsonResponse({"MESSAGE":"KEY_ERROR"}, status=400)

get_or_create() method

등록된 게시물이 아니거나, 가입된 사용자가 아닐 경우 에러를 반환하는 코드는 이 전과 같은 방식으로 사용되었다. 하지만 이번엔 입력받은 두 데이터의 조건에 따라 다른 명령을 해야했다.

  • 데이터가 Table에 존재하지 않으면 Create 한다.
  • 이미 데이터가 존재하면 데이터 Delete 한다.

이런 조건을 처리하기 위해 안성맞춤인 method가 get_or_create()라고 생각했다. get은 QuerySet에서 특정 조건을 만족하는 Instance를 return하고, create는 새로운 Instance를 만들어 return한다.

반면 get_or_create()는 (object, created) 라는 Tuple을 return한다. objectget이나 create를 사용했을 때와 같이 return해주는 Instance이고 created는 boolean flag로, 만약 get_or_create에 의해 새로 instance가 생성되었다면 True, 이미 데이터베이스에 존재하던 Instance라면 False를 return한다.

 like, is_like = Like.objects.get_or_create(
 	user = User.objects.get(email=data['email']),
    	post_id = data['post']
 )
 if not is_like:
 	like.delete()
    	return JsonResponse({"MESSAGE":"DELETE"}, status=200)

따라서 위와 같이 코딩을 하게되면 like에는 Like Instance, is_like에는 Boolean 값이 선언되며, 이미 좋아요를 눌러 Instance가 존재했다면 False값을 return 하여 if not is_lie 명령문을 실행해 row를 삭제할 것이다.

httpie request

서버에 httpie로 요청하여 구현한 기능이 잘 실행되는지 확인해보자.

  • 아이유가 나의 갱얼쥐 게시물에 좋아요를 눌러주었다.
http -v POST 127.0.0.1:8000/posts/like email="dlwlrma@naver.com" post=2
  • 내가 아이유를 팔로우했다. 그랬더니 아이유도 나를 팔로우했다.
http -v POST 127.0.0.1:8000/posts/follow follow="leon1111@naver.com" followed="dlwlrma@naver.com"
http -v POST 127.0.0.1:8000/posts/follow follow="dlwlrma@naver.com" followed="leon1111@naver.com"

  • 아이유가 나를 언팔했다..
http -v POST 127.0.0.1:8000/posts/follow follow="dlwlrma@naver.com" followed="leon1111@naver.com"


TIL

동일 클래스의 같은 객체를 참조하는 다른 Field들을 역참조 할 수 있도록 하는 것, get_or_create()를 사용해 데이터를 생성하거나 삭제하는 법애 대해 새로 배우게 되었다. 생각보다 ERD도 단순하고 구현할 View Logic도 많지 않았기 때문에, 여유가 생긴다면 조금씩 살을 붙여 Table과 Field, 기능들을 추가하여 github에 commit해야겠다.

profile
어디를 가든 마음을 다해 가자

1개의 댓글

comment-user-thumbnail
2021년 7월 28일

아이유가 언팔했다는 항목이 살짝 슬프네요 ㅋㅋㅋ 아직 저는 구현하지 못한 부분이라 어떻게 하셨는지 참고하려고 봤는데 깔끔하게 정리 잘하셔서 많이 배우고 갑니다 종성님!

답글 달기