DRF使用SerializerMethodField获取外键信息报NoneType错误
问题根因
- 你遇到的
'NoneType' object has no attribute 'nom'报错,本质是关联链路上的所有外键都配置了null=True,直接用obj.location.organisme.nom这种硬链式写法时,只要任意一级关联(DocumentLocation的location、Location的organisme、Organisme的nom)为空,就会触发空指针错误。 - 接口返回中
location只显示ID是DRF的默认行为:外键字段默认序列化为主键值,没有配置嵌套序列化逻辑时不会返回关联对象的详情。 - 你之前尝试给
SerializerMethodField加source参数是无效用法,这个字段的返回值完全由对应的get_<字段名>方法决定,source参数不会对它的取值逻辑产生作用。
修复方案
基础修复(返回需要的机构名称、电话、用户邮箱,不改动原有创建逻辑、不改变location字段返回ID的格式)
直接修改你的序列化器代码,添加空值防御逻辑,避免链式取值空指针:
class NestedDocumentLocationSerializer(WritableNestedModelSerializer): # 新增三个只读关联字段,不影响写入逻辑 organisme_name = serializers.SerializerMethodField(read_only=True) organisme_telephone = serializers.SerializerMethodField(read_only=True) utilisateur_email = serializers.SerializerMethodField(read_only=True) nda = DocumentSerializer(allow_null=True, required=False) kbis = DocumentSerializer(allow_null=True, required=False) qualiopi = DocumentSerializer(allow_null=True, required=False) rib = DocumentSerializer(allow_null=True, required=False) contrat = DocumentSerializer(allow_null=True, required=False) class Meta: model = DocumentLocation # 把新增的三个字段加到fields列表里 fields = ['location', 'organisme_name', 'organisme_telephone', 'utilisateur_email', 'nda', 'kbis', 'qualiopi', 'rib', 'contrat'] def _get_linked_organisme(self, obj): # 抽公共方法做链路空校验,减少重复代码 if not obj.location: return None if not obj.location.organisme: return None return obj.location.organisme def get_organisme_name(self, obj): organisme = self._get_linked_organisme(obj) return organisme.nom if organisme else None def get_organisme_telephone(self, obj): organisme = self._get_linked_organisme(obj) return organisme.telephone if organisme else None def get_utilisateur_email(self, obj): organisme = self._get_linked_organisme(obj) if not organisme or not organisme.utilisateur: return None return organisme.utilisateur.email
可选优化:让location字段返回关联详情
如果你不想让location只返回ID,需要展示Location本身的字段信息,可以新增一个轻量的Location只读序列化器嵌套使用,不会破坏你现有的创建逻辑:
# 放在NestedDocumentLocationSerializer前面定义 class LocationBriefSerializer(serializers.ModelSerializer): class Meta: model = Location fields = ['id', 'siret', 'nda', 'date_ouverture', 'commentaire'] read_only_fields = fields class NestedDocumentLocationSerializer(WritableNestedModelSerializer): # 覆盖默认的location字段,只读不影响写入时传ID的逻辑 location = LocationBriefSerializer(required=False, allow_null=True) # 剩下的代码和上面基础修复的内容一致 organisme_name = serializers.SerializerMethodField(read_only=True) organisme_telephone = serializers.SerializerMethodField(read_only=True) utilisateur_email = serializers.SerializerMethodField(read_only=True) # ... 其余Document嵌套字段、Meta、get_*方法保持不变
性能优化建议
如果接口返回数据量较大,在对应视图的查询集上添加预加载配置,避免关联查询产生N+1问题:
# 视图中查询集配置示例 queryset = DocumentLocation.objects.select_related( 'location__organisme__utilisateur', 'nda', 'kbis', 'qualiopi', 'rib', 'contrat' ).all()
注意事项:
- 所有配置了
null=True的外键关联,做链式取值前必须做空判断,不要直接写多级点属性的硬编码。- SerializerMethodField默认就是只读字段,不需要额外在extra_kwargs里配置read_only。
- 如果嵌套的location序列化器不需要支持写入,一定要加只读配置,否则会要求你传Location的嵌套对象才能创建,破坏原有正常的创建流程。
内容的提问来源于stack exchange,提问作者Rémi
相关产品推荐
相关产品推荐

