django-storages icon indicating copy to clipboard operation
django-storages copied to clipboard

Value Error when uploading resized images to Google Cloud Storage

Open domicoeth opened this issue 6 years ago • 6 comments

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.

domicoeth avatar Feb 04 '19 00:02 domicoeth

@designkai Did you ever figure this out?

sww314 avatar Apr 03 '19 01:04 sww314

@sww314 Nope, I had to switch to AWS for file storage.

domicoeth avatar Apr 03 '19 01:04 domicoeth

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

bgrace avatar May 19 '19 20:05 bgrace

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 avatar Jun 25 '20 12:06 samuel88-cloud

@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 avatar Jun 30 '20 20:06 bgrace

@bgrace Thanks a lot for a working solution!

Daniil0891 avatar Jul 28 '22 18:07 Daniil0891