annotate core/image_uploader.py @ 887:9a15f7c27526

Actually save model object upon change. This commit was tested on the comments model. Additional logging added. Added check for Markdown image references. Added TODOs after observing behavior on comments.
author Brian Neal <bgneal@gmail.com>
date Tue, 03 Feb 2015 21:09:44 -0600
parents ee47122d6277
children 51a2051588f5
rev   line source
bgneal@700 1 """This module contains a function to upload an image file to a S3Bucket.
bgneal@696 2
bgneal@700 3 The image can be resized and a thumbnail can be generated and uploaded as well.
bgneal@696 4
bgneal@696 5 """
bgneal@885 6 from base64 import b64encode
bgneal@696 7 import logging
bgneal@696 8 from io import BytesIO
bgneal@696 9 import os.path
bgneal@700 10 import tempfile
bgneal@696 11 import uuid
bgneal@696 12
bgneal@696 13 from PIL import Image
bgneal@696 14
bgneal@700 15 from core.functions import temp_open
bgneal@837 16 from core.image import orient_image
bgneal@700 17
bgneal@696 18
bgneal@696 19 logger = logging.getLogger(__name__)
bgneal@696 20
bgneal@696 21
bgneal@885 22 def make_key():
bgneal@885 23 """Generate a random key suitable for a filename"""
bgneal@885 24 return b64encode(uuid.uuid4().bytes, '-_').rstrip('=')
bgneal@885 25
bgneal@885 26
bgneal@700 27 def upload(fp, bucket, metadata=None, new_size=None, thumb_size=None):
bgneal@700 28 """Upload an image file to a given S3Bucket.
bgneal@696 29
bgneal@700 30 The image can optionally be resized and a thumbnail can be generated and
bgneal@700 31 uploaded as well.
bgneal@696 32
bgneal@700 33 Parameters:
bgneal@700 34 fp - The image file to process. This is expected to be an instance of
bgneal@700 35 Django's UploadedFile class. The file must have a name attribute and
bgneal@700 36 we expect the name to have an extension. This is extension is
bgneal@700 37 used for the uploaded image and thumbnail names.
bgneal@700 38 bucket - A core.s3.S3Bucket instance to upload to.
bgneal@700 39 metadata - If not None, must be a dictionary of metadata to apply to the
bgneal@700 40 uploaded file and thumbnail.
bgneal@700 41 new_size - If not None, the image will be resized to the dimensions
bgneal@700 42 specified by new_size, which must be a (width, height) tuple.
bgneal@700 43 thumb_size - If not None, a thumbnail image will be created with the
bgneal@700 44 dimensions specified by thumb_size, which must be a (width,
bgneal@700 45 height) tuple. The thumbnail will use the same metadata, if
bgneal@700 46 present, as the image. The thumbnail filename will be the
bgneal@700 47 same basename as the image with a 't' appended. The
bgneal@700 48 extension will be the same as the original image.
bgneal@700 49
bgneal@700 50 A tuple is returned: (image_url, thumb_url) where thumb_url will be None if
bgneal@700 51 a thumbnail was not requested.
bgneal@696 52 """
bgneal@696 53
bgneal@738 54 filename = fp.name
bgneal@737 55 logger.info('Processing image file: %s', filename)
bgneal@700 56
bgneal@700 57 # Trying to use PIL (or Pillow) on a Django UploadedFile is often
bgneal@700 58 # problematic because the file is often an in-memory file if it is under
bgneal@700 59 # a certain size. This complicates matters and many of the operations we try
bgneal@700 60 # to perform on it fail if this is the case. To get around these issues,
bgneal@700 61 # we make a copy of the file on the file system and operate on the copy.
bgneal@700 62 # First generate a unique name and temporary file path.
bgneal@885 63 unique_key = make_key()
bgneal@700 64 ext = os.path.splitext(fp.name)[1]
bgneal@700 65 temp_name = os.path.join(tempfile.gettempdir(), unique_key + ext)
bgneal@696 66
bgneal@700 67 # Write the UploadedFile to a temporary file on disk
bgneal@700 68 with temp_open(temp_name, 'wb') as temp_file:
bgneal@700 69 for chunk in fp.chunks():
bgneal@700 70 temp_file.write(chunk)
bgneal@700 71 temp_file.close()
bgneal@696 72
bgneal@837 73 # Re-orient if necessary
bgneal@837 74 image = Image.open(temp_name)
bgneal@837 75 changed, image = orient_image(image)
bgneal@837 76 if changed:
bgneal@837 77 image.save(temp_name)
bgneal@837 78
bgneal@700 79 # Resize image if necessary
bgneal@700 80 if new_size:
bgneal@700 81 image = Image.open(temp_name)
bgneal@700 82 if image.size > new_size:
bgneal@700 83 logger.debug('Resizing from {} to {}'.format(image.size, new_size))
bgneal@700 84 image.thumbnail(new_size, Image.ANTIALIAS)
bgneal@700 85 image.save(temp_name)
bgneal@696 86
bgneal@700 87 # Create thumbnail if necessary
bgneal@700 88 thumb = None
bgneal@700 89 if thumb_size:
bgneal@700 90 logger.debug('Creating thumbnail {}'.format(thumb_size))
bgneal@700 91 image = Image.open(temp_name)
bgneal@700 92 image.thumbnail(thumb_size, Image.ANTIALIAS)
bgneal@700 93 thumb = BytesIO()
bgneal@700 94 image.save(thumb, format=image.format)
bgneal@696 95
bgneal@700 96 # Upload images to S3
bgneal@700 97 file_key = unique_key + ext
bgneal@700 98 logging.debug('Uploading image')
bgneal@700 99 image_url = bucket.upload_from_filename(file_key, temp_name, metadata)
bgneal@696 100
bgneal@700 101 thumb_url = None
bgneal@700 102 if thumb:
bgneal@700 103 logging.debug('Uploading thumbnail')
bgneal@700 104 thumb_key = '{}t{}'.format(unique_key, ext)
bgneal@700 105 thumb_url = bucket.upload_from_string(thumb_key,
bgneal@700 106 thumb.getvalue(),
bgneal@700 107 metadata)
bgneal@696 108
bgneal@737 109 logger.info('Completed processing image file: %s', filename)
bgneal@696 110
bgneal@700 111 return (image_url, thumb_url)