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

Django单元测试外键约束失败:指定主键仍出现location_id非空错误

解决Django API单元测试中外键非空约束失败问题

问题场景

  • 多表Django项目中,ExampleModel通过外键关联Location模型,Location使用name字段作为自定义主键
  • 服务器运行时提交数据正常,但单元测试中创建ExampleModel对象时,触发IntegrityError: NOT NULL constraint failed: app_examplemodel.location_id
  • 测试代码先创建Location对象,再向ExampleModel接口提交包含location: "location name"的请求,但报错
  • 尝试给外键添加null=True后错误消失,但外键字段始终为null

核心原因

问题出在ExampleModelSerializer的depth=1配置上:

  • depth参数仅在序列化输出时生效,用于嵌套展示关联对象的详细数据
  • 在**反序列化(创建/更新数据)**时,DRF的ModelSerializer会忽略depth参数,且默认期望外键字段传入关联对象的主键值,但depth=1的存在会导致序列化器无法正确解析传入的location字段值,最终外键字段未被赋值,触发非空约束错误

另外,即使Location使用自定义主键name,Django ORM仍会自动生成location_id字段存储关联主键值,该字段默认非空。

解决方案

方案1:拆分序列化器(推荐)

创建两个序列化器,分别处理反序列化(创建/更新)和序列化(输出):

# 用于创建/更新的序列化器(无depth,支持外键解析)
class ExampleModelCreateSerializer(serializers.ModelSerializer):
    location = serializers.SlugRelatedField(
        queryset=Location.objects.all(),
        slug_field='name'  # 对应Location的主键字段name
    )

    class Meta:
        model = ExampleModel
        fields = "__all__"

# 用于输出的序列化器(带嵌套展示)
class ExampleModelSerializer(serializers.ModelSerializer):
    location = LocationSerializer()

    class Meta:
        model = ExampleModel
        fields = "__all__"

在视图中根据请求动作选择序列化器:

class ExampleModelViewSet(viewsets.ModelViewSet):
    queryset = ExampleModel.objects.all()
    
    def get_serializer_class(self):
        if self.action in ['create', 'update', 'partial_update']:
            return ExampleModelCreateSerializer
        return ExampleModelSerializer

方案2:单序列化器兼容两种场景

通过重写to_representation方法实现嵌套输出,同时显式指定外键的反序列化规则:

class ExampleModelSerializer(serializers.ModelSerializer):
    location = serializers.SlugRelatedField(
        queryset=Location.objects.all(),
        slug_field='name'
    )

    class Meta:
        model = ExampleModel
        fields = "__all__"

    def to_representation(self, instance):
        # 自定义序列化输出,实现类似depth=1的嵌套效果
        representation = super().to_representation(instance)
        representation['location'] = LocationSerializer(instance.location).data
        return representation

额外检查

确认模型定义的继承语法正确,ExampleModel的定义应该是:

class ExampleModel(BaseExampleModel):
    location = models.ForeignKey(Location, on_delete=models.PROTECT)

(原代码中缺少class关键字,属于笔误,需修正)

验证

修改后重新运行单元测试,location: "location name"会被正确解析为对应的Location对象,外键值正常存入location_id字段,不再触发非空约束错误;服务器端的嵌套输出功能也能正常工作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 09:43:37