DRF中空Base64ImageField保存报文件为空错误
问题根因
- 你设置的
required=False、allow_null=True、allow_empty_file=True仅对父类ImageField的原生校验逻辑生效,自定义的to_internal_value方法没有提前拦截空值,导致空字符串进入了后续处理流程:- 前端传入的空字符串
""属于字符串类型,会直接进入isinstance(data, six.string_types)的处理分支 - 空字符串不含base64头标识,直接执行base64解码,得到空字节串
- 空字节串被包装为
ContentFile传入父类方法,父类检测到文件内容为空,直接抛出The submitted file is empty错误
- 前端传入的空字符串
- 额外注意:
allow_null=True仅会放过None类型的入参,不会自动识别空字符串为清空指令。 - 原代码的
to_representation方法还存在隐藏bug:当字段值为None时,直接访问instance.name会抛出空对象异常。
修复代码
修改Base64ImageField类,在to_internal_value最前端增加空值拦截逻辑,同时修复序列化空值的bug,完整实现如下:
class Base64ImageField(serializers.ImageField): def to_internal_value(self, data): # 拦截空值:传入None或空字符串时直接返回None,触发字段清空 if data in (None, ""): return None if isinstance(data, six.string_types): if 'data:' in data and ';base64,' in data: header, data = data.split(';base64,') try: decoded_file = base64.b64decode(data) except TypeError: self.fail('invalid_image') # 拦截解码后为空的非法base64内容 if not decoded_file: return None file_name = str(uuid.uuid4())[:16] file_extension = self.get_file_extension(file_name, decoded_file) complete_file_name = "%s.%s" % (file_name, file_extension, ) data = ContentFile(decoded_file, name=complete_file_name) return super(Base64ImageField, self).to_internal_value(data) def get_file_extension(self, file_name, decoded_file): import imghdr extension = imghdr.what(file_name, decoded_file) extension = "jpg" if extension == "jpeg" else extension return extension def to_representation(self, instance): # 先判断实例是否存在,避免空值报错 if not instance or not instance.name: return None return settings.BASE_URL + reverse('download', args=[instance.name])
注意事项
- 保证对应模型的
logo字段配置了null=True,允许存储空值,否则返回None时会触发模型层非空校验错误 - 原有序列化器字段上的
required=False、allow_null=True、allow_empty_file=True参数无需修改,保留即可 - 修复后前端传入空字符串、
null都可以正常清空已存储的logo图片,传入合法base64图片时保留原有上传逻辑不变
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

