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

如何处理嵌套关联中PrimaryKeyRelatedField字段的更新问题?

问题:嵌套序列化器更新时*_id字段未出现在validated_data中

在序列化器中,country是Country模型的关联对象,我添加了后缀为*_id的PrimaryKeyRelatedField字段,用于让前端在变更时发送关联ID。但通过公司序列化器更新地址时,country_id字段未出现在validated_data中,导致KeyError;单独更新地址时无此问题。尝试设置read_only=False、write_only=True均未解决。


相关代码与请求示例

地址序列化器

class AddressSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField()
    country = CountrySerializer(read_only = True)
    country_id = serializers.PrimaryKeyRelatedField(queryset = Country.objects.all(), source="country") # 用于获取ID的字段

    class Meta:
        model = Address
        fields = '__all__'

公司序列化器

class CompanySerializer(serializers.ModelSerializer):
    category = CategorySerializer(read_only=True)
    category_id = serializers.PrimaryKeyRelatedField(queryset = Category.objects.all(), source='category', allow_null=True)
    addresses = AddressSerializer(many=True, read_only=False)

    class Meta:
        model = Company
        fields = '__all__'

    def update(self, instance, validated_data):
        addresses = validated_data.pop('addresses')
        instance = super().update(instance, validated_data)

        address_list = []
        for address in addresses:
            address_id = address['id']
            if address_id != 0:
                address_instance = Address.objects.get(id = address_id)
                address_instance.country_id = address['country_id']
                address_instance.save()
            elif address_id == 0:
                address.pop('id', None)
                
        address_list.append(Address.objects.create(**address))
           instance.addresses.add(*address_list)
           return instance

更新请求JSON示例

{
   "id":25,
   "category_id":1,
   "name":"COMPANY SA DE CV",
   "addresses":[{
         "id":1,
         "country_id":2,
         "content_type":36,
         "object_id":25 
   }]
}

错误信息

...
File "/Users/obedramales/Sites/webegin-project/webegin/app/serializers.py", line 214, in update
address_instance.country_id = address['country_id']
KeyError: 'country_id'

模型定义

Company模型

class Company(SafeDeleteModel):
    category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True, blank=True)
    code = models.CharField(max_length=10, null=True)
    name = models.CharField(max_length=100)
    addresses = GenericRelation('Address')

Address模型

class Address(SafeDeleteModel):
    content_type = models.ForeignKey(ContentType, related_name='model_addresses', on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField(null=True)
    content_object = GenericForeignKey('content_type', 'object_id')
    country = models.ForeignKey(Country, on_delete=models.CASCADE, null=True, blank=True)

解决方法

原因分析

  1. PrimaryKeyRelatedField设置source="country"后,DRF会把前端传入的country_id值映射为country字段存入validated_data,不会保留country_id键。
  2. 嵌套更新场景下,未明确设置write_only=True的PrimaryKeyRelatedField默认会被视为只读,DRF不会解析前端传入的该字段。

修正步骤

1. 调整AddressSerializer的字段定义

把country_id换成IntegerField(更适合嵌套更新场景),或者明确设置write_only=True:

class AddressSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField()
    country = CountrySerializer(read_only=True)
    # 直接用IntegerField接收ID,设置write_only确保只用于写入
    country_id = serializers.IntegerField(write_only=True, required=False)

    class Meta:
        model = Address
        fields = '__all__'

    def update(self, instance, validated_data):
        # 让子序列化器自己处理country_id的更新
        if 'country_id' in validated_data:
            instance.country_id = validated_data.pop('country_id')
            instance.save()
        return super().update(instance, validated_data)

2. 优化CompanySerializer的update方法

不要手动操作子模型实例,让AddressSerializer处理自身的更新逻辑,降低耦合:

def update(self, instance, validated_data):
    addresses_data = validated_data.pop('addresses', [])
    # 先更新公司自身字段
    instance = super().update(instance, validated_data)

    for address_data in addresses_data:
        address_id = address_data.get('id')
        if address_id != 0:
            # 用AddressSerializer更新已有地址
            address_instance = Address.objects.get(id=address_id)
            address_serializer = AddressSerializer(address_instance, data=address_data, partial=True)
            if address_serializer.is_valid():
                address_serializer.save()
        else:
            # 创建新地址时自动补充泛型关联字段
            address_data.pop('id', None)
            address_data['content_type'] = ContentType.objects.get_for_model(instance)
            address_data['object_id'] = instance.id
            new_address = Address.objects.create(**address_data)
            instance.addresses.add(new_address)
    return instance

关键提示

  • 嵌套更新时,子序列化器的可写字段必须明确标记write_only=True或read_only=False,否则DRF会忽略前端传入的值。
  • 尽量让子序列化器处理自身的CRUD逻辑,避免在父序列化器中直接操作数据库实例,减少出错概率。

内容的提问来源于stack exchange,提问作者Obed Ramales

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 17:03:21