将调整尺寸后的Django图片上传至Google Cloud时触发ValueError问题排查
解决Django对接Google Cloud Storage时图片上传的Size不匹配错误
这个问题我之前在项目切换到GCS时也碰到过,核心原因是你用sys.getsizeof(output)获取的是BytesIO对象在内存中的总占用大小(包括对象本身的结构开销),而不是图片数据的真实字节数。GCS的存储后端会严格校验这个传入的大小值,当它发现实际读取到的字节数和你指定的不符时,就会抛出这个ValueError。
下面是修改后的完整save方法,我会逐一说明关键修改点:
import sys from io import BytesIO from uuid import uuid4 from PIL import Image from django.core.files.uploadedfile import InMemoryUploadedFile from django.db import models from django.contrib.auth.models import User class ItemImage(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) item = models.ForeignKey(Item, on_delete=models.CASCADE) image = models.ImageField(null=True, blank=True, upload_to='item_img/') created = models.DateTimeField(auto_now_add=True) def save(self, *args, **kwargs): # 仅当图片是新上传(未提交到存储)时才处理,避免更新其他字段重复生成图片 if self.image and not self.image._committed: im = Image.open(self.image) im = im.convert('RGB') output = BytesIO() # 调整图片尺寸 im = im.resize((700, 700)) # 将修改后的图片写入BytesIO im.save(output, format='JPEG', quality=90) # 获取图片数据的真实字节数:tell()返回当前文件指针位置,即写入的总字节数 file_size = output.tell() # 将指针移回开头,方便后续存储读取 output.seek(0) # 用UUID生成唯一文件名,避免原文件名重复或后缀问题 file_name = f"{uuid4()}.jpg" # 创建InMemoryUploadedFile时使用真实的文件大小 self.image = InMemoryUploadedFile( output, 'ImageField', file_name, 'image/jpeg', file_size, # 替换原来的sys.getsizeof(output) None ) # 调用父类save方法,传递所有参数避免丢失 super().save(*args, **kwargs) def __str__(self): return self.item.title
关键修改说明:
- 替换大小计算方式:
把sys.getsizeof(output)换成output.tell()。tell()会返回BytesIO当前的文件指针位置,也就是写入的图片数据的真实字节数,GCS的存储后端会正确识别这个值。 - 添加图片处理的条件判断:
新增if self.image and not self.image._committed:判断,只有当图片是新上传的(未被提交到存储)时才执行处理逻辑。这样可以避免在更新模型其他字段(比如修改关联的item)时重复生成图片,提升效率。 - 优化文件名生成:
直接用UUID生成唯一文件名,替换原来的%s.jpg" % self.image.name,避免原文件名带来的重复、后缀重复(比如xxx.jpg.jpg)等问题,同时保证文件名的唯一性。 - 传递完整的save参数:
在调用父类save方法时添加*args, **kwargs,确保所有传入save的参数(比如using、force_insert等)都能正确传递,避免功能异常。
本地存储没问题的原因是:Django默认的FileSystemStorage对大小参数的校验比较宽松,它会直接读取文件内容而忽略指定的大小;但GCS的存储后端(比如django-storages的GoogleCloudStorage)会严格校验这个值,所以就触发了错误。
内容的提问来源于stack exchange,提问作者YahyaST18




