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
相关产品推荐
相关产品推荐

