如何处理嵌套关联中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)
解决方法
原因分析
PrimaryKeyRelatedField设置source="country"后,DRF会把前端传入的country_id值映射为country字段存入validated_data,不会保留country_id键。- 嵌套更新场景下,未明确设置
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
相关产品推荐
相关产品推荐

