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

上传带图片数据时,无效关联主键引发UTF-8解码错误

Django REST Framework 上传图片时外键校验触发UTF-8解码错误问题

问题描述

在基于Django REST Framework的系统中,上传包含图片的ConsumerDetails数据时,若存在PrimaryKeyRelatedField缺失或ID无效的情况,会触发错误:'utf-8' codec can't decode byte 0xff in position 0: invalid start byte;但不上传图片时,系统能正常返回字段校验错误。

相关代码

模型定义

class ConsumerDetails(CreatorModifierInfo):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True, db_index=True)
    office = models.ForeignKey(Office, null=True, blank=True, on_delete=models.SET_NULL)
    service_cost_details = models.ForeignKey(ServiceCostDetails, null=True, on_delete=models.SET_NULL)
    photo = models.ImageField(null=True, blank=True, upload_to='consumer/image/%Y/%m/%d/')
    citizenship_issue_district = models.ForeignKey(District, related_name='citizenship_issue_district', null=True, blank=True, on_delete=models.SET_NULL)
    citizenship_front = models.ImageField(null=True, blank=True, upload_to='consumer/image/%Y/%m/%d/')
    citizenship_back = models.ImageField(null=True, blank=True, upload_to='consumer/image/%Y/%m/%d/')
    province = models.ForeignKey(Province, null=True, on_delete=models.SET_NULL)
    district = models.ForeignKey(District, null=True, on_delete=models.SET_NULL)
    municipality = models.ForeignKey(Municipality, null=True, on_delete=models.SET_NULL)
    ward = models.ForeignKey(Ward, null=True, on_delete=models.SET_NULL)
    tol = models.ForeignKey(Tol, null=True, on_delete=models.SET_NULL)
    status = models.BooleanField(default=True, null=True, blank=True)

    def __str__(self):
        return self.membership_number if self.membership_number else f"{self.id}"

序列化器定义

class ConsumerDetailsSerializer(serializers.ModelSerializer):
    service_cost_details = serializers.PrimaryKeyRelatedField(queryset=ServiceCostDetails.objects.all(), required=True, allow_empty=True, allow_null=True)
    province = serializers.PrimaryKeyRelatedField(queryset=Province.objects.all(), required=True, allow_null=True, allow_empty=True)
    district = serializers.PrimaryKeyRelatedField(queryset=District.objects.all(), required=True, allow_empty=True, allow_null=True)
    municipality = serializers.PrimaryKeyRelatedField(queryset=Municipality.objects.all(), required=True, allow_empty=True, allow_null=True)
    ward = serializers.PrimaryKeyRelatedField(queryset=Ward.objects.all(), required=True, allow_empty=True, allow_null=True)
    tol = serializers.PrimaryKeyRelatedField(queryset=Tol.objects.all(), required=True, allow_empty=True, allow_null=True)
    citizenship_front = serializers.ImageField()
    citizenship_back = serializers.ImageField()
    photo = serializers.ImageField()

    class Meta:
        model = ConsumerDetails
        fields = ['id', 'service_cost_details', 'house_number', 'old_membership_number',
                  'first_name', 'middle_name', 'last_name', 'email', 'phone_number', 'photo', 'address',
                  'date_of_birth', 'gender', 'pan_number', 'family_member_count', 'citizenship_number',
                  'citizenship_issue_district', 'citizenship_front', 'citizenship_back', 'province', 'district',
                  'municipality', 'ward', 'tol']

视图定义

class ConsumerDetailsView(viewsets.ViewSet, CustomPagination):
    permission_classes = [IsAuthenticated, HasGroupPermission]
    def create(self, request):
        """
        Create a Consumer Detail.
        """
        serializer = ConsumerDetailsSerializer(data=request.data)
        api_status = False
        if serializer.is_valid():
            serializer.save(creator=self.request.user, office=self.request.user.office)
            api_status = True
        return Response(
            {
                "status": api_status,
                "errors": serializer.errors,
                "results": serializer.data
            }, status=status.HTTP_200_OK if api_status else status.HTTP_400_BAD_REQUEST
        )

测试场景

  • 所有数据(含图片)正确时,API返回成功结果;
  • 不上传图片但传无效外键时,返回预期的字段校验错误;
  • 上传图片且传无效外键时,触发UTF-8解码错误,期望返回字段校验错误而非解码错误。

问题原因及解决方案

原因分析

上传文件时,DRF解析请求数据后,若外键校验失败,后续访问serializer.data会尝试将二进制图片数据序列化为JSON,而JSON无法处理二进制数据,从而触发UTF-8解码错误。另外,序列化器中图片字段设置为必填,但模型中图片字段允许为空,也会导致逻辑冲突。

解决步骤

  1. 修正序列化器图片字段属性
    模型中图片字段已设置null=True, blank=True,序列化器对应字段需添加required=False,匹配模型规则:

    citizenship_front = serializers.ImageField(required=False)
    citizenship_back = serializers.ImageField(required=False)
    photo = serializers.ImageField(required=False)
    
  2. 避免校验失败时访问serializer.data
    当serializer.is_valid()返回False时,serializer.data未完成正确序列化,直接访问会引发二进制数据解码问题。修改视图逻辑,仅在校验成功时返回数据:

    return Response(
         {
             "status": api_status,
             "errors": serializer.errors,
             "results": serializer.data if api_status else None
         }, status=status.HTTP_200_OK if api_status else status.HTTP_400_BAD_REQUEST
     )
    
  3. 可选:优先校验外键字段
    若需确保外键校验优先执行,可自定义序列化器的validate方法,提前校验外键,避免后续文件字段处理:

    def validate(self, data):
        required_foreign_keys = ['service_cost_details', 'province', 'district', 'municipality', 'ward', 'tol']
        for field in required_foreign_keys:
            pk = data.get(field)
            if pk is not None:
                model_class = self.fields[field].queryset.model
                try:
                    model_class.objects.get(pk=pk)
                except model_class.DoesNotExist:
                    raise serializers.ValidationError({field: f"无效的{model_class.__name__} ID"})
        return super().validate(data)
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 15:03:17