개발일지/스파르타코딩클럽

파이썬 장고 실무 심화 4주차 : 인스타그램 기능 클론(3)

마이구미+ 2023. 4. 25. 20:12

<실습>

- 좋아요 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했더니 팔로우 완료! 라고 떴다
  • 데이터베이스에 가보면 팔로잉 테이블에 데이터가 추가된 것을 볼 수 있다

  • 다시 한 번 클릭하면 팔로우가 취소되고 데이터베이스에도 사라진 것을 확인할 수 있다

팔로우 취소
DB에 데이터가 사라짐


- 게시글 리스트 / 게시글 상세페이지 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에 방금 만든 필드를 추가하는 것을 잊지 말아야 한다
  • 시리얼메소드필드를 사용하면 모델에 해당 필드가 없어도 시리얼라이저를 통해 필드를 만들어줄 수 있다!