javascriptpythondjango-rest-frameworkdjango-rest-viewsets

Using http methods with django rest framework


I'm looking for some advice on how to correctly use my DRF API I have built, specifically the PATCH method at present. I am trying to formulate a script to patch the quantity of a Product / Cart Item that has been successfully added to the cart but I cannot get it to work.

For context of my project: My goal of the scripts are to be able to create a Cart, use a button to either add and Item or remove an Item (-1 quantity), Once the customer clicks a proceed to payment button the cart items are then transfered into the Order model So I can then process the order. Once order status is complete, cart is deleted and recreated.

Here is my current script:

const updateBtns = document.querySelectorAll(".update-cart");
const user = "{{request.user}}";
for (let i = 0; i < updateBtns.length; i++) {
  updateBtns[i].addEventListener("click", function () {
    event.preventDefault(); // prevent page from refreshing
    const productId = this.dataset.product;
    const action = this.dataset.action;
    console.log("productId:", productId, "action:", action);
    console.log("USER:", user);
    createCart(productId, action);
  });
}
function createCart(productId, action, cartId) {
  var csrftoken = getCookie("csrftoken");
  console.log("User is logged in, sending data...");
  fetch("/get_cart_id/")
    .then((response) => response.json())
    .then((data) => {
      const cartId = data.cart_id;
      console.log("Cart ID:", cartId); // log cartId in console

      let method, url;
      if (action === "add") {
        method = "POST";
        url = `/api/carts/${cartId}/items/`;
      } else if (action === "remove") {
        method = "PATCH";

        url = `/api/carts/${cartId}/items/${THIS_IS_WHERE_I_NEED_ITEM_ID}/`;
      } else {
        console.log(`Invalid action: ${action}`);
        return;
      }

      fetch(url, {
        method: method,
        headers: {
          "Content-Type": "application/json",
          "X-CSRFToken": csrftoken,
        },
        body: JSON.stringify({
          product_id: productId,
          quantity: 1,
        }),
      })
        .then((response) => response.json())
        .then((data) => {
          console.log(`Item ${action}ed in cart:`, data);
        });
    });
}

Here are my Serializera relating to my cart(please ask for me if you require more info to assist me):

class CartItemSerializer(serializers.ModelSerializer):
    product = SimpleProductSerializer()
    total_price = serializers.SerializerMethodField()

    def get_total_price(self, cart_item:CartItem):
        return cart_item.quantity * cart_item.product.price
    
    class Meta:
        model = CartItem
        fields = ['id', 'product', 'quantity', 'total_price']

 CartSerializer(serializers.ModelSerializer):
    id = serializers.UUIDField(read_only=True)
    items = CartItemSerializer(many=True, read_only=True)
    total_price = serializers.SerializerMethodField()

    def get_total_price(self, cart):
        return sum([item.quantity * item.product.price for item in cart.items.all()])

    class Meta:
        model = Cart
        fields = ['id', 'items', 'total_price']
    
class AddCartItemSerializer(serializers.ModelSerializer):
    product_id = serializers.IntegerField()

    def validate_product_id(self, value):
        if not Product.objects.filter(pk=value).exists():
            raise serializers.ValidationError('No product with the given ID was found.')
        return value

    def save(self, **kwargs):
        cart_id = self.context['cart_id']
        product_id = self.validated_data['product_id']
        quantity = self.validated_data['quantity']

        try: 
            cart_item = CartItem.objects.get(cart_id=cart_id, product_id=product_id)
            cart_item.quantity += quantity
            cart_item.save()
            self.instance = cart_item
        except CartItem.DoesNotExist:
            self.instance = CartItem.objects.create(cart_id=cart_id, **self.validated_data)
        
        return self.instance

    class Meta:
        model = CartItem
        fields = ['id', 'product_id', 'quantity']

class UpdateCartItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = CartItem
        fields = ['quantity']

Finally here are my viewsets:

class CartViewSet(CreateModelMixin,
                  RetrieveModelMixin,
                  DestroyModelMixin,
                  GenericViewSet):
    queryset = Cart.objects.prefetch_related('items__product').all()
    serializer_class = CartSerializer
 
class CartItemViewSet(ModelViewSet):
    http_method_names = ['get', 'post', 'patch', 'delete']
   
    def get_serializer_class(self):
        if self.request.method == 'POST':
            return AddCartItemSerializer 
        elif self.request.method == 'PATCH':
            return UpdateCartItemSerializer
        return CartItemSerializer

    def get_serializer_context(self):
        return {'cart_id': self.kwargs['cart_pk']}

    def get_queryset(self):
        return CartItem.objects \
                .filter(cart_id=self.kwargs['cart_pk']) \
                .select_related('product')

This is my view that I created to retrieve the cart that is related to the current user which is created when they enter the home/gallery/product page:

def get_cart_id(request):
    if request.user.is_authenticated:
        cart = Cart.objects.get(user=request.user)
        cart_id = cart.id
        return JsonResponse({'cart_id': cart_id})
    else:
        return JsonResponse({'error': 'User is not authenticated'})

Solution

  • I would recommend moving if (action === "...") checks a bit higher in the nesting. It should simplify your code greatly, even if you need to make it longer by repeating fetch("/get_cart_id/"), it will be easier to follow. Even better would be to separate the actions into different functions.

    After you create an item using POST, if successful, the response will contain an id of your freshly created item. You can use that id to remove the item. The method should be DELETE and not PATCH.

    function createCart(productId, action, cartId) {
      // ...
      
      if (action === "add") {
        fetch("/get_cart_id/")
        .then((response) => response.json())
        .then((data) => {
          // ...
    
          fetch(`/api/carts/${cartId}/items/`, {
            method: 'POST',
            // ...
          })
          .then((response) => response.json())
          .then((data) => {
            // Save data.id somewhere
            console.log(`Created Item with id: ${data.id}`);
          });
        });
      }
      else if (action === "remove") {
        // Use saved id there
        fetch(`/api/carts/${cartId}/items/${SAVED_ID_OF_THE_ITEM_TO_REMOVE}/`, {
          method: 'DELETE',
          // ...
        })
        .then(...)
        // ...
      }
      // ...
    }
    

    One last thing, you should be storing your cart_id, since, I imagine, it's not something that's going to change, and you shouldn't fetch it every time you want to add a product.