<실습>
- 좋아요 models-serializer-views
- 일단 좋아요를 모델에 추가해줘야 한다
- 좋아요는 유저와 게시글 사이에서 다대다 관계를 맺는다
- 한 유저가 여러 게시글에 좋아요를 누를 수 있고, 한 게시글에 여러 유저들이 좋아요를 누를 수 있다
# articles/models.py
...
class Article(models.Model):
...
likes = models.ManyToManyField(User, related_name="like_articles")
def __str__(self):
...
...
- Article 모델에 likes 필드를 생성했다 User 모델과 다대다 관계를 가지고 있고, related_name은 like_articles로 했다 유저가 좋아한 게시글을 찾을 때는 user.like_articles로 찾을 수 있다
- makemigrations, migrate 잊지 않고 해준 다음 admin 페이지로 가서 좋아요를 눌러본다
- 저기 선택된 사람들이 이 게시글의 좋아요를 누른 사람들이다 좋아요 기능은 잘 작동하는 것 같다
- 이제 views.py에서 좋아요 클릭 함수를 작성하자
# articles/views.py
...
class LikeView(APIView):
def post(self, request, article_id):
"""좋아요 클릭"""
article = get_object_or_404(Article, id=article_id)
if request.user in article.likes.all():
article.likes.remove(request.user)
return Response("좋아요 취소..", status=status.HTTP_200_OK)
else:
article.likes.add(request.user)
return Response("좋아요 완료!", status=status.HTTP_200_OK)
- article은 댓글 조회했던 것처럼 불러오면 된다
- article.likes.all()은 해당 게시글에 좋아요를 누른 유저 모두를 부른 것이고, 방금 클릭한 유저가 그 유저들 중에 있는지를 묻는 게 조건문이다
- 만약 있다면 이미 좋아요를 누른 것이니 remove 함수를 사용해서 해당 유저를 likes 목록에서 삭제한다
- 없다면 add함수를 사용해서 좋아요 목록에 해당 유저를 추가한다
- 이제 포스트맨 가서 실행을 해보자
- like click 리퀘스트를 만들고 헤더에 로그인한 유저를 실어서 send하면 잘 작동되는 것을 확인할 수 있다
- admin 페이지에서 해당 게시글에 좋아요를 누른 유저이기 때문에 좋아요가 취소되었다
- 다시 한 번 누르면 좋아요 상태로 변한다
- 데이터베이스에서도 확인이 가능하다
- follow models-serializer-views
- 좋아요 기능이랑 로직이 비슷하다
- 먼저 User 모델에 followings 필드를 생성한다
# users/models.py
...
class User(AbstractBaseUser):
...
followings = models.ManyToManyField(
"self", symmetrical=False, related_name="followers")
...
- follow 기능은 유저끼리 다대다 관계다 그래서 self로 User 모델 자신을 참조한다
- symmetrical이 True이면 한쪽이 팔로우 했을 때 자동으로 맞팔이 된다 예전에 싸이월드 생각하면 된다 서로 팔로우하는 관계가 된다
- False면 내가 상대를 팔로우 하면 나만 팔로우 할 뿐 상대가 내 팔로워가 되지는 않는다
- related_name은 상대가 내 followings를 역참조 할 때 상대한테는 내가 follower가 되기 때문에 followers로 정했다
- 모델을 변경했으니 makemigrations, migrate를 잊지 않고 해준다
- users앱의 urls.py에 팔로우 url을 만들어준다
# users/urls.py
...
urlpatterns = [
...
path('follow/<int:user_id>/', views.FollowView.as_view(), name='follow_view'),
]
- 이제 views.py에 가서 FollowView 함수를 작성해준다
# users/views.py
from rest_framework.generics import get_object_or_404
from users.models import User
...
...
class FollowView(APIView):
def post(self, request, user_id):
"""팔로우 기능"""
you = get_object_or_404(User, id=user_id)
me = request.user
if me in you.followers.all():
you.followers.remove(me)
return Response("팔로우 취소..", status=status.HTTP_200_OK)
else:
you.followers.add(me)
return Response("팔로우 완료!", status=status.HTTP_200_OK)
- user가 2명이기 때문에 user로 하면 헷갈려서 로그인한 사용자는 me, 로그인한 사용자가 팔로우 하고 싶은 user는 you로 정했다
- 로직은 좋아요와 같다 you의 팔로우 목록에 me가 없으면 me를 추가해주고, 있으면 me를 삭제해준다
- 포스트맨에서 실행해보자
- following 리퀘스트를 생성하고 url에 팔로우 하고 싶은 user의 아이디를 넣고 Header에 로그인 사용자 정보를 실어 send했더니 팔로우 완료! 라고 떴다
- 데이터베이스에 가보면 팔로잉 테이블에 데이터가 추가된 것을 볼 수 있다
- 다시 한 번 클릭하면 팔로우가 취소되고 데이터베이스에도 사라진 것을 확인할 수 있다
- 게시글 리스트 / 게시글 상세페이지 serializer 수정
게시글 상세페이지에 댓글들 나오게 하기
- 먼저 Comment 모델에 article 필드의 related_name을 설정해준다
- Comment에서 article 필드를 Article 모델로 외래키를 사용하고 있어서 원래 기본값은 comment_set인데 게시글에서 댓글을 역참조할 때 게시글에 여러 댓글들이 있으니까 comments로 필드명을 정하는 것이 직관적이기 때문에 수정한다
# articles/models.py
...
class Comment(models.Model):
...
article = models.ForeignKey(
Article, on_delete=models.CASCADE, related_name="comments")
- 모델 수정했으니 makemigrations, migrate 해주기!!
- 이제 serializers.py로 이동한다
- 게시글 상세보기에서 참조하고 있는 건 ArticleSerializer니까 그 부분을 수정해준다
# articles/serializers.py
...
class ArticleSerializer(serializers.ModelSerializer):
...
comments = CommentSerializer(many=True)
...
- comments 리스트는 CommentSerializer를 참조하고 있어서 똑같이 comments에 CommentSerializer를 넣어주고 여러 댓글들이 있으니까 many=True를 매개변수에 넣는다
- 이렇게 다른 시리얼라이저를 참조하기 위해서는 그 시리얼라이저가 현재 시리얼라이저보다 위쪽에 있어야 한다 위치에 유의하도록 하자! 이걸 nested relationship이라고 한다(관련 문서 참조: Serializer relations - Django REST framework (django-rest-framework.org))
- 포스트맨에서 테스트해보면 게시글 상세페이지에 댓글들이 추가된 것을 확인할 수 있다
- 이제 수정하고 싶은 부분은 해당 게시글에 달린 댓글들이기 때문에 댓글에 있는 article 필드는 필요없어서 빼고싶다
- serializers.py에 가서 CommentSerializer를 변경해준다
# articles/serializers.py
...
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
exclude = ("article",) # 여기 변경!
- 원래는 fields = '__all__'였는데 모든 필드 중에 article 필드만 필요없어서 필드 중에 빼고 싶은 걸 뺄 수 있는 exclude를 사용했다(인스턴스인가..?모르겠다)
- 포스트맨에서 다시 실행해보면 article 필드가 사라진 것을 볼 수 있다
- 다음으로 수정하고 싶은 부분은 게시글 상세보기에 like필드에 like를 누른 user들의 아이디번호가 나온다 이걸 이메일 형식으로 바꾸고 싶다
- 공식 문서: Serializer relations - Django REST framework (django-rest-framework.org)
- 이 부분은 공식 문서를 참고해야 한다
- 공식 문서에 나온 대로 ArticleSerializer를 수정한다
# articles/serializers.py
...
class ArticleSerializer(serializers.ModelSerializer):
...
likes = serializers.StringRelatedField(many=True)
...
class Meta:
...
- 이렇게 쓰고 저장한 후 포스트맨에서 실행해보면 like 필드의 user들이 메일형식으로 나오는 걸 볼 수 있다
- 흠 다시 보니 댓글 단 user도 메일형식으로 나오면 좋을 것 같아서 CommentSerializer를 수정했다
# articles/serializers.py
...
class CommentSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField()
def get_user(self, obj):
return obj.user.email
class Meta:
model = Comment
exclude = ("article",)
- 이제까지 user 필드에 id로 나오는 걸 email로 나오게끔 바꾸는 코드를 그대로 복사했다
- 포스트맨에서 실행하면 댓글 작성자도 메일형식으로 나오는 걸 확인할 수 있다
게시글 리스트에 likes_count, comments_count 추가하기
- 전체 게시글 보기 함수에서 참조하는 시리얼라이저는 ArticleListSerializer니까 이 부분을 수정하면 된다
- user가 id로 나오는 걸 email로 바꾼 것처럼 시리얼라이저메소드필드를 쓰면 된다
- likes_count와 comments_count를 한 번에 만들어야겠다
# articles/serializers.py
...
class ArticleListSerializer(serializers.ModelSerializer):
...
likes_count = serializers.SerializerMethodField()
comments_count = serializers.SerializerMethodField()
...
def get_likes_count(self, obj):
return obj.likes.count()
def get_comments_count(self, obj):
return obj.comments.count()
class Meta:
model = Article
fields = ("pk", "title", "image", "updated_at",
"user", "likes_count", "comments_count",)
- fields에 방금 만든 필드를 추가하는 것을 잊지 말아야 한다
- 시리얼메소드필드를 사용하면 모델에 해당 필드가 없어도 시리얼라이저를 통해 필드를 만들어줄 수 있다!