I inherited the Django project, and we moved the images to S3
One of the models is a typical user profile.
class Profile(UUIDBase): first_name = models.CharField(_("First Name"), max_length=20) last_name = models.CharField(_("Last Name"), max_length=20, null=True) profile_image = models.ImageField( _("Profile Image"), upload_to=profile_image_name, max_length=254, blank=True, null=True ) profile_image_thumb = models.ImageField( _("Profile Image Thumbnail"), upload_to=profile_image_name, max_length=254, blank=True, null=True ) ... other fields
Where profile_image_name is a function:
def profile_image_name(instance, filename): if filename: target_dir = 'uploads/profile_img/' _, ext = filename.rsplit('.', 1) filename = str(instance.uid) + '.' + ext return '/'.join([target_dir, filename])
I have some code that worked:
@shared_task def resize_image(image_path, dim_x, append_str='_resized', **kwargs): ''' resize any image_obj while maintaining aspect ratio ''' orig = storage.open(image_path, 'r') im = Image.open(orig, mode='r') new_y = (float(dim_x) * float(im.height)) / float(im.width) new_im = im.resize((dim_x, int(new_y)), Image.ANTIALIAS) img_path, img_name = path.split(image_path) file_name, img_ext = img_name.rsplit('.', 1) new_img_path = path.join(img_path, file_name + append_str + '.' + img_ext) try: new_f = storage.open(new_img_path, 'w') except IOError as e: logger.critical("Caught IOError in {}, {}".format(__file__, e)) ravenclient.captureException() return None try: new_im.save(new_f) except IOError as e: logger.critical("Caught IOError in {}, {}".format(__file__, e)) ravenclient.captureException() return None except Exception as e: logger.critical("Caught unhandled exception in {}. {}".format( __file__, e) ) ravenclient.captureException() return None im.close() new_im.close() new_f.close() return new_img_path
which is called from the post_save handler:
@receiver(post_save, sender=Profile, dispatch_uid='resize_profile_image') def resize_profile_image(sender, instance=None, created=False, **kwargs): if created: if instance.profile_image: width, height = image_dimensions(instance.profile_image.name) print(width, height) if width > MAX_WIDTH: result = resize_image.delay(instance.profile_image.name, MAX_WIDTH) instance.profile_image.name = result.get() if width > THUMB_WIDTH: result = resize_image.delay( instance.profile_image.name, THUMB_WIDTH, append_str='_thumb' ) instance.profile_image_thumb.name = result.get() try: instance.save() except Exception as e: log.critical("Unhandled exception in {}, {}".format(__name__, e)) ravenclient.captureException()
The goal is to download the downloaded images and resize them 1) to the maximum width that the mobile device can display, and 2) to a thumbnail of 50 pixels for use in a mobile application.
When I look at S3, I do not see my modified images or thumbnails. However, unit tests (which are exhaustive) give no errors.
When I get the image sizes:
def image_dimensions(image_path): f = storage.open(image_path, 'r') im = Image.open(f, 'r') height = im.height width = im.width im.close() f.close() return (width, height)
No problem accessing the ImageField object. I do not get an error when I use default_storage to open an instance of profile_image. PIL Method
new_im = im.resize((dim_x, int(new_y)), Image.ANTIALIAS) returns a new instance of the class 'PIL.Image.Image'.
Actually (have mercy on my verbosity)
This does not cause an error:
>>> u = User(email=" root@groupon.com ", password="sdfbskjfskjfskjdf") >>> u.save() >>> p = Profile(user=u, profile_image=create_image_file()) >>> p.save() >>> from django.core.files.storage import default_storage as storage >>> orig = storage.open(p.profile_image.name, 'r') >>> orig <S3BotoStorageFile: uploads/profile_img/b0fd4f00-cce6-4dd3-b514-4c46a801ab19.jpg> >>> im = Image.open(orig, mode='r') >>> im <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=5000x5000 at 0x10B8F1FD0> >>> im.__class__ <class 'PIL.JpegImagePlugin.JpegImageFile'> >>> dim_x = 500 >>> new_y = (float(dim_x) * float(im.height)) / float(im.width) >>> new_im = im.resize((dim_x, int(new_y)), Image.ANTIALIAS) >>> new_im.__class__ <class 'PIL.Image.Image'> >>> img_path, img_name = path.split(p.profile_image.name) >>> file_name, img_ext = img_name.rsplit('.', 1) >>> append_str='_resized' >>> new_img_path = path.join(img_path, file_name + append_str + '.' + img_ext) >>> new_f = storage.open(new_img_path, 'w') >>> new_f <S3BotoStorageFile: uploads/profile_img/b0fd4f00-cce6-4dd3-b514-4c46a801ab19_resized.jpg> >>> new_im.save(new_f)
>>> p.save() loads the new profile picture in S3. I was expecting >>> new_im.save(new_f) write the image file to S3. But this is not so.
Any insight or help is greatly appreciated and thank you for taking the time to look at this issue.
Edit ...
My settings:
AWS_STORAGE_BUCKET_NAME = 'testthis' AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME MEDIAFILES_LOCATION = 'media' MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, MEDIAFILES_LOCATION) DEFAULT_FILE_STORAGE = 'custom_storages.MediaStorage'
Where custom_storage.py
from django.conf import settings from storages.backends.s3boto import S3BotoStorage class MediaStorage(S3BotoStorage): location = settings.MEDIAFILES_LOCATION bucket_name = settings.AWS_STORAGE_BUCKET_NAME