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

Django购物车API更新CartItem无效及序列化器表单字段异常

问题描述

我正在构建类似Udemy的后端系统,在购物车API中尝试更新CartItem对象(学生想用另一门课程替换购物车中的当前课程),需要修改CartItem的course字段并更新购物车的total_price。但执行PUT请求后,虽然返回HTTP 200 OK状态码,响应体却仍是更新前的对象数据,实际对象并未更新。

额外问题:序列化器的HTML表单无法识别字段,已尝试注释代码排查但未找到原因。


相关代码

models.py

class Cart(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid4)
    student = models.OneToOneField(
        User, on_delete=models.CASCADE, related_name='cart')
    total_price = models.IntegerField(default=0)


class CartItem(models.Model):
    cart = models.ForeignKey(
        Cart, on_delete=models.CASCADE, related_name='items')
    course = models.ForeignKey(Course, on_delete=models.CASCADE)

views.py

class CartItemViewSet(ModelViewSet):
    http_method_names = ['get', 'post', 'put', 'delete']

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

    def get_serializer_class(self):
        if self.request.method == 'PUT':
            return UpdateCartItemSerializer
        elif self.request.method == 'POST':
            return CreateCartItemSerializer
        else:
            return CartItemSerializer

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

serializers.py

class CartItemSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)
    student_id = serializers.SerializerMethodField(read_only=True)

    def get_student_id(self, cart_item):
        return Cart.objects.filter(id=self.context['cart_id'])[0].student_id

    class Meta:
        model = CartItem
        fields = ['id', 'student_id', 'course_id']


class CreateCartItemSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(read_only=True)

    def create(self, validated_data):
        instance = CartItem.objects.get(cart_id=validated_data['cart_id'],
                course_id=validated_data['course_id'])

        try:
            return instance
        except CartItem.DoesNotExist:
            instance = CartItem.objects.create(**validated_data)
            instance.save()

        return instance

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


class UpdateCartItemSerializer(serializers.ModelSerializer):
    def update(self, instance, validated_data):
        print(instance.__dict__, validated_data)
        current_course_id = instance.course_id
        new_course_id = validated_data.get('course_id', instance.course_id)

        if current_course_id != new_course_id:
            condition = CartItem.objects\
                .filter(cart_id=instance.cart_id) \
                .filter(course_id=new_course_id) \
                .exists()

            if not condition:
                with transaction.atomic():
                    CartItem.objects \
                        .filter(id=instance.id) \
                        .update(course_id=new_course_id)

                    old_price = Course.objects \
                        .filter(id=current_course_id)[0].price

                    new_price = Course.objects \
                        .filter(id=new_course_id)[0].price

                    current_cart_price = Cart.objects \
                        .filter(id=instance.cart_id)[0].total_price

                    current_cart_price -= old_price
                    current_cart_price += new_price

                    Course.objects \
                        .filter(id=new_course_id) \
                        .update(total_price=current_cart_price)

                    instance.save()
            else:
                CartItem.objects.filter(id=instance.id).delete()
                return Response(status=status.HTTP_204_NO_CONTENT)

        return instance

    class Meta:
        model = CartItem
        fields = ['id', 'course_id']
        read_only_fields = ('id', )

解决方案

主问题:PUT请求返回旧数据、实际未更新的修复

1. 修复instance刷新问题

你用CartItem.objects.filter(id=instance.id).update(course_id=new_course_id)直接更新数据库后,内存中的instance还是旧数据,导致返回的响应是旧值。两种修复方式:

  • 直接修改instance属性再保存:
    # 替换原CartItem更新代码
    instance.course_id = new_course_id
    instance.save()
    
  • 数据库更新后重新拉取instance:
    CartItem.objects.filter(id=instance.id).update(course_id=new_course_id)
    instance = CartItem.objects.get(id=instance.id)  # 刷新内存中的对象
    

2. 修复购物车总价更新错误

代码中错误地更新了Course的total_price,应该更新Cart的对应字段:

# 替换原总价更新代码
Cart.objects.filter(id=instance.cart_id).update(total_price=current_cart_price)

3. 修复CreateCartItemSerializer的异常逻辑

原代码中get()方法在对象不存在时会直接抛出异常,无法进入except块,调整为:

def create(self, validated_data):
    try:
        instance = CartItem.objects.get(cart_id=validated_data['cart_id'],
                course_id=validated_data['course_id'])
        return instance
    except CartItem.DoesNotExist:
        instance = CartItem.objects.create(**validated_data)
        return instance

额外问题:序列化器HTML表单无法识别字段的修复

1. 显式定义可写字段

HTML表单需要明确的可写字段,对于外键字段,显式指定course_id的字段类型:

class UpdateCartItemSerializer(serializers.ModelSerializer):
    course_id = serializers.PrimaryKeyRelatedField(
        queryset=Course.objects.all(), 
        source='course'  # 关联到模型的course外键
    )

    # 你的update逻辑...

    class Meta:
        model = CartItem
        fields = ['id', 'course_id']
        read_only_fields = ('id', )

或者直接使用course字段(表单会生成下拉选择框):

course = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all())

2. 确保视图集渲染表单时使用正确序列化器

ModelViewSet默认会根据请求方法调用get_serializer_class(),确保PUT请求对应的UpdateCartItemSerializer包含可写的、适合表单渲染的字段。


内容的提问来源于stack exchange,提问作者Mohab Yaser

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 02:37:02