트러블 슈팅
문제 발생
IsAuthenticated 권한 클래스를 사용하여 Products 앱의 모든 기능에 인증된 사용자만 접근할 수 있도록 구현.
하지만 과제의 조건란을 자세히 읽어보니 상품 목록 조회에는 로그인 상태가 불필요하다는 것을 확인함.
결론적으로 상품 목록 조회, 상품 디테일 페이지 조회 기능에는 로그인하지 않아도 접근할 수 있도록 구현해야 하는 것.
하지만 어떻게 기능별로 IsAuthenticated를 적용, 미적용 시키는지 알지 못함.
class ProductListView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
def post(self, request):
title = request.data.get("title")
content = request.data.get("content")
image = request.FILES.get("image")
product = Product.objects.create(title=title, content=content, user=request.user, image=image)
serializer = ProductSerializer(product)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def get(self, request):
products = Product.objects.all()
serializer = ProductSerializer(products, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
class ProductDetailView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk):
product = get_object_or_404(Product, pk=pk)
serializer = ProductSerializer(product)
return Response(serializer.data, status=status.HTTP_200_OK)
def put(self, request, pk):
product = get_object_or_404(Product, pk=pk)
if product.user != request.user:
return Response({"message": "해당 게시글을 수정할 권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN)
serializer = ProductSerializer(product, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
product = get_object_or_404(Product, pk=pk)
if product.user != request.user:
return Response({"error": "해당 게시글을 삭제할 권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN)
product.delete()
return Response({"message": "게시글이 삭제되었습니다."}, status=status.HTTP_204_NO_CONTENT)
이런 식으로 구현하다보니 상품 목록, 상품 디테일 페이지 조회 시에도 토큰을 필요로 하는 상황 발생.
Postman에서도 토큰이 없으면 상품 목록 조회가 불가능.
문제 해결 중 위기 상황 발생
간단하게 필요한 메서드 내부에만 permission_classes = [IsAuthenticated]를 선언하면 된다고 생각했음.
class ProductListView(APIView):
parser_classes = [MultiPartParser, FormParser]
def post(self, request):
permission_classes = [IsAuthenticated]
title = request.data.get("title")
content = request.data.get("content")
image = request.FILES.get("image")
product = Product.objects.create(title=title, content=content, user=request.user, image=image)
serializer = ProductSerializer(product)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def get(self, request):
products = Product.objects.all()
serializer = ProductSerializer(products, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
class ProductDetailView(APIView):
def get(self, request, pk):
product = get_object_or_404(Product, pk=pk)
serializer = ProductSerializer(product)
return Response(serializer.data, status=status.HTTP_200_OK)
def put(self, request, pk):
permission_classes = [IsAuthenticated]
product = get_object_or_404(Product, pk=pk)
if product.user != request.user:
return Response({"message": "해당 게시글을 수정할 권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN)
serializer = ProductSerializer(product, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
permission_classes = [IsAuthenticated]
product = get_object_or_404(Product, pk=pk)
if product.user != request.user:
return Response({"error": "해당 게시글을 삭제할 권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN)
product.delete()
return Response({"message": "게시글이 삭제되었습니다."}, status=status.HTTP_204_NO_CONTENT)
이렇게 하면 되는 줄 알았다.
하지만 permission_classes를 개별 메서드 안에서 선언하는 구조는 올바르지 않다는 것을 알게 됨.
permission_classes = [IsAuthenticated]는 클래스 레벨에서 설정해야 한다는 것.
문제 해결
self.check_permissions(request)를 사용해서 문제를 해결함.
permission_classes를 클래스 레벨에서 선언하지 않고, self.check_permissions(request)를 통해 IsAuthenticated 권한을 메서드에서 사용하게 되는 것.
즉 IsAuthenticated를 직접 permission_classes에 선언하지 않고, 권한 검사를 수동으로 수행하는 방법을 채택.
class ProductListView(APIView):
parser_classes = [MultiPartParser, FormParser]
def post(self, request):
self.check_permissions(request)
title = request.data.get("title")
content = request.data.get("content")
image = request.FILES.get("image")
product = Product.objects.create(title=title, content=content, user=request.user, image=image)
serializer = ProductSerializer(product)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def get(self, request):
products = Product.objects.all()
serializer = ProductSerializer(products, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
class ProductDetailView(APIView):
def get(self, request, pk):
product = get_object_or_404(Product, pk=pk)
serializer = ProductSerializer(product)
return Response(serializer.data, status=status.HTTP_200_OK)
def put(self, request, pk):
self.check_permissions(request)
product = get_object_or_404(Product, pk=pk)
if product.user != request.user:
return Response({"message": "해당 게시글을 수정할 권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN)
serializer = ProductSerializer(product, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
self.check_permissions(request)
product = get_object_or_404(Product, pk=pk)
if product.user != request.user:
return Response({"error": "해당 게시글을 삭제할 권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN)
product.delete()
return Response({"message": "게시글이 삭제되었습니다."}, status=status.HTTP_204_NO_CONTENT)
필요한 기능에서만 self.check_permissions을 통해 인증된 사용자일 때 접근 가능하도록 구현.
토큰 없이도 상품 목록 조회가 잘 이루어지는 것을 확인.
게시글 상세 조회 역시 토큰 없이 잘 이루어진다.
개인 과제 완료
https://github.com/Kyuho09/spartamarket_DRF