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

DRF中PUT更新外键数据:如何仅更新已编辑的嵌套Description

解决DRF嵌套序列化器更新描述时重复创建的问题

你遇到的核心问题是没有利用Description的主键(pk)来匹配已存在的记录,而是用text + task作为get_or_create的查询条件——这就导致当用户修改描述文本时,原有的记录匹配不上,只能新建一条。下面是一套简单直接的解决方案:

关键思路

要区分「更新现有描述」和「创建新描述」,必须依赖Description的pk:

  1. 前端传递数据时,对已存在的描述必须带上它的pk;
  2. 后端在update方法里,根据pk判断是更新还是创建;
  3. 可选:同步删除前端未传递的原有描述(如果需要保持数据完全一致)。

修改后的代码实现

1. 调整TaskSerializer的update方法

重点修改描述处理的逻辑,替换掉原来的get_or_create逻辑:

def update(self, instance, validated_data):
    descriptions_data = validated_data.pop('descriptions', None)
    
    # 先更新Task自身的基础字段(保留你原有逻辑)
    instance.title = validated_data.get('title', instance.title)
    instance.project = validated_data.get('project', instance.project)
    instance.status = validated_data.get('status', instance.status)
    
    # 处理作者和执行者的逻辑(保留你原有逻辑)
    author = validated_data.pop('author', None)
    executor = validated_data.pop('executor', None)
    try:
        if author:
            task_author = User.objects.get(username=author['username'])
            instance.author = task_author
        if executor:
            task_executor = User.objects.get(username=executor['username'])
            instance.executor = task_executor
    except User.DoesNotExist:
        raise ValidationError(_("Такого пользователя не существует!"))
    
    # 核心:处理描述的更新/创建/删除
    if descriptions_data:
        # 收集现有任务的所有描述ID,用于后续判断删除
        existing_desc_pks = set(instance.descriptions.values_list('pk', flat=True))
        incoming_desc_pks = set()
        
        for desc_data in descriptions_data:
            desc_pk = desc_data.get('pk')
            
            if desc_pk:
                # 有PK,说明是更新现有描述
                try:
                    description = instance.descriptions.get(pk=desc_pk)
                    description.text = desc_data['text']
                    description.save()
                    incoming_desc_pks.add(desc_pk)
                except Description.DoesNotExist:
                    # 可选:如果传递的PK不属于当前任务,抛出错误
                    raise ValidationError(_(f"Описание с ID {desc_pk} не принадлежит этой задаче!"))
            else:
                # 无PK,创建新描述
                description = Description.objects.create(task=instance, text=desc_data['text'])
                incoming_desc_pks.add(description.pk)
        
        # 可选:删除前端未提交的原有描述(如果需要同步数据)
        # instance.descriptions.exclude(pk__in=incoming_desc_pks).delete()
        
        # 更新任务的描述集合(确保关联正确)
        instance.descriptions.set(instance.descriptions.filter(pk__in=incoming_desc_pks))
    
    instance.save()
    return instance

2. 前端数据格式要求

前端提交更新请求时,必须给已存在的描述带上pk,示例:

{
  "title": "Обновленная задача",
  "descriptions": [
    {"pk": 1, "text": "Редактированный текст описания"},
    {"text": "Новое описание"}
  ]
}

为什么原来的get_or_create不行?

你之前用text=description["text"], task=instance作为查询条件,当用户修改描述文本时,这个条件就匹配不到数据库里的原有记录,所以get_or_create会默认新建一条。而用pk作为匹配依据,不管文本怎么改,都能精准定位到要更新的记录。

额外优化建议

  • 可以给DescriptionSerializer的pk字段加上required=False(其实默认就是非必填,不过明确写出来更清晰);
  • 如果需要严格控制权限,可以在更新描述时检查用户是否有权限修改该记录;
  • 若不想处理删除逻辑,直接去掉注释里的删除代码即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:13:10