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
相关产品推荐
相关产品推荐

