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

DRF视图集create方法保存前丢失数据?求助排查

DRF ModelViewSet创建数据时task字段丢失导致非空约束错误

问题场景

我编写了如下DRF视图集:

class RecordViewSet(ModelViewSet):
    queryset = Record.objects.all()
    serializer_class = RecordSerializer
    filterset_fields = ['task', 'workday']
    
    def get_workday(self, request):
        date = get_date_from_calendar_string(request.data['date'])
        obj, _ = Workday.objects.get_or_create(user=request.user, date=date)
        return obj.id
    
    def create(self, request):
        request.data['workday'] = self.get_workday(request)
        print(request.data)
        return super().create(request)

调用create()方法时触发非空约束错误:

django.db.utils.IntegrityError: null value in column "task_id" violates not-null constraint
DETAIL:  Failing row contains (159, Added via task panel., 0, 0, 0, null, t, f, null, 98).

但create()中的打印语句显示提交的数据包含:

{'minutes_planned': 5, 'description': 'Added via task panel.', 'minutes_worked': 5, 'task': 148, 'workday': 98}

错误信息中task的主键148未被写入,数据丢失。未使用信号或重写模型save()方法。

关联的模型和序列化器代码:

模型代码

class Record(models.Model):
    task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='records')
    workday = models.ForeignKey(Workday, on_delete=models.CASCADE, related_name='records')
    description = models.CharField(max_length=128, blank=True, null=True)
    minutes_planned = models.IntegerField(default=0)
    minutes_worked = models.IntegerField(default=0)
    minutes_worked_store = models.IntegerField(default=0)
    user_generated = models.BooleanField(default=True)
    completed = models.BooleanField(default=False)

序列化器代码

class RecordSerializer(serializers.ModelSerializer):
    task = TaskSerializer(read_only=True)

    class Meta:
        model = Record
        fields = ['id', 'workday', 'description', 'completed', 'task', 'minutes_worked', 'minutes_planned']

原因分析

问题出在序列化器的task字段配置上:

  • 给task字段指定了TaskSerializer(read_only=True),read_only=True会告诉DRF这个字段只用于序列化输出(返回给前端),不处理反序列化输入(前端传入的数据)。
  • 因此即使前端传入了task:148,DRF在验证和处理数据时会直接忽略这个值,最终导致创建Record时task_id为空,触发非空约束错误。

解决方案

根据需求,有两种常见处理方式:

方式一:保留嵌套序列化输出,同时允许传入task ID

在序列化器中添加一个用于接收输入的task_id字段,保留task字段用于输出嵌套数据:

class RecordSerializer(serializers.ModelSerializer):
    task = TaskSerializer(read_only=True)
    task_id = serializers.PrimaryKeyRelatedField(
        queryset=Task.objects.all(),
        source='task',
        write_only=True
    )

    class Meta:
        model = Record
        fields = ['id', 'workday', 'description', 'completed', 'task', 'task_id', 'minutes_worked', 'minutes_planned']

此时前端提交数据时需要传task_id而非task,也可调整视图中对request.data的处理,把task值映射到task_id字段。

方式二:使用主键字段处理输入输出(不嵌套)

如果不需要返回嵌套的Task数据,直接移除TaskSerializer配置,让DRF使用默认的主键字段处理:

class RecordSerializer(serializers.ModelSerializer):
    # 移除task = TaskSerializer(read_only=True)这一行

    class Meta:
        model = Record
        fields = ['id', 'workday', 'description', 'completed', 'task', 'minutes_worked', 'minutes_planned']

这样DRF会自动处理task字段的主键输入和输出,前端传入task:148就能正常被序列化器接收。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 06:27:28