Westagram Project를 진행하며 회원가입, 로그인, 게시물과 댓글 등록 기능까지 구현해봤다. 이번엔 게시물에 대한 좋아요 기능과 사용자끼리의 팔로우 기능을 추가해봤다.
기존 Database Model에 Like와 Follow Table을 추가하여 AQueryTool을 이용해 ERD를 구상해봤다.
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들을 구분해준다.
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)
등록된 게시물이 아니거나, 가입된 사용자가 아닐 경우 에러를 반환하는 코드는 이 전과 같은 방식으로 사용되었다. 하지만 이번엔 입력받은 두 데이터의 조건에 따라 다른 명령을 해야했다.
이런 조건을 처리하기 위해 안성맞춤인 method가 get_or_create()
라고 생각했다. get
은 QuerySet에서 특정 조건을 만족하는 Instance를 return하고, create
는 새로운 Instance를 만들어 return한다.
반면 get_or_create()
는 (object, created) 라는 Tuple을 return한다. object
는 get
이나 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"
동일 클래스의 같은 객체를 참조하는 다른 Field들을 역참조 할 수 있도록 하는 것, get_or_create()를 사용해 데이터를 생성하거나 삭제하는 법애 대해 새로 배우게 되었다. 생각보다 ERD도 단순하고 구현할 View Logic도 많지 않았기 때문에, 여유가 생긴다면 조금씩 살을 붙여 Table과 Field, 기능들을 추가하여 github에 commit해야겠다.
아이유가 언팔했다는 항목이 살짝 슬프네요 ㅋㅋㅋ 아직 저는 구현하지 못한 부분이라 어떻게 하셨는지 참고하려고 봤는데 깔끔하게 정리 잘하셔서 많이 배우고 갑니다 종성님!