在Django REST Framework中使用PATCH方法更新购物车商品数量遇阻求助
问题描述
在基于Django REST Framework(DRF)搭建的API中实现购物车功能,需求包括:创建购物车、按钮增减商品数量、支付后将购物车项转入Order模型、订单完成后重建购物车。当前前端使用PATCH方法更新购物车商品数量时,因无法获取对应Cart Item的ID导致功能失效,相关代码如下:
前端脚本
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(); // 阻止页面刷新 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); // 控制台打印cartId 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); }); }); }
购物车序列化器
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'] class 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']
视图集
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')
获取购物车ID视图
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'})
解决方案
针对无法获取Cart Item ID的问题,提供三种可行方案:
方案1:前端渲染时嵌入Cart Item ID
在渲染购物车页面的模板中,给每个增减按钮添加data-item-id属性,直接传入对应的Cart Item ID:
<!-- 模板中遍历购物车项的示例代码 --> {% for item in cart.items %} <div class="cart-item"> <!-- 商品信息展示 --> <button class="update-cart" data-product="{{ item.product.id }}" data-action="add" data-item-id="{{ item.id }}">+</button> <span>{{ item.quantity }}</span> <button class="update-cart" data-product="{{ item.product.id }}" data-action="remove" data-item-id="{{ item.id }}">-</button> </div> {% endfor %}
修改前端脚本,直接读取data-item-id并传入请求:
updateBtns[i].addEventListener("click", function () { event.preventDefault(); const productId = this.dataset.product; const action = this.dataset.action; const itemId = this.dataset.itemId; // 新增读取Item ID console.log("productId:", productId, "action:", action, "itemId:", itemId); console.log("USER:", user); createCart(productId, action, itemId); // 传入Item ID }); // 修改createCart函数参数 function createCart(productId, action, itemId) { 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); let method, url, body; if (action === "add") { method = "POST"; url = `/api/carts/${cartId}/items/`; body = JSON.stringify({ product_id: productId, quantity: 1, }); } else if (action === "remove") { method = "PATCH"; url = `/api/carts/${cartId}/items/${itemId}/`; body = JSON.stringify({ quantity: -1, // 传入-1实现数量减少 }); } else { console.log(`Invalid action: ${action}`); return; } fetch(url, { method: method, headers: { "Content-Type": "application/json", "X-CSRFToken": csrftoken, }, body: body, }) .then((response) => response.json()) .then((data) => { console.log(`Item ${action}ed in cart:`, data); // 可选:更新页面上的商品数量显示 }); }); }
方案2:后端新增通过Product ID更新的接口
修改CartItemViewSet,新增自定义动作,允许通过Product ID直接更新购物车项数量,无需Item ID:
from rest_framework.decorators import action from rest_framework.response import Response class CartItemViewSet(ModelViewSet): http_method_names = ['get', 'post', 'patch', 'delete'] # ... 保留原有代码 ... @action(detail=False, methods=['patch']) def update_by_product(self, request, cart_pk=None): product_id = request.data.get('product_id') quantity_change = request.data.get('quantity', 0) if not product_id: return Response({'error': 'product_id is required'}, status=400) try: cart_item = CartItem.objects.get(cart_id=cart_pk, product_id=product_id) cart_item.quantity += quantity_change // 确保数量不小于1,小于则删除该项 if cart_item.quantity < 1: cart_item.delete() return Response({'message': 'Item removed from cart'}) cart_item.save() serializer = CartItemSerializer(cart_item) return Response(serializer.data) except CartItem.DoesNotExist: return Response({'error': 'Cart item not found'}, status=404)
前端修改remove分支的请求地址,调用新增接口:
else if (action === "remove") { method = "PATCH"; url = `/api/carts/${cartId}/items/update_by_product/`; body = JSON.stringify({ product_id: productId, quantity: -1, }); }
方案3:前端预加载购物车项,缓存映射关系
页面加载时先获取购物车详情,构建Product ID到Item ID的映射表,后续操作直接使用:
let productToItemIdMap = {}; // 页面加载时获取购物车所有项 function loadCartItems() { fetch("/get_cart_id/") .then(response => response.json()) .then(data => { const cartId = data.cart_id; return fetch(`/api/carts/${cartId}/`); }) .then(response => response.json()) .then(cartData => { // 构建Product ID到Item ID的映射 cartData.items.forEach(item => { productToItemIdMap[item.product.id] = item.id; }); }); } // 页面加载完成后执行 document.addEventListener('DOMContentLoaded', loadCartItems); // 修改createCart函数 function createCart(productId, action) { var csrftoken = getCookie("csrftoken"); fetch("/get_cart_id/") .then((response) => response.json()) .then((data) => { const cartId = data.cart_id; let method, url, body; if (action === "add") { method = "POST"; url = `/api/carts/${cartId}/items/`; body = JSON.stringify({ product_id: productId, quantity: 1 }); } else if (action === "remove") { const itemId = productToItemIdMap[productId]; if (!itemId) { console.log("Item not found in cart"); return; } method = "PATCH"; url = `/api/carts/${cartId}/items/${itemId}/`; body = JSON.stringify({ quantity: -1 }); } else { console.log(`Invalid action: ${action}`); return; } fetch(url, { method: method, headers: { "Content-Type": "application/json", "X-CSRFToken": csrftoken }, body: body, }) .then(response => response.json()) .then(data => { console.log(`Item ${action}ed in cart:`, data); // 更新映射表,适配新增或删除操作 if (action === "add") { productToItemIdMap[productId] = data.id; } else if (data.quantity === 0) { delete productToItemIdMap[productId]; } }); }); }
内容的提问来源于stack exchange,提问作者David Henson
相关产品推荐
相关产品推荐

