Django: changing image size and upload to S3
<p>I have inherited a Django Project and we have moved images to S3</p>

<p>One of the models is a typical user profile</p>

<pre><code>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
</code></pre>

<p>Where <code>profile_image_name</code> is a function:</p>

<pre><code>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])
</code></pre>

<p>I have a bit of code that worked:</p>

<pre><code>@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
</code></pre>

<p>Which is called from a post_save signal handler :</p>

<pre><code>@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 &gt; MAX_WIDTH:
                result = resize_image.delay(instance.profile_image.name, MAX_WIDTH)
                instance.profile_image.name = result.get()
            if width &gt; 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()
</code></pre>

<p>The intent is to take uploaded images and resize them 1) to the max width that a mobile device can display and 2) to a 50 pixel thumbnail for use in the mobile app.</p>

<p>When I look on S3, I do not see my resized images or thumbnails. Yet the unit tests (which are thorough) don't give any errors.</p>

<p>When I get the image dimensions:</p>

<pre><code>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)
</code></pre>

<p>There is no problem accessing the object's ImageField.  I get no error when I use default_storage to open the instance's profile_image. The PIL method </p>

<p><code>new_im = im.resize((dim_x, int(new_y)), Image.ANTIALIAS)</code>  does return a new instance of class 'PIL.Image.Image'.</p>

<p>In fact (pardon my verbosity)</p>

<p>This does not raise an error:</p>

<pre><code>&gt;&gt;&gt; u = User(email="root@groupon.com", password="sdfbskjfskjfskjdf")
&gt;&gt;&gt; u.save()
&gt;&gt;&gt; p = Profile(user=u, profile_image=create_image_file())
&gt;&gt;&gt; p.save()
&gt;&gt;&gt; from django.core.files.storage import default_storage as storage
&gt;&gt;&gt; orig = storage.open(p.profile_image.name, 'r')
&gt;&gt;&gt; orig
&lt;S3BotoStorageFile: uploads/profile_img/b0fd4f00-cce6-4dd3-b514-4c46a801ab19.jpg&gt;
&gt;&gt;&gt; im = Image.open(orig, mode='r')
&gt;&gt;&gt; im
&lt;PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=5000x5000 at 0x10B8F1FD0&gt;
&gt;&gt;&gt; im.__class__
&lt;class 'PIL.JpegImagePlugin.JpegImageFile'&gt;
&gt;&gt;&gt; dim_x = 500
&gt;&gt;&gt; new_y = (float(dim_x) * float(im.height)) / float(im.width)
&gt;&gt;&gt; new_im = im.resize((dim_x, int(new_y)), Image.ANTIALIAS)
&gt;&gt;&gt; new_im.__class__
&lt;class 'PIL.Image.Image'&gt;
&gt;&gt;&gt; img_path, img_name = path.split(p.profile_image.name)
&gt;&gt;&gt; file_name, img_ext = img_name.rsplit('.', 1)
&gt;&gt;&gt; append_str='_resized'
&gt;&gt;&gt; new_img_path = path.join(img_path, file_name + append_str + '.' + img_ext)
&gt;&gt;&gt; new_f = storage.open(new_img_path, 'w')
&gt;&gt;&gt; new_f
&lt;S3BotoStorageFile: uploads/profile_img/b0fd4f00-cce6-4dd3-b514-4c46a801ab19_resized.jpg&gt;
&gt;&gt;&gt; new_im.save(new_f)  #### This does NOT create an S3 file!!!!
&gt;&gt;&gt; im.close()
&gt;&gt;&gt; new_im.close()
&gt;&gt;&gt; new_f.close()
</code></pre>

<p><code>&gt;&gt;&gt; p.save()</code> uploads the new profile image to S3.  I was expecting <code>&gt;&gt;&gt; new_im.save(new_f)</code> to write the Image file to S3.  But it does not.</p>

<p>Any insight or help is greatly appreciated and thank you for taking the time to look at this problem.</p>

<p>Edit ...</p>

<p>My settings:</p>

<pre><code>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'
</code></pre>

<p>Where custom_storage.py is</p>

<pre><code>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
</code></pre>