<실습>
- 게시글 상세/수정/삭제 serializers.py, views.py 그리고 포스트맨으로 테스트하기
게시글 상세
- 먼저 views.py를 작성한다
# articles/views.py
...
class ArticleDetailView(APIView):
def get(self, request, article_id):
"""상세게시글 조회"""
article = Article.objects.get(id=article_id)
serializer = ArticleSerializer(article)
return Response(serializer.data, status=status.HTTP_200_OK)
- 전체 게시글 보기랑 비슷한데, articles 변수에 all로 받아서 담는 게 아니라 get을 이용해 해당 게시글 id에 해당하는 글 하나를 가져오는 게 다르다
- 시리얼라이저는 모든 필드를 가져오는 기본 아티클시리얼라이저를 쓴다
- 저장하고 포스트맨으로 가서 실행시켜본다
- article detail이라는 get 방식의 리퀘스트를 하나 만들고 url에는 urls.py에서 정한대로 게시글 아이디 번호를 넣어서 send 한다
- 다 잘 나오는데 user에 이메일 형식이 아닌 아이디번호 형태로 나오는 게 아쉽다
- 시리얼라이저를 수정한다
# articles/serializers.py
...
class ArticleSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField()
def get_user(self, obj):
return obj.user.email
class Meta:
...
...
- ArticleListSerializer에 써놨던 것처럼 시리얼라이저메소드를 이용한 부분을 그대로 긁어서 class Meta 위에 추가해준다
- 다시 포스트맨으로 가서 실행시켜 보면
- user에 아이디번호가 아닌 이메일 형식으로 출력되는 것을 확인할 수 있다
게시글 수정
- 2주차에 했던 것과 동일하게 해주면 된다
- 일단 views.py만 수정하면 되는데
# articles/views.py
from rest_framework.generics import get_object_or_404
...
class ArticleView(APIView):
...
class ArticleDetailView(APIView):
def get(self, request, article_id):
...
def put(self, request, article_id):
"""게시글 수정"""
article = get_object_or_404(Article, id=article_id)
serializer = ArticleCreateSerializer(article, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- 이렇게 하면 된다
- 데이터베이스에 저장된 데이터 아이디가 5까지밖에 없는데 누군가 주소를 직접 쳐서 5가 아닌 데이터베이스에 없는 숫자를 입력한다면 에러창이 뜬다
- 그걸 방지하기 위해 get_object_or_404를 쓴다
- 시리얼라이저에는 원래 게시글 정보를 넣어주고, data로 수정할 내용을 담아 넣고 검증을 통과하면 수정한대로 저장된다
- 포스트맨에서 확인해보자 아이디 4인 게시글을 수정하려고 한다 일단 데이터베이스에는 이렇게 써있다
- 포스트맨에서 수정 리퀘스트를 새로 만들고 바꾸고 싶은 내용을 입력하고 send를 누른다
- 잘 수정된 것 같다 다시 데이터베이스로 가보면
- 아이디가 4인 게시글이 수정된 것을 확인할 수 있다 업데이트 시간도 잘 반영되었다
- 그런데 여기 심각한 문제가 있다
- 지금 코드상으로는 로그인한 사용자가 누구든 게시글을 수정할 수 있게끔 되어있다
- 이걸 해당 게시글을 작성한 사용자만이 수정할 수 있도록 조건을 걸어줘야 한다
# articles/views.py
...
class ArticleView(APIView):
...
class ArticleDetailView(APIView):
def get(self, request, article_id):
...
def put(self, request, article_id):
"""게시글 수정"""
article = get_object_or_404(Article, id=article_id)
if request.user == article.user:
...
else:
return Response("권한이 없습니다!", status=status.HTTP_403_FORBIDDEN)
- article 변수에 해당 article 정보를 담은 이후에 수정을 누른 user와 게시글 작성자가 일치하는지를 판별한다
- 맞으면 코드를 이어서 진행하고, 틀리다면 권한이 없다는 메시지와 함께 403 코드를 띄운다
- 보통 403 Forbidden 코드는 admin 계정이 아니거나 글 작성자가 아닌 경우에 띄우고 로그인이 안 되어 있는 경우에는 401 Unuthorized 코드를 띄운다
- 이제 포스트맨으로 가서 다른 아이디로 로그인 한 후 4번 게시글을 수정하려고 하면
- 작성한 메시지가 잘 나온다!
게시글 삭제
- 2주차에 해봤듯 삭제는 간단하다
- 다만 게시글 작성자와 로그인한 사용자가 일치하는지 조건문을 잊지 말고 잘 넣어줄 것!
# articles/views.py
...
class ArticleView(APIView):
...
class ArticleDetailView(APIView):
def get(self, request, article_id):
...
def put(self, request, article_id):
...
def delete(self, request, article_id):
"""게시글 삭제"""
article = get_object_or_404(Article, id=article_id)
if request.user == article.user:
article.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
else:
return Response("권한이 없습니다!", status=status.HTTP_403_FORBIDDEN)
- 204코드를 쓰면 "글이 삭제되었습니다."하는 문구도 필요없다고 한다
- 이대로 포스트맨으로 실행해보면
- 삭제가 되지 않는다
- 이 글 작성자가 아닌 다른 사용자로 아까 로그인했기 때문
- 다시 이 글을 작성한 사용자로 로그인하고 실행하면
- 원하는 상태코드가 나오는 것으로 보아 잘 삭제된 것 같다
- 데이터베이스에 가보면
- 원래 있었던 5번 게시글이
- 삭제된 것을 확인할 수 있다
- 댓글 models-serializer-views
댓글 모델
- 먼저 Comment 모델을 작성해보자!
# articles/models.py
...
class Article(models.Model):
...
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
article = models.ForeignKey(Article, on_delete=models.CASCADE)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.content)
- 일단 user는 Article 모델에서 쓴 것처럼 User 모델에서 외래키로 가져왔고, article도 Article 모델에서 외래키로 가져왔다
- 댓글 내용이 들어갈 content와 생성시간, 수정시간 필드를 넣었고, admin 페이지에서 댓글 조회할 때 목록에 댓글 내용이 보이도록 __str__함수를 만들어서 content 값을 리턴하도록 했다
- 다음은 admin 페이지에 댓글 모델을 추가해준다
# articles/admin.py
...
from articles.models import Comment
...
admin.site.register(Comment)
- 이후 서버를 실행해서 http://127.0.0.1:8000/admin/ 여기로 가면 Comment 카테고리가 생성된 것을 확인할 수 있다
- add 눌러서 댓글을 생성해보면
- __str__함수로 인해 댓글 내용이 목록에 바로 보이는 걸 확인할 수 있다
- 댓글을 눌러보면 어떤 유저가 어떤 게시글에 썼는지 선택할 수 있게 되어 있다
댓글 조회
- 일단 댓글이나 좋아요나 한 게시글에 대한 것이니까 url에 게시글 아이디가 있어야 할 것 같다 urls.py를 수정하자
# articles/urls.py
...
urlpatterns = [
...
path('<int:article_id>/comment/', views.CommentView.as_view(), name='comment_view'),
path('<int:article_id>/comment/<int:comment_id>/', views.CommentDetailView.as_view(), name='comment_detail_view'),
path('<int:article_id>/like/', views.LikeView.as_view(), name='like_view'),
]
- 댓글, 댓글 상세, 좋아요 url에 모두 게시글 아이디를 추가했다
- 이제 serializers.py에 가서 CommentSerializer를 먼저 작성한다
# articles/serializers.py
...
from articles.models import Comment
...
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = "__all__"
- 일단 게시글 기본 시리얼라이저와 동일하게 만들었다
- 이제 views.py로 이동해서 작성한다
# articles/views.py
...
from articles.models import Comment
from articles.serializers import CommentSerializer
...
class CommentView(APIView):
def get(self, request, article_id):
"""댓글 조회"""
article = get_object_or_404(Article, id=article_id)
comments = article.comment_set.all()
serializer = CommentSerializer(comments, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
- 댓글이 달린 해당 게시글을 get_object_or_404로 가져온다(강의에는 Article.objects.get(id=article_id)로 되어 있는데 전에 설명했던 것처럼 주소를 직접 쳐서 들어가는데 게시글 아이디가 없는 번호라면 에러창이 뜰 것 같아서 내 맘대로 get_object_or_404를 썼다 나중에 잘못되면 고치지 뭐..!)
- comments 변수에는 해당 게시글의 댓글들을 모두 가져오는 코드인데 comment_set은 댓글에서 게시글을 역참조할 때(?)(아직 참조 역참조 그 방향?을 잘 모르겠다 여튼 역참조하는 거라고 한다) related_name이 기본값으로 commet_set 이라고 정해져 있다고 한다
- 위에서 작성한 커멘트시리얼라이저를 serializer 변수에 넣고 매개변수에 댓글들이 들어간 comments 변수와 여러 쿼리셋을 불러올 때 쓰는 many=True를 넣었다
- 이제 포스트맨으로 가보자
- comments 콜렉션을 새로 만들어서 comments list라는 리퀘스트를 생성했다
- 주소창에 아까 admin 페이지에서 댓글을 달았던 게시글 아이디가 3이어서 3을 넣어서 send했더니 댓글이 알맞게 잘 나왔다
- 왠지 댓글리스트시리얼라이저를 새로 만들어서 필요한 필드만 남기고 user는 이메일, article은 제목이 나오게끔 고칠 것 같은 예감이 드는군
댓글 작성
- 게시글 작성과 비슷하게 하면 된다
- 일단 댓글작성시리얼라이저를 만들어 준다
- 게시글 작성 때와 마찬가지로 user는 request.user로 저장할 거고, article id도 url에 있는 article id로 저장할 거다
- 그러니 content만 사용자가 작성하면 된다
# articles/serializers.py
...
class CommentCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ("content",)
- 여기서 주의할 점은 필드가 한 개만 있더라도 () 안에 넣어주고 ,(콤마)도 꼭 찍어줘야 한다 그래야 필드로 인식한다
- 괄호 안에 안 넣거나 콤마를 안 찍으면 그냥 스트링으로 인식한다고 한다
- 이제 다음은 views.py를 작성한다
# articles/views.py
...
from articles.serializers import CommentCreateSerializer
...
class CommentView(APIView):
def get(self, request, article_id):
...
def post(self, request, article_id):
"""댓글 작성"""
serializer = CommentCreateSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, article_id=article_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- 게시글 작성 함수에서 그대로 복붙해서 시리얼라이저에 CommentCreateSerializer로 수정하고, 시리얼라이저 저장할 때 매개변수로 article_id=article_id를 추가해줬다
댓글 수정
- 댓글 수정도 게시글 수정이랑 비슷하다
- views.py에 게시글 수정 함수의 내용을 그대로 복사해서 댓글 수정 함수에 붙여준다
# articles/views.py
...
from articles.serializers import CommentCreateSerializer
...
class CommentDetailView(APIView):
def put(self, request, comment_id, article_id):
"""댓글 수정"""
comment = get_object_or_404(Comment, id=comment_id)
if request.user == comment.user:
serializer = CommentCreateSerializer(comment, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response("권한이 없습니다!", status=status.HTTP_403_FORBIDDEN)
- 게시글 수정이랑 비슷하다 여기선 article 대신 comment를 불러온다
- 댓글 작성자와 로그인한 사용자가 일치하면 수정을 진행한다
- 시리얼라이저도 CommentCreateSerializer로 바꿔주고, 원래 정보인 comment 매개변수에 넣고, 수정한 데이터가 들어가게끔 data=request.data로 쓴다
- 저장할 때는 이미 유저아이디와 아티클아이디가 저장이 되어 있기 때문에 추가로 매개변수로 넣어주지 않아도 된다
- 함수에 article_id가 들어간 곳이 없는데 매개변수로 있는 이유는 url에 article_id가 들어가기 때문이다
- 댓글 수정하는 데에 아티클아이디가 꼭 필요한 건 아닌데 url의 통일성을 위해 넣어주었다
- 이제 포스트맨에서 테스트를 해보면
- 댓글이 잘 수정되는 것을 확인할 수 있다
- 댓글 수정 리퀘스트를 만들 때 Header에 유저 정보 넣는 것을 잊지 말아야 한다
- 댓글 작성자가 아닌 다른 유저 정보를 넣으면 권한이 없다고 뜰 것이다
- 댓글 리스트를 다시 조회해보면
- 댓글이 수정된 것을 확인할 수 있다
댓글 삭제
- 게시글 삭제 함수에서 복붙하면 된다
# articles/views.py
...
class CommentDetailView(APIView):
def put(self, request, comment_id, article_id):
...
def delete(self, request, comment_id, article_id):
"""댓글 삭제"""
comment = get_object_or_404(Comment, id=comment_id)
if request.user == comment.user:
comment.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
else:
return Response("권한이 없습니다!", status=status.HTTP_403_FORBIDDEN)
- article만 comment로 바꿔주면 된다
- 포스트맨에서 실행해보면
- 댓글이 잘 삭제되는 걸 볼 수 있다
- 댓글리스트에도 해당 댓글이 없어진 것을 확인할 수 있다