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'})
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.