DRF使用ModelSerializer保存带Through表的多对多关系时触发AttributeError
问题分析
错误的核心原因是SaleSerializer中错误地将模型的多对多字段products与SaleItemSerializer绑定:
- Sale模型的
products字段是指向Product的多对多关联,序列化时DRF会取出关联的Product实例; - 但你用
SaleItemSerializer去序列化这些Product实例,而该序列化器期望处理的是SaleItem对象——它的product字段需要从SaleItem实例中获取关联的Product,于是DRF尝试在Product实例上调用.product属性,自然触发AttributeError。
另外还有两个显性问题:
- SaleItemSerializer的
fields里包含value,但SaleItem模型中只有price字段,没有value; - SaleSerializer的
create方法中,validated_data.pop('products')对应的是请求里的products数组,但这个字段原本是模型的多对多字段,现在被用来传递SaleItem数据,逻辑冲突。
解决方案
1. 修正模型关联与序列化器字段
先给SaleItem模型的sale外键添加related_name,方便反向查询:
class SaleItem(models.Model): sale = models.ForeignKey(Sale, on_delete=models.CASCADE, related_name='sale_items') # 添加反向关联名 product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.IntegerField() price = models.DecimalField(max_digits=10, decimal_places=2) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)
然后调整序列化器逻辑,区分商品展示与中间表数据:
# 定义商品基础序列化器,用于展示商品信息 class ProductSerializer(serializers.ModelSerializer): class Meta: model = Product fields = ['id', 'name', 'price'] # 根据需求选择字段 class SaleItemSerializer(serializers.ModelSerializer): product = ProductSerializer() # 嵌套商品序列化器,展示关联商品详情 # 可选:添加计算字段value(数量×单价) value = serializers.SerializerMethodField() def get_value(self, obj): return obj.quantity * obj.price class Meta: model = SaleItem fields = ['product', 'quantity', 'price', 'value'] # 修正字段匹配模型 class SaleSerializer(serializers.ModelSerializer): # 读取时展示关联的商品列表(只读) products = ProductSerializer(many=True, read_only=True) # 写入时接收中间表SaleItem的数据(只写) sale_items = SaleItemSerializer(many=True, write_only=True) class Meta: model = Sale fields = ['id', 'created_at', 'updated_at', 'products', 'sale_items'] def create(self, validated_data): sale_items_data = validated_data.pop('sale_items') sale = Sale.objects.create(**validated_data) for item_data in sale_items_data: # 从嵌套数据中取出商品ID,关联已存在的商品 product_id = item_data.pop('product')['id'] product = Product.objects.get(id=product_id) # 创建中间表记录 SaleItem.objects.create(sale=sale, product=product, **item_data) return sale
2. 调整请求数据格式
现在创建Sale的请求JSON需要匹配sale_items字段,示例:
{ "sale_items": [ { "product": {"id": 1}, "quantity": 2, "price": "19.99" }, { "product": {"id": 3}, "quantity": 1, "price": "29.99" } ] }
3. 读取Sale数据的效果
当读取Sale详情时,返回的JSON会包含products字段(关联的商品列表)和基础销售信息,示例:
{ "id": 1, "created_at": "2024-05-20T12:00:00Z", "updated_at": "2024-05-20T12:00:00Z", "products": [ {"id": 1, "name": "商品A", "price": "19.99"}, {"id": 3, "name": "商品C", "price": "29.99"} ] }
内容的提问来源于stack exchange,提问作者Andeen
相关产品推荐
相关产品推荐

