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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 17:53:23