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

使用Django REST Framework上传文件:新增正常但无法更新同名数据

问题解决:DRF文件导入时无法更新同名现有条目

在使用Django REST Framework实现文件上传导入Category数据时,新数据能正常创建,但遇到CSV中name字段(已转小写)与数据库中现有条目重复时,无法更新现有条目,反而会触发唯一约束报错。

原代码

class CategoryUploadView(APIView):
    authentication_classes = [OAuth2Authentication]
    permission_classes = [AllowAny]    
    parser_classes = [MultiPartParser]

    def post(self, request):
        file_obj = request.FILES['file']
        decoded_file = file_obj.read().decode('utf-8').splitlines()
        reader = csv.DictReader(decoded_file)
        for row in reader:
            serializer = CategorySerializer(data=row)
            if serializer.is_valid():
                serializer.save()
            else:
                return Response(serializer.errors, status=400)
        return Response({"message": "Data imported successfully"}, status=200)

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model=Category
        fields=['id', 'name']

    def to_internal_value(self, data):
        data = data.copy()
        data['name'] = data.get('name', None).lower()
        return super().to_internal_value(data)

class Category(models.Model):
    name = models.CharField(max_length=32, unique=True)
    description = models.CharField(max_length=255)
    update_on = models.DateField(auto_now=True)

    def __str__(self) -> str:
        return self.name 

核心原因

当前代码每次调用serializer.save()都会尝试创建新的Category实例,而name字段是唯一约束,当CSV中存在同名(转小写后)条目时,会直接触发数据库唯一约束错误,且没有逻辑去匹配现有条目执行更新操作。

修复方案

我们可以在循环处理CSV每行数据时,先根据小写后的name查询现有记录,存在则更新,不存在则创建。同时优化错误处理,避免遇到单个错误就终止整个导入流程。

方案一:直接在视图层处理(高效直观)

修改CategoryUploadView的post方法:

from django.db import IntegrityError

class CategoryUploadView(APIView):
    authentication_classes = [OAuth2Authentication]
    permission_classes = [AllowAny]    
    parser_classes = [MultiPartParser]

    def post(self, request):
        file_obj = request.FILES['file']
        decoded_file = file_obj.read().decode('utf-8').splitlines()
        reader = csv.DictReader(decoded_file)
        errors = []
        for idx, row in enumerate(reader, 1):
            # 先处理name转小写,和serializer逻辑保持一致
            row_copy = row.copy()
            row_copy['name'] = row_copy.get('name', '').lower()
            try:
                # 查询现有条目,不存在则创建
                category, created = Category.objects.get_or_create(
                    name=row_copy['name'],
                    defaults={**row_copy}
                )
                # 如果不是新建条目,执行更新
                if not created:
                    # 仅更新模型中存在的字段
                    for key, value in row_copy.items():
                        if key != 'name' and hasattr(category, key):
                            setattr(category, key, value)
                    category.save()
            except IntegrityError as e:
                errors.append(f"第{idx}行数据出错: {str(e)}")
            except Exception as e:
                errors.append(f"第{idx}行数据处理失败: {str(e)}")
        
        if errors:
            return Response({"errors": errors}, status=400)
        return Response({"message": "Data imported successfully"}, status=200)

方案二:复用Serializer验证逻辑(符合DRF规范)

如果希望通过Serializer处理验证和更新逻辑,可以在初始化Serializer时传入现有实例:

class CategoryUploadView(APIView):
    authentication_classes = [OAuth2Authentication]
    permission_classes = [AllowAny]    
    parser_classes = [MultiPartParser]

    def post(self, request):
        file_obj = request.FILES['file']
        decoded_file = file_obj.read().decode('utf-8').splitlines()
        reader = csv.DictReader(decoded_file)
        errors = []
        for idx, row in enumerate(reader, 1):
            try:
                # 先处理name转小写
                row_copy = row.copy()
                row_copy['name'] = row_copy.get('name', '').lower()
                # 查询现有条目
                category = Category.objects.get(name=row_copy['name'])
                # 传入实例执行更新
                serializer = CategorySerializer(instance=category, data=row_copy)
            except Category.DoesNotExist:
                # 无实例则执行创建
                serializer = CategorySerializer(data=row_copy)
            
            if serializer.is_valid():
                serializer.save()
            else:
                errors.append(f"第{idx}行数据验证失败: {serializer.errors}")
        
        if errors:
            return Response({"errors": errors}, status=400)
        return Response({"message": "Data imported successfully"}, status=200)

额外优化建议

  • 批量处理性能:如果导入数据量极大,可以收集待创建和待更新的条目,最后用bulk_create和bulk_update批量操作,减少数据库查询次数。
  • 字段白名单:可以在视图中定义允许更新的字段列表,避免CSV中的无关字段导致意外更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 23:31:02