上传带图片数据时,无效关联主键引发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解码错误。另外,序列化器中图片字段设置为必填,但模型中图片字段允许为空,也会导致逻辑冲突。
解决步骤
修正序列化器图片字段属性
模型中图片字段已设置null=True, blank=True,序列化器对应字段需添加required=False,匹配模型规则:citizenship_front = serializers.ImageField(required=False) citizenship_back = serializers.ImageField(required=False) photo = serializers.ImageField(required=False)避免校验失败时访问
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 )可选:优先校验外键字段
若需确保外键校验优先执行,可自定义序列化器的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
相关产品推荐
相关产品推荐

