1) 먼저, 이 구현을 위해서는 Review Model에 대한 수정이 필요하다.
User 모델을 임포트해야 한다. 우선 models.py로 들어간다.
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.contrib.auth.models import User
# 원래 맨 위 두 줄만 임포트 되어있었는데, 지금 이 바로 윗줄을 임포트해준다.
# 현재 모델에는 Ebook과 Review만 존재하는데, 그 어디에도 user 관련 테이블은 없다.
# 하지만 지금 우리는 review를 적은 바로 그 유저만 해당 리뷰 수정/삭제를 가능하게 할 것이므로
# django.contrib.auth.models 내의 User를 임포트한다. 이후 Review 모델만 수정한다.
class Review(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now_add=True)
review_author = models.ForeignKey(User, on_delete=models.CASCADE)
# 원래는 models.CharField(max_length=8, blank=True, null=True)였지만
# 임포트한 User 모델을 사용해준다.
review = models.TextField(blank=True, null=True)
rating = models.PositiveIntegerField(validators=[MinValueValidator(1),
MaxValueValidator(5)])
ebook = models.ForeignKey(Ebook,
on_delete=models.CASCADE,
related_name="reviews")
def __str__(self):
return str(self.rating)
2) models.py에 변화가 생기면 반드시 해야하는 일, python manage.py makemigrations
를 해준다. 이 때, 왜 default값 지정안하냐고 에러 비슷한 것이 뜰건데, 1번을 눌러 대충 괜찮은 default를 지정하는척하면서 None으로 입력한다. (실제 강사가 한 방식)
3) python manage.py migrate
4) ebooksapi/ebooks/api/serializers.py의 ReviewSerializer를 수정한다.
from rest_framework import serializers
from ebooks.models import Ebook, Review
class ReviewSerializer(serializers.ModelSerializer):
review_author = serializers.StringRelatedField(read_only=True)
# 왜 read_only=True를 하냐면, request.user와 review_author field를 자동으로 묶고 싶기 때문
class Meta:
model = Review
exclude = ("ebook", )
#fields = "__all__"
5) ebooksapi/ebooks/api/views.py의 ReviewCreateAPIView를 수정한다.
class ReviewCreateAPIView(generics.CreateAPIView):
queryset = Review.objects.all()
serializer_class = ReviewSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
# 3) 이 permission이 꼭 있어야 하는게, 인증된 유저가 아니면 앞의 1), 2) 과정은 진행되어서는 안되기 때문이다.
def perform_create(self, serializer):
ebook_pk = self.kwargs.get("ebook_pk")
ebook = get_object_or_404(Ebook, pk=ebook_pk)
review_author = self.request.user
# 1) 위에서 말했듯 request.user와 review_author를 자동으로 결합시킬 수 있는 코드를 짠다
review_queryset = Review.objects.filter(
ebook=ebook, review_author=review_author)
if review_queryset.exists():
raise ValidationError("You've already reviewed this Ebook!")
# 4) 한 유저가 한 책에 대해 리뷰를 작성한다면 한 건일테니,
# 두 건 이상 작성하지 못하도록 조건을 건다.
# 이 제약을 걸기위해서는 맨 윗단에 from rest_framework.exceptions import ValidationError를 임포트해줘야 한다.
serializer.save(ebook=ebook, review_author=review_author)
# 2) 그리고 원래는 ebook=ebook만을 인자로 취했지만 review_author=review_author(이게 방금 자동으로 결합된 review_author)를 써준다.
6) ebooksapi/ebooks/api/permissions.py 로 돌아가서 Review를 더 안전하게 해줄 완벽한 단 하나의 permission을 더 추가해보자.
class IsReviewAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.review_author == request.user
# 해당 리뷰를 쓴 사람이 접근하지 않으면 False값을 리턴하여 리뷰 수정/삭제를 막는다
7) 다시 ebooksapi/ebooks/api/views.py의 ReviewCreateAPIView를 수정한다. 방금 permissions.py에서 새로운 class를 만들었으므로 import단부터 바꿔야한다.
from rest_framework import generics
from rest_framework import permissions
from rest_framework.exceptions import ValidationError
from rest_framework.generics import get_object_or_404
from ebooks.models import Ebook, Review
from ebooks.api.permissions import IsAdminUserOrReadOnly, IsReviewAuthorOrReadOnly # 방금 생성한 IsReviewAuthorOrReadOnly 임포트 추가
from ebooks.api.serializers import EbookSerializer, ReviewSerializer
class ReviewCreateAPIView(generics.CreateAPIView):
queryset = Review.objects.all()
serializer_class = ReviewSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
ebook_pk = self.kwargs.get("ebook_pk")
ebook = get_object_or_404(Ebook, pk=ebook_pk)
review_author = self.request.user
review_queryset = Review.objects.filter(
ebook=ebook, review_author=review_author)
if review_queryset.exists():
raise ValidationError("You've already reviewed this Ebook!")
serializer.save(ebook=ebook, review_author=review_author)
class ReviewDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Review.objects.all()
serializer_class = ReviewSerializer
permission_classes = [IsReviewAuthorOrReadOnly]
# 당연히 이부분에 IsReviewAuthorOrReadOnly가 필요하게 되므로, permission_classes로 추가해준다.
# permission에 정의한대로, 작성자가 아니면 수정/삭제가 불가능하게 된다.