django-storages
django-storages copied to clipboard
Value Error when uploading resized images to Google Cloud Storage
My image model contains;
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
image_asset = img.open(self.image)
image_name = uuid.uuid4()
image_asset.thumbnail((1200, 1200), img.ANTIALIAS)
fi_io = io.BytesIO()
image_asset.save(fi_io, format='JPEG', quality=90)
self.image = InMemoryUploadedFile(
fi_io,
'ImageField',
'%s.jpg' % image_name,
'image/jpeg',
sys.getsizeof(fi_io), None
)
super(Image, self).save(force_update=force_update)
Whenever I attempt to save an image I get this error (the exact size depends on the file uploaded):
ValueError: Size 3984 was specified but the file-like object only had 3862 bytes remaining.
Without this code, the image saves fine. Also when switching to a Digital Ocean storage this exact code works without a problem.
@designkai Did you ever figure this out?
@sww314 Nope, I had to switch to AWS for file storage.
I think this is an error in the end user code which GCS rejects and the other services are more liberal about. The call sys.getsizeof(fi_io) yields the size of the BytesIO
object, not the size of the buffer:
>>> iobuffer = io.BytesIO() # empty buffer (0 bytes)
>>> sys.getsizeof(iobuffer)
88
>>> len(iobuffer.getbuffer())
0
>>>
This following code works for me with django-storages and GCS when the returned thumbnail is saved to the model:
def generate_thumbnail(src):
image = Image.open(src) # in memory
image.thumbnail(settings.THUMBNAIL_SIZE, Image.ANTIALIAS)
buffer = BytesIO()
image.save(buffer, 'JPEG')
file_name = Path(src.name).name
temp_file = InMemoryUploadedFile(buffer, None, file_name, 'image/jpeg', len(buffer.getbuffer()), None)
return temp_file
Hi bgrace i followed the above, but it doesnt work, the default unsized image is being stored.
models.py
class Product(models.Model): name=models.CharField(max_length=100) image=models.ImageField(default='default.jpg',upload_to='productimages') price=models.FloatField()
def generate_thumbnail(self,src):
image = Image.open(src) # in memory
image.thumbnail((400,300), Image.ANTIALIAS)
buffer = BytesIO()
image.save(buffer, 'JPEG')
file_name = os.path.join(settings.MEDIA_ROOT, self.image.name)
temp_file = InMemoryUploadedFile(buffer, None, file_name, 'image/jpeg', len(buffer.getbuffer()), None)
return temp_file
def save(self, *args, **kwargs):
self.image=self.generate_thumbnail(self.image)
print(self.image.width) #prints the original size
print(self.image.height)
super(Product,self).save(*args, **kwargs)
@samuel88-cloud Just a guess but perhaps the call to save
with the same arguments is overwriting your change to self.image
. Consider using a post-save signal to generate the thumbnails. In any case it's not an issue with django-storages so questions should be taken to the support channel for PIL (or whatever image library you're using). Also look into using the Decimal type, not Float, for your price field, Float is not suitable for currency. See https://docs.python.org/3.8/library/decimal.html and https://husobee.github.io/money/float/2016/09/23/never-use-floats-for-currency.html
@bgrace Thanks a lot for a working solution!