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@1195
|
7 import datetime
|
bgneal@696
|
8 import logging
|
bgneal@696
|
9 from io import BytesIO
|
bgneal@1196
|
10 import os
|
bgneal@696
|
11 import os.path
|
bgneal@1195
|
12 import shutil
|
bgneal@696
|
13 import uuid
|
bgneal@696
|
14
|
bgneal@1195
|
15 from django.conf import settings
|
bgneal@1195
|
16 from django.contrib.sites.models import Site
|
bgneal@696
|
17 from PIL import Image
|
bgneal@696
|
18
|
bgneal@971
|
19 from .utils import orient_image
|
bgneal@1195
|
20 from core.s3 import S3Bucket
|
bgneal@700
|
21
|
bgneal@696
|
22
|
bgneal@696
|
23 logger = logging.getLogger(__name__)
|
bgneal@696
|
24
|
bgneal@696
|
25
|
bgneal@1195
|
26 def process_upload(user, filename):
|
bgneal@1195
|
27 if settings.USER_PHOTOS_LOCAL_UPLOAD:
|
bgneal@1195
|
28 url, thumb_url = _process_local_upload(
|
bgneal@1195
|
29 filename,
|
bgneal@1195
|
30 new_size=settings.USER_PHOTOS_MAX_SIZE,
|
bgneal@1195
|
31 thumb_size=settings.USER_PHOTOS_THUMB_SIZE)
|
bgneal@1195
|
32 else:
|
bgneal@1195
|
33 now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
bgneal@1195
|
34 metadata = {'user': user.username, 'date': now}
|
bgneal@1195
|
35
|
bgneal@1195
|
36 bucket = S3Bucket(access_key=settings.USER_PHOTOS_ACCESS_KEY,
|
bgneal@1195
|
37 secret_key=settings.USER_PHOTOS_SECRET_KEY,
|
bgneal@1195
|
38 base_url=settings.USER_PHOTOS_BASE_URL,
|
bgneal@1195
|
39 bucket_name=settings.USER_PHOTOS_BUCKET)
|
bgneal@1195
|
40
|
bgneal@1195
|
41 url, thumb_url = upload(filename,
|
bgneal@1195
|
42 bucket=bucket,
|
bgneal@1195
|
43 metadata=metadata,
|
bgneal@1195
|
44 new_size=settings.USER_PHOTOS_MAX_SIZE,
|
bgneal@1195
|
45 thumb_size=settings.USER_PHOTOS_THUMB_SIZE)
|
bgneal@1195
|
46 return (url, thumb_url)
|
bgneal@1195
|
47
|
bgneal@1195
|
48
|
bgneal@885
|
49 def make_key():
|
bgneal@885
|
50 """Generate a random key suitable for a filename"""
|
bgneal@885
|
51 return b64encode(uuid.uuid4().bytes, '-_').rstrip('=')
|
bgneal@885
|
52
|
bgneal@885
|
53
|
bgneal@964
|
54 def upload(filename, bucket, metadata=None, new_size=None, thumb_size=None):
|
bgneal@700
|
55 """Upload an image file to a given S3Bucket.
|
bgneal@696
|
56
|
bgneal@700
|
57 The image can optionally be resized and a thumbnail can be generated and
|
bgneal@700
|
58 uploaded as well.
|
bgneal@696
|
59
|
bgneal@700
|
60 Parameters:
|
bgneal@964
|
61 filename - The path to the file to process. The filename should have an
|
bgneal@964
|
62 extension, and this is used for the uploaded image & thumbnail
|
bgneal@964
|
63 names.
|
bgneal@700
|
64 bucket - A core.s3.S3Bucket instance to upload to.
|
bgneal@700
|
65 metadata - If not None, must be a dictionary of metadata to apply to the
|
bgneal@700
|
66 uploaded file and thumbnail.
|
bgneal@700
|
67 new_size - If not None, the image will be resized to the dimensions
|
bgneal@700
|
68 specified by new_size, which must be a (width, height) tuple.
|
bgneal@700
|
69 thumb_size - If not None, a thumbnail image will be created with the
|
bgneal@700
|
70 dimensions specified by thumb_size, which must be a (width,
|
bgneal@700
|
71 height) tuple. The thumbnail will use the same metadata, if
|
bgneal@700
|
72 present, as the image. The thumbnail filename will be the
|
bgneal@700
|
73 same basename as the image with a 't' appended. The
|
bgneal@700
|
74 extension will be the same as the original image.
|
bgneal@700
|
75
|
bgneal@700
|
76 A tuple is returned: (image_url, thumb_url) where thumb_url will be None if
|
bgneal@700
|
77 a thumbnail was not requested.
|
bgneal@696
|
78 """
|
bgneal@696
|
79
|
bgneal@737
|
80 logger.info('Processing image file: %s', filename)
|
bgneal@700
|
81
|
bgneal@885
|
82 unique_key = make_key()
|
bgneal@964
|
83 ext = os.path.splitext(filename)[1]
|
bgneal@696
|
84
|
bgneal@964
|
85 # Re-orient if necessary
|
bgneal@964
|
86 image = Image.open(filename)
|
bgneal@964
|
87 changed, image = orient_image(image)
|
bgneal@964
|
88 if changed:
|
bgneal@964
|
89 image.save(filename)
|
bgneal@696
|
90
|
bgneal@964
|
91 # Resize image if necessary
|
bgneal@964
|
92 if new_size:
|
bgneal@964
|
93 image = Image.open(filename)
|
bgneal@964
|
94 if image.size > new_size:
|
bgneal@964
|
95 logger.debug('Resizing from {} to {}'.format(image.size, new_size))
|
bgneal@964
|
96 image.thumbnail(new_size, Image.ANTIALIAS)
|
bgneal@964
|
97 image.save(filename)
|
bgneal@837
|
98
|
bgneal@964
|
99 # Create thumbnail if necessary
|
bgneal@964
|
100 thumb = None
|
bgneal@964
|
101 if thumb_size:
|
bgneal@964
|
102 logger.debug('Creating thumbnail {}'.format(thumb_size))
|
bgneal@964
|
103 image = Image.open(filename)
|
bgneal@964
|
104 image.thumbnail(thumb_size, Image.ANTIALIAS)
|
bgneal@964
|
105 thumb = BytesIO()
|
bgneal@964
|
106 image.save(thumb, format=image.format)
|
bgneal@696
|
107
|
bgneal@964
|
108 # Upload images to S3
|
bgneal@964
|
109 file_key = unique_key + ext
|
bgneal@964
|
110 logging.debug('Uploading image')
|
bgneal@964
|
111 image_url = bucket.upload_from_filename(file_key, filename, metadata)
|
bgneal@696
|
112
|
bgneal@700
|
113 thumb_url = None
|
bgneal@700
|
114 if thumb:
|
bgneal@700
|
115 logging.debug('Uploading thumbnail')
|
bgneal@700
|
116 thumb_key = '{}t{}'.format(unique_key, ext)
|
bgneal@700
|
117 thumb_url = bucket.upload_from_string(thumb_key,
|
bgneal@700
|
118 thumb.getvalue(),
|
bgneal@700
|
119 metadata)
|
bgneal@696
|
120
|
bgneal@737
|
121 logger.info('Completed processing image file: %s', filename)
|
bgneal@696
|
122
|
bgneal@700
|
123 return (image_url, thumb_url)
|
bgneal@1195
|
124
|
bgneal@1195
|
125
|
bgneal@1195
|
126 def _process_local_upload(filename, new_size=None, thumb_size=None):
|
bgneal@1195
|
127 # Re-orient if necessary
|
bgneal@1195
|
128 image = Image.open(filename)
|
bgneal@1195
|
129 changed, image = orient_image(image)
|
bgneal@1195
|
130 if changed:
|
bgneal@1195
|
131 image.save(filename)
|
bgneal@1195
|
132
|
bgneal@1195
|
133 # Resize image if necessary
|
bgneal@1195
|
134 if new_size:
|
bgneal@1195
|
135 image = Image.open(filename)
|
bgneal@1195
|
136 if image.size > new_size:
|
bgneal@1195
|
137 logger.debug('Resizing from {} to {}'.format(image.size, new_size))
|
bgneal@1195
|
138 image.thumbnail(new_size, Image.ANTIALIAS)
|
bgneal@1195
|
139 image.save(filename)
|
bgneal@1195
|
140
|
bgneal@1195
|
141 # Create thumbnail if necessary
|
bgneal@1195
|
142 thumb = None
|
bgneal@1195
|
143 if thumb_size:
|
bgneal@1195
|
144 logger.debug('Creating thumbnail {}'.format(thumb_size))
|
bgneal@1195
|
145 image = Image.open(filename)
|
bgneal@1195
|
146 image.thumbnail(thumb_size, Image.ANTIALIAS)
|
bgneal@1195
|
147 thumb = BytesIO()
|
bgneal@1195
|
148 image.save(thumb, format=image.format)
|
bgneal@1195
|
149
|
bgneal@1195
|
150 # Copy file and thumbnail to our user upload files directory.
|
bgneal@1195
|
151 upload_dir = os.path.join(settings.MEDIA_ROOT,
|
bgneal@1195
|
152 settings.USER_PHOTOS_LOCAL_UPLOAD_DIR)
|
bgneal@1195
|
153 unique_name = uuid.uuid4().hex
|
bgneal@1195
|
154 ext = os.path.splitext(filename)[1]
|
bgneal@1195
|
155 image_name = '%s%s' % (unique_name, ext)
|
bgneal@1195
|
156 thumb_name = '%st%s' % (unique_name, ext)
|
bgneal@1195
|
157 image_path = os.path.join(upload_dir, image_name)
|
bgneal@1195
|
158 thumb_path = os.path.join(upload_dir, thumb_name)
|
bgneal@1196
|
159 perms = 0644
|
bgneal@1195
|
160
|
bgneal@1195
|
161 shutil.copy(filename, image_path)
|
bgneal@1196
|
162 os.chmod(image_path, perms)
|
bgneal@1196
|
163
|
bgneal@1195
|
164 if thumb:
|
bgneal@1196
|
165 thumb.seek(0)
|
bgneal@1195
|
166 shutil.copyfileobj(thumb, thumb_path)
|
bgneal@1196
|
167 os.chmod(thumb_path, perms)
|
bgneal@1195
|
168
|
bgneal@1195
|
169 # Generate URLs for the image and thumb.
|
bgneal@1196
|
170 image_url = _user_photo_url(image_name)
|
bgneal@1196
|
171 thumb_url = _user_photo_url(thumb_name)
|
bgneal@1196
|
172
|
bgneal@1196
|
173 return (image_url, thumb_url)
|
bgneal@1196
|
174
|
bgneal@1196
|
175
|
bgneal@1196
|
176 def _user_photo_url(filename):
|
bgneal@1196
|
177 url_pattern = '%s://%s%s%s/%s'
|
bgneal@1195
|
178 site = Site.objects.get_current()
|
bgneal@1195
|
179
|
bgneal@1195
|
180 image_url = url_pattern % (
|
bgneal@1195
|
181 settings.SITE_SCHEME,
|
bgneal@1195
|
182 site.domain,
|
bgneal@1195
|
183 settings.MEDIA_URL,
|
bgneal@1195
|
184 settings.USER_PHOTOS_LOCAL_UPLOAD_DIR,
|
bgneal@1196
|
185 filename)
|