You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

将调整尺寸后的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的参数(比如usingforce_insert等)都能正确传递,避免功能异常。

本地存储没问题的原因是:Django默认的FileSystemStorage对大小参数的校验比较宽松,它会直接读取文件内容而忽略指定的大小;但GCS的存储后端(比如django-storagesGoogleCloudStorage)会严格校验这个值,所以就触发了错误。

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

火山引擎 最新活动