# HG changeset patch
# User Brian Neal
# Date 1442971430 18000
# Node ID 4f265f61874be458914340dd5c38906c5ed9948c
# Parent bd594bcba5eb3f3879b2fb20682e9557fcec92e3
Hotlink image form is functioning.
The user can now submit a URL via a form and the URL will be downloaded
and uploaded to a S3 bucket if it is an image.
Tests to follow.
diff -r bd594bcba5eb -r 4f265f61874b bio/forms.py
--- a/bio/forms.py Sun Sep 13 14:51:33 2015 -0500
+++ b/bio/forms.py Tue Sep 22 20:23:50 2015 -0500
@@ -15,7 +15,7 @@
from bio.models import UserProfile
from core.widgets import AutoCompleteUserInput
-from core.image import parse_image, downscale_image_square
+from core.images.utils import parse_image, downscale_image_square
class EditUserForm(forms.ModelForm):
diff -r bd594bcba5eb -r 4f265f61874b core/image.py
--- a/core/image.py Sun Sep 13 14:51:33 2015 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-"""
-This file contains common utility functions for manipulating images for
-the rest of the applications in the project.
-"""
-from PIL import ImageFile
-from PIL import Image
-
-
-def parse_image(file):
- """
- Returns a PIL Image from the supplied Django file object.
- Throws IOError if the file does not parse as an image file or some other
- I/O error occurred.
-
- """
- parser = ImageFile.Parser()
- for chunk in file.chunks():
- parser.feed(chunk)
- image = parser.close()
- return image
-
-
-def downscale_image_square(image, size):
- """
- Scale an image to the square dimensions given by size (in pixels).
- The new image is returned.
- If the image is already smaller than (size, size) then no scaling
- is performed and the image is returned unchanged.
-
- """
- # don't upscale
- if (size, size) >= image.size:
- return image
-
- (w, h) = image.size
- if w > h:
- diff = (w - h) / 2
- image = image.crop((diff, 0, w - diff, h))
- elif h > w:
- diff = (h - w) / 2
- image = image.crop((0, diff, w, h - diff))
- image = image.resize((size, size), Image.ANTIALIAS)
- return image
-
-
-# Various image transformation functions:
-def flip_horizontal(im):
- return im.transpose(Image.FLIP_LEFT_RIGHT)
-
-def flip_vertical(im):
- return im.transpose(Image.FLIP_TOP_BOTTOM)
-
-def rotate_180(im):
- return im.transpose(Image.ROTATE_180)
-
-def rotate_90(im):
- return im.transpose(Image.ROTATE_90)
-
-def rotate_270(im):
- return im.transpose(Image.ROTATE_270)
-
-def transpose(im):
- return rotate_90(flip_horizontal(im))
-
-def transverse(im):
- return rotate_90(flip_vertical(im))
-
-# From http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
-# EXIF Orientation tag values:
-# 1 = Horizontal (normal)
-# 2 = Mirror horizontal
-# 3 = Rotate 180
-# 4 = Mirror vertical
-# 5 = Mirror horizontal and rotate 270 CW
-# 6 = Rotate 90 CW
-# 7 = Mirror horizontal and rotate 90 CW
-# 8 = Rotate 270 CW
-
-ORIENT_FUNCS = {
- 2: flip_horizontal,
- 3: rotate_180,
- 4: flip_vertical,
- 5: transpose,
- 6: rotate_270,
- 7: transverse,
- 8: rotate_90,
-}
-
-ORIENT_TAG = 0x112
-
-
-def orient_image(im):
- """Transforms the given image according to embedded EXIF data.
-
- The image instance, im, should be a PIL Image.
- If there is EXIF information for the image, and the orientation tag
- indicates that the image should be transformed, perform the transformation.
-
- Returns a tuple of the form (flag, image) where flag is True if the image
- was oriented and False otherwise. image is either a new transformed image or
- the original image instance.
-
- """
- if hasattr(im, '_getexif'):
- try:
- exif = im._getexif()
- except IndexError:
- # Work around issue seen in Pillow
- # https://github.com/python-pillow/Pillow/issues/518
- exif = None
-
- if exif and ORIENT_TAG in exif:
- orientation = exif[ORIENT_TAG]
- func = ORIENT_FUNCS.get(orientation)
- if func:
- return (True, func(im))
-
- return (False, im)
diff -r bd594bcba5eb -r 4f265f61874b core/image_uploader.py
--- a/core/image_uploader.py Sun Sep 13 14:51:33 2015 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-"""This module contains a function to upload an image file to a S3Bucket.
-
-The image can be resized and a thumbnail can be generated and uploaded as well.
-
-"""
-from base64 import b64encode
-import logging
-from io import BytesIO
-import os.path
-import uuid
-
-from PIL import Image
-
-from core.image import orient_image
-
-
-logger = logging.getLogger(__name__)
-
-
-def make_key():
- """Generate a random key suitable for a filename"""
- return b64encode(uuid.uuid4().bytes, '-_').rstrip('=')
-
-
-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:
- 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.
- new_size - If not None, the image will be resized to the dimensions
- specified by new_size, which must be a (width, height) tuple.
- thumb_size - If not None, a thumbnail image will be created with the
- dimensions specified by thumb_size, which must be a (width,
- height) tuple. The thumbnail will use the same metadata, if
- present, as the image. The thumbnail filename will be the
- same basename as the image with a 't' appended. The
- extension will be the same as the original image.
-
- A tuple is returned: (image_url, thumb_url) where thumb_url will be None if
- a thumbnail was not requested.
- """
-
- logger.info('Processing image file: %s', filename)
-
- unique_key = make_key()
- ext = os.path.splitext(filename)[1]
-
- # Re-orient if necessary
- image = Image.open(filename)
- changed, image = orient_image(image)
- if changed:
- image.save(filename)
-
- # 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)
-
- # 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)
-
- # 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:
- logging.debug('Uploading thumbnail')
- thumb_key = '{}t{}'.format(unique_key, ext)
- thumb_url = bucket.upload_from_string(thumb_key,
- thumb.getvalue(),
- metadata)
-
- logger.info('Completed processing image file: %s', filename)
-
- return (image_url, thumb_url)
diff -r bd594bcba5eb -r 4f265f61874b core/images/upload.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/core/images/upload.py Tue Sep 22 20:23:50 2015 -0500
@@ -0,0 +1,94 @@
+"""This module contains a function to upload an image file to a S3Bucket.
+
+The image can be resized and a thumbnail can be generated and uploaded as well.
+
+"""
+from base64 import b64encode
+import logging
+from io import BytesIO
+import os.path
+import uuid
+
+from PIL import Image
+
+from .utils import orient_image
+
+
+logger = logging.getLogger(__name__)
+
+
+def make_key():
+ """Generate a random key suitable for a filename"""
+ return b64encode(uuid.uuid4().bytes, '-_').rstrip('=')
+
+
+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:
+ 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.
+ new_size - If not None, the image will be resized to the dimensions
+ specified by new_size, which must be a (width, height) tuple.
+ thumb_size - If not None, a thumbnail image will be created with the
+ dimensions specified by thumb_size, which must be a (width,
+ height) tuple. The thumbnail will use the same metadata, if
+ present, as the image. The thumbnail filename will be the
+ same basename as the image with a 't' appended. The
+ extension will be the same as the original image.
+
+ A tuple is returned: (image_url, thumb_url) where thumb_url will be None if
+ a thumbnail was not requested.
+ """
+
+ logger.info('Processing image file: %s', filename)
+
+ unique_key = make_key()
+ ext = os.path.splitext(filename)[1]
+
+ # Re-orient if necessary
+ image = Image.open(filename)
+ changed, image = orient_image(image)
+ if changed:
+ image.save(filename)
+
+ # 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)
+
+ # 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)
+
+ # 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:
+ logging.debug('Uploading thumbnail')
+ thumb_key = '{}t{}'.format(unique_key, ext)
+ thumb_url = bucket.upload_from_string(thumb_key,
+ thumb.getvalue(),
+ metadata)
+
+ logger.info('Completed processing image file: %s', filename)
+
+ return (image_url, thumb_url)
diff -r bd594bcba5eb -r 4f265f61874b core/images/utils.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/core/images/utils.py Tue Sep 22 20:23:50 2015 -0500
@@ -0,0 +1,118 @@
+"""
+This file contains common utility functions for manipulating images for
+the rest of the applications in the project.
+"""
+from PIL import ImageFile
+from PIL import Image
+
+
+def parse_image(file):
+ """
+ Returns a PIL Image from the supplied Django file object.
+ Throws IOError if the file does not parse as an image file or some other
+ I/O error occurred.
+
+ """
+ parser = ImageFile.Parser()
+ for chunk in file.chunks():
+ parser.feed(chunk)
+ image = parser.close()
+ return image
+
+
+def downscale_image_square(image, size):
+ """
+ Scale an image to the square dimensions given by size (in pixels).
+ The new image is returned.
+ If the image is already smaller than (size, size) then no scaling
+ is performed and the image is returned unchanged.
+
+ """
+ # don't upscale
+ if (size, size) >= image.size:
+ return image
+
+ (w, h) = image.size
+ if w > h:
+ diff = (w - h) / 2
+ image = image.crop((diff, 0, w - diff, h))
+ elif h > w:
+ diff = (h - w) / 2
+ image = image.crop((0, diff, w, h - diff))
+ image = image.resize((size, size), Image.ANTIALIAS)
+ return image
+
+
+# Various image transformation functions:
+def flip_horizontal(im):
+ return im.transpose(Image.FLIP_LEFT_RIGHT)
+
+def flip_vertical(im):
+ return im.transpose(Image.FLIP_TOP_BOTTOM)
+
+def rotate_180(im):
+ return im.transpose(Image.ROTATE_180)
+
+def rotate_90(im):
+ return im.transpose(Image.ROTATE_90)
+
+def rotate_270(im):
+ return im.transpose(Image.ROTATE_270)
+
+def transpose(im):
+ return rotate_90(flip_horizontal(im))
+
+def transverse(im):
+ return rotate_90(flip_vertical(im))
+
+# From http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
+# EXIF Orientation tag values:
+# 1 = Horizontal (normal)
+# 2 = Mirror horizontal
+# 3 = Rotate 180
+# 4 = Mirror vertical
+# 5 = Mirror horizontal and rotate 270 CW
+# 6 = Rotate 90 CW
+# 7 = Mirror horizontal and rotate 90 CW
+# 8 = Rotate 270 CW
+
+ORIENT_FUNCS = {
+ 2: flip_horizontal,
+ 3: rotate_180,
+ 4: flip_vertical,
+ 5: transpose,
+ 6: rotate_270,
+ 7: transverse,
+ 8: rotate_90,
+}
+
+ORIENT_TAG = 0x112
+
+
+def orient_image(im):
+ """Transforms the given image according to embedded EXIF data.
+
+ The image instance, im, should be a PIL Image.
+ If there is EXIF information for the image, and the orientation tag
+ indicates that the image should be transformed, perform the transformation.
+
+ Returns a tuple of the form (flag, image) where flag is True if the image
+ was oriented and False otherwise. image is either a new transformed image or
+ the original image instance.
+
+ """
+ if hasattr(im, '_getexif'):
+ try:
+ exif = im._getexif()
+ except IndexError:
+ # Work around issue seen in Pillow
+ # https://github.com/python-pillow/Pillow/issues/518
+ exif = None
+
+ if exif and ORIENT_TAG in exif:
+ orientation = exif[ORIENT_TAG]
+ func = ORIENT_FUNCS.get(orientation)
+ if func:
+ return (True, func(im))
+
+ return (False, im)
diff -r bd594bcba5eb -r 4f265f61874b core/tests/test_html.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/core/tests/test_html.py Tue Sep 22 20:23:50 2015 -0500
@@ -0,0 +1,68 @@
+"""Tests for the core.html module."""
+import unittest
+
+from core.html import ImageCheckError
+from core.html import image_check
+
+
+TEST_HTML = """
+
Posters and Facebook events are starting to come in...