You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 06:37:03