본문 바로가기
Django

[Django] DRF - 1차 프로젝트 리팩토링 (3) Cart

by 혀넝 2022. 6. 9.

리팩토링을 진행하면서 cart 부분이 가장 시간이 오래 걸렸다.
1차 프로젝트 당시에 cart 부분을 담당하지 않아서 이해하는데 시간이 걸린 것도 있지만, view 또는 serializer 둘 중 어디서 create 또는 post를 적용시켜야 할지 오랜 고민을 하기도 했다. 몇 번의 시도 끝에 3일 만에 기능을 구현할 수 있었다.

1. CreateAPIView 활용

처음에는 generic views를 사용해서 구현했다.

# serializer

class CartSerializer(serializers.ModelSerializer):
    user    = serializers.StringRelatedField(read_only=True)
    product = serializers.StringRelatedField(read_only=True)
    
    class Meta:
        model = Cart
        fields = '__all__'
# view

class CartCreateView(generics.CreateAPIView):
    serializer_class   = CartSerializer
    permission_classes = [IsAuthenticated]

    def perform_create(self, serializer):
        pk      = self.kwargs.get('pk')
        user    = self.request.user
        product = Product.objects.get(pk=pk)

        serializer.save(user=user, product=product)
# url

...
path('/create/<int:pk>', CartCreateView.as_view()),
...

view에서 perform_create를 활용했기 때문에 url에서 pk를 넘기고 있다.

의문점

cart model을 보면 product와 user를 foreign key로 물고 있다. user는 request.user로 가져오면 되는데, product id를 처리하는 게 잘 되지 않아서 perform_create method를 이용했다. 그러면 url에서 pk 값으로 product를 가져올 수는 있지만, cart post에 있어서 url에 pk 값을 넣는 게 옳은 걸까?

 

2. APIView 활용

위에서 든 의문을 해결하고자 이번엔 APIView를 사용했다.

그런데 APIView를 사용하면 계속 product id가 넘어가지 않는 문제가 있어 강의에서 사용한 코드를 살펴보니, serializers.py에서 문제점을 발견해서 수정했다.

기존 serializer의 내용은 아래와 같았다

class CartSerializer(serializers.ModelSerializer):
    user    = serializers.StringRelatedField(read_only=True)
    product = serializers.ReadOnlyField(source='product.id')

		class Meta:
        model = Cart
        fields = '__all__'

 

수정한 serializer는 아래와 같다

# version 1
class CartSerializer(serializers.ModelSerializer):
    user       = serializers.StringRelatedField(read_only=True)
    product_id = serializers.ReadOnlyField(source='product.id')

		class Meta:
        model = Cart
        fields = '__all__'


# version 2

class CartSerializer(serializers.ModelSerializer):
    user = serializers.StringRelatedField(read_only=True)

		class Meta:
        model = Cart
        fields = '__all__'

기존에 product로 설정해둔 이름을 product_id로 바꾸니 제대로 product id가 넘어갔다. 또는 해당 한 줄을 제거해도 잘 넘어갔다. 첫 번째 버전에서 source=’product.id’로 설정할 거면 굳이 필요 없어서 두 번째 버전을 사용했다.

# view

class CartListView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        serializer = CartSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(user=request.user)
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)
    
    def get(self, request):
        carts      = Cart.objects.all()
        serializer = CartSerializer(carts, many=True)
        return Response(serializer.data)

view까지 작성하면 특별한 조건이 없는 post와 get은 잘 진행이 된다.

하지만 여기서 필요한 조건으로, 한 유저가 상품 1번을 3개 post 하고(pk=1) 다음에 다른 상품을 post 하다가, 다시 상품 1번을 2개 post 하면, 새로운 데이터로 입력되는 것이 아니라, 기존에 post 했던 내용을 가져와(pk=1) 총상품이 5개가 되게 해야 한다.

그렇게 하기 위해서 serializer에서 create method를 override 해주었다.
ModelSerializer는 이미 create와 update method를 가지고 있기 때문에 따로 설정하지 않아도 된다. 하지만 custom이 필요한 내용이 있다면 override를 할 수 있다.

# serializer

class CartSerializer(serializers.ModelSerializer):
    user = serializers.StringRelatedField(read_only=True)
    
    def create(self, validated_data):
        user     = validated_data.get('user')
        quantity = validated_data.get('quantity')
        product  = validated_data.get('product')

        if not quantity:
            raise serializers.ValidationError({'you need more than 1 product'})

        cart, is_created = Cart.objects.get_or_create(
                                user     = user,
                                product  = product,
                                defaults = {'quantity':quantity}
                            )
        
        if not is_created:
            cart.quantity += quantity
            cart.save()

        return cart

    class Meta:
        model = Cart
        fields = '__all__'

처음엔 defaults={’quantity’:0}으로 설정을 했었는데, 이렇게 하니까 계속 0으로 리셋이 되어 수량이 늘어나지 않았었다. 하지만 현재의 defaults로 설정하니 이 문제는 해결되었다.
또한 처음엔 "if cart"로 조건을 걸었더니 여기서도 문제가 있었다. 처음에 quantity:2를 post 하면 처음 결과는 2가 나와야 하는데, 2에 2를 더한 4가 처음의 결과로 나왔다. 이 부분을 "if not is_created"로 조건을 바꾸니 해결되었다.

detail view도 작성하여 element get, put, delete도 구현했다. 완성된 view는 아래와 같다.

# view

class CartListView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        serializer = CartSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(user=request.user)
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)
    
    def get(self, request):
        carts      = Cart.objects.all()
        serializer = CartSerializer(carts, many=True)
        return Response(serializer.data)


class CartDetailView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, pk):
        try:
            cart = Cart.objects.get(pk=pk)
        except Cart.DoesNotExist:
            return Response({'your cart info does not exist'}, status=400)
        serializer = CartSerializer(cart)
        return Response(serializer.data, status=200)

    def put(self, request, pk):
        cart = Cart.objects.get(pk=pk)
        serializer = CartSerializer(cart, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=200)
    
    def delete(self, request, pk):
        cart = Cart.objects.get(pk=pk)
        cart.delete()
        return Response(status=204)