Mercurial > public > sg101
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)