changeset 964:51a2051588f5

Image uploading now expects a file. Refactor image uploading to not expect a Django UploadedFile and use a regular file instead. This will be needed for the future feature of being able to save and upload images from the Internet.
author Brian Neal <bgneal@gmail.com>
date Wed, 02 Sep 2015 20:50:08 -0500
parents 4619290d171d
children 734a4a350bd5
files core/functions.py core/image_uploader.py user_photos/forms.py
diffstat 3 files changed, 71 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/core/functions.py	Tue Sep 01 20:33:40 2015 -0500
+++ b/core/functions.py	Wed Sep 02 20:50:08 2015 -0500
@@ -1,9 +1,9 @@
 """This file houses various core utility functions"""
-from contextlib import contextmanager
 import datetime
 import logging
 import os
 import re
+import tempfile
 
 from django.contrib.sites.models import Site
 from django.conf import settings
@@ -12,15 +12,23 @@
 import core.tasks
 
 
-@contextmanager
-def temp_open(path, mode):
-    """A context manager for closing and removing temporary files."""
-    fp = open(path, mode)
-    try:
-        yield fp
-    finally:
-        fp.close()
-        os.remove(path)
+class TemporaryFile(object):
+    """Context manager class for working with temporary files.
+
+    The file will be closed and removed when the context is exited.
+    """
+    def __init__(self, **kwargs):
+        """kwargs will be passed to mkstemp."""
+        self.kwargs = kwargs
+
+    def __enter__(self):
+        self.fd, self.filename = tempfile.mkstemp(**self.kwargs)
+        self.file = os.fdopen(self.fd, 'w+b')
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.file.close()
+        os.remove(self.filename)
 
 
 def send_mail(subject, message, from_email, recipient_list, reply_to=None, defer=True):
--- a/core/image_uploader.py	Tue Sep 01 20:33:40 2015 -0500
+++ b/core/image_uploader.py	Wed Sep 02 20:50:08 2015 -0500
@@ -7,12 +7,10 @@
 import logging
 from io import BytesIO
 import os.path
-import tempfile
 import uuid
 
 from PIL import Image
 
-from core.functions import temp_open
 from core.image import orient_image
 
 
@@ -24,17 +22,16 @@
     return b64encode(uuid.uuid4().bytes, '-_').rstrip('=')
 
 
-def upload(fp, bucket, metadata=None, new_size=None, thumb_size=None):
+def upload(filename, bucket, metadata=None, new_size=None, thumb_size=None):
     """Upload an image file to a given S3Bucket.
 
     The image can optionally be resized and a thumbnail can be generated and
     uploaded as well.
 
     Parameters:
-        fp - The image file to process. This is expected to be an instance of
-             Django's UploadedFile class. The file must have a name attribute and
-             we expect the name to have an extension. This is extension is
-             used for the uploaded image and thumbnail names.
+        filename - The path to the file to process. The filename should have an
+                   extension, and this is used for the uploaded image & thumbnail
+                   names.
         bucket - A core.s3.S3Bucket instance to upload to.
         metadata - If not None, must be a dictionary of metadata to apply to the
                    uploaded file and thumbnail.
@@ -51,52 +48,38 @@
     a thumbnail was not requested.
     """
 
-    filename = fp.name
     logger.info('Processing image file: %s', filename)
 
-    # Trying to use PIL (or Pillow) on a Django UploadedFile is often
-    # problematic because the file is often an in-memory file if it is under
-    # a certain size. This complicates matters and many of the operations we try
-    # to perform on it fail if this is the case. To get around these issues,
-    # we make a copy of the file on the file system and operate on the copy.
-    # First generate a unique name and temporary file path.
     unique_key = make_key()
-    ext = os.path.splitext(fp.name)[1]
-    temp_name = os.path.join(tempfile.gettempdir(), unique_key + ext)
+    ext = os.path.splitext(filename)[1]
 
-    # Write the UploadedFile to a temporary file on disk
-    with temp_open(temp_name, 'wb') as temp_file:
-        for chunk in fp.chunks():
-            temp_file.write(chunk)
-        temp_file.close()
+    # Re-orient if necessary
+    image = Image.open(filename)
+    changed, image = orient_image(image)
+    if changed:
+        image.save(filename)
 
-        # Re-orient if necessary
-        image = Image.open(temp_name)
-        changed, image = orient_image(image)
-        if changed:
-            image.save(temp_name)
+    # Resize image if necessary
+    if new_size:
+        image = Image.open(filename)
+        if image.size > new_size:
+            logger.debug('Resizing from {} to {}'.format(image.size, new_size))
+            image.thumbnail(new_size, Image.ANTIALIAS)
+            image.save(filename)
 
-        # Resize image if necessary
-        if new_size:
-            image = Image.open(temp_name)
-            if image.size > new_size:
-                logger.debug('Resizing from {} to {}'.format(image.size, new_size))
-                image.thumbnail(new_size, Image.ANTIALIAS)
-                image.save(temp_name)
+    # Create thumbnail if necessary
+    thumb = None
+    if thumb_size:
+        logger.debug('Creating thumbnail {}'.format(thumb_size))
+        image = Image.open(filename)
+        image.thumbnail(thumb_size, Image.ANTIALIAS)
+        thumb = BytesIO()
+        image.save(thumb, format=image.format)
 
-        # Create thumbnail if necessary
-        thumb = None
-        if thumb_size:
-            logger.debug('Creating thumbnail {}'.format(thumb_size))
-            image = Image.open(temp_name)
-            image.thumbnail(thumb_size, Image.ANTIALIAS)
-            thumb = BytesIO()
-            image.save(thumb, format=image.format)
-
-        # Upload images to S3
-        file_key = unique_key + ext
-        logging.debug('Uploading image')
-        image_url = bucket.upload_from_filename(file_key, temp_name, metadata)
+    # Upload images to S3
+    file_key = unique_key + ext
+    logging.debug('Uploading image')
+    image_url = bucket.upload_from_filename(file_key, filename, metadata)
 
     thumb_url = None
     if thumb:
--- a/user_photos/forms.py	Tue Sep 01 20:33:40 2015 -0500
+++ b/user_photos/forms.py	Wed Sep 02 20:50:08 2015 -0500
@@ -1,12 +1,14 @@
 """Forms for the user_photos application."""
 import datetime
 import hashlib
+import os.path
 
 from django import forms
 from django.conf import settings
 
 from core.s3 import S3Bucket
 from core.image_uploader import upload
+from core.functions import TemporaryFile
 from core.services import get_redis_connection
 from user_photos.models import Photo
 
@@ -70,14 +72,29 @@
                           base_url=settings.USER_PHOTOS_BASE_URL,
                           bucket_name=settings.USER_PHOTOS_BUCKET)
 
-        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-        metadata = {'user': self.user.username, 'date': now}
+        # Trying to use PIL (or Pillow) on a Django UploadedFile is often
+        # problematic because the file is often an in-memory file if it is under
+        # a certain size. This complicates matters and many of the operations we try
+        # to perform on it fail if this is the case. To get around these issues,
+        # we make a copy of the file on the file system and operate on the copy.
 
-        url, thumb_url = upload(fp=self.cleaned_data['image_file'],
-                                bucket=bucket,
-                                metadata=metadata,
-                                new_size=settings.USER_PHOTOS_MAX_SIZE,
-                                thumb_size=settings.USER_PHOTOS_THUMB_SIZE)
+        fp = self.cleaned_data['image_file']
+        ext = os.path.splitext(fp.name)[1]
+
+        # Write the UploadedFile to a temporary file on disk
+        with TemporaryFile(suffix=ext) as t:
+            for chunk in fp.chunks():
+                t.file.write(chunk)
+            t.file.close()
+
+            now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+            metadata = {'user': self.user.username, 'date': now}
+
+            url, thumb_url = upload(filename=t.filename,
+                                    bucket=bucket,
+                                    metadata=metadata,
+                                    new_size=settings.USER_PHOTOS_MAX_SIZE,
+                                    thumb_size=settings.USER_PHOTOS_THUMB_SIZE)
 
         photo = Photo(user=self.user, url=url, thumb_url=thumb_url,
                 signature=signature)