DRF序列化器创建关联模型实例时POST报字段必填错误
DRF关联模型保存报字段必填错误
场景说明
DRF初学者实现关联模型数据保存功能:models.py中PackageDetails和PhysicalDetail两个模型均通过外键关联至Member模型。
现有serializers.py代码
from rest_framework import serializers from .models import Member, PackageDetails, PhysicalDetail class PackageDetailsSerializer(serializers.ModelSerializer): is_expired = serializers.SerializerMethodField() members_expiry_date = serializers.SerializerMethodField() class Meta: model = PackageDetails exclude = ['id'] extra_fields = ['is_expired', 'members_expiry_date'] def get_is_expired(self, instance): return instance.is_expired def get_members_expiry_date(self, instance): return instance.members_expiry_date class PhysicalDetailSerializer(serializers.ModelSerializer): class Meta: model = PhysicalDetail exclude = ['id'] class MemberSerializer(serializers.ModelSerializer): physical_details = PhysicalDetailSerializer(many=True) package_details = PackageDetailsSerializer(many=True) class Meta: model = Member fields = '__all__' extra_fields = ['physical_details', 'package_details'] def create(self, validated_data): physical_detail_data = validated_data.pop("physical_details") package_detail_data = validated_data.pop("package_details") member = Member.objects.create(**validated_data) PhysicalDetail.objects.create(member=member, **physical_detail_data) PackageDetails.objects.create(member=member, **package_detail_data) return member
现有views.py代码
class MemberViewset(viewsets.ModelViewSet): queryset = Member.objects.all() serializer_class = MemberSerializer class PackageDetailViewset(viewsets.ModelViewSet): queryset = PackageDetails.objects.all() serializer_class = PackageDetailsSerializer class PhysicalDetailViewset(viewsets.ModelViewSet): queryset = PhysicalDetail.objects.all() serializer_class = PhysicalDetailSerializer
异常表现
接口GET请求可正常返回嵌套关联数据,但使用和GET响应结构完全一致的JSON发起POST请求时,接口返回如下错误:
{ "physical_details": [ "This field is required." ], "package_details": [ "This field is required." ] }
即使请求体中已经传入了对应字段,依然触发该错误。
问题成因
报错由三类代码错误共同导致:
- 嵌套关联字段和Django反向关联规则不匹配。Django外键的默认反向关联属性名为
小写模型名_set,PhysicalDetail、PackageDetails关联Member后,默认反向访问名是physicaldetail_set、packagedetails_set。序列化器中声明的嵌套字段名为physical_details、package_details,又没有指定source参数映射到正确的反向属性,反序列化阶段DRF无法从请求数据中匹配到对应字段的输入值,因此抛出字段必填错误。 create方法逻辑错误。嵌套字段设置了many=True,说明入参是列表结构,但现有代码直接对列表对象做解包传给单条create方法,即使字段校验通过也会抛出参数错误。- 配置项使用错误。
ModelSerializer不存在extra_fields配置项,所有序列化器中写在这个参数里的字段都不会被自动识别,属于无效配置。其中SerializerMethodField类型的自定义字段只要在序列化器类上显式声明就会自动生效,不需要额外加入Meta配置。
修复方案
- 处理字段映射问题:给两个嵌套序列化器字段添加
source参数,映射到正确的模型反向关联属性;如果希望直接用physical_details/package_details作为入参和出参的字段名,可以直接在两个子模型的外键字段上添加related_name='physical_details'、related_name='package_details'配置,后续不需要额外写source参数。 - 修正
create方法逻辑:遍历嵌套列表的每一项数据,批量创建关联模型实例,注意pop数据时的key要和source值(或外键的related_name值)保持一致。 - 删除所有无效的
extra_fields配置,嵌套字段只要在序列化器上显式声明,配置fields = '__all__'时会自动包含。
修复后的MemberSerializer参考代码:
class MemberSerializer(serializers.ModelSerializer): # 如果外键配置了对应的related_name,可去掉source参数 physical_details = PhysicalDetailSerializer(many=True, source='physicaldetail_set') package_details = PackageDetailsSerializer(many=True, source='packagedetails_set') class Meta: model = Member fields = '__all__' def create(self, validated_data): # 这里pop的key要和source的值保持一致,如果用了related_name就pop对应的related_name值 physical_detail_list = validated_data.pop("physicaldetail_set") package_detail_list = validated_data.pop("packagedetails_set") member = Member.objects.create(**validated_data) # 遍历列表创建关联数据 for phy_data in physical_detail_list: PhysicalDetail.objects.create(member=member, **phy_data) for pkg_data in package_detail_list: PackageDetails.objects.create(member=member, **pkg_data) return member
内容的提问来源于stack exchange,提问作者Sandesh Nepal
相关产品推荐
相关产品推荐

