Ticket #4115: contrib.thumbnails.patch
File contrib.thumbnails.patch, 27.3 KB (added by , 18 years ago) |
---|
-
django/contrib/thumbnails/base.py
1 from django.conf import settings 2 import os 3 from StringIO import StringIO 4 from PIL import Image 5 from exceptions import ThumbnailNoData, ThumbnailInvalidImage 6 from methods import scale 7 8 __all__ = ('Thumbnail',) 9 10 class Thumbnail(object): 11 method = scale 12 base_url = settings.MEDIA_URL 13 root = settings.MEDIA_ROOT 14 15 def __init__(self, filename='', data=None, overwrite=False, size=None): 16 self._data = data 17 if size: 18 self.size = size 19 self.filename = filename 20 self.overwrite = overwrite 21 if data: 22 # If data was given and the thumbnail does not already exist, 23 # generate thumbnail image now. 24 self.make_thumbnail(data) 25 26 def get_filename(self): 27 return self._filename 28 def set_filename(self, filename): 29 if filename: 30 filename = filename % {'x': self.size[0], 'y': self.size[1], 'method': self.method.__name__} 31 filename = os.path.normpath(filename).lstrip(os.sep) 32 if os.path.splitext(filename)[1] != '.jpg': 33 filename.append('.jpg') 34 filename = os.path.join(self.root, filename) 35 self._filename = filename 36 filename = property(get_filename, set_filename) 37 38 def get_thumbnail(self): 39 if hasattr(self, '_thumbnail'): 40 return self._thumbnail 41 try: 42 img = Image.open(self.filename) 43 except IOError, msg: 44 raise ThumbnailInvalidImage(msg) 45 self._thumbnail = img 46 return img 47 thumbnail = property(get_thumbnail) 48 49 def make_thumbnail(self, data): 50 if self.overwrite or not os.path.isfile(self.filename): 51 try: 52 original = Image.open(StringIO(data)) 53 except IOError, msg: 54 raise ThumbnailInvalidImage(msg) 55 self._original_image = original 56 thumbnail = self.method() 57 self._thumbnail = thumbnail 58 if self.filename: 59 thumbnail.save(self.filename, "JPEG") 60 61 def get_url(self): 62 if hasattr(self, '_url'): 63 return self._url 64 filename = self.filename 65 if not os.path.isfile(filename): 66 raise ThumbnailNoData 67 url = os.path.normpath(filename[len(self.root):]).lstrip(os.sep) 68 url = os.path.join(self.base_url, url) 69 if os.sep != '/': 70 url = url.replace(os.sep, '/') 71 self._url = url 72 return url 73 url = property(get_url) 74 75 # Make the object will output it's url to Django templates. 76 __str__ = get_url 77 78 def get_original_image(self): 79 if hasattr(self, '_original_image'): 80 return self._original_image 81 raise ThumbnailNoData 82 original_image = property(get_original_image) 83 84 def delete(self): 85 if os.path.isfile(self.filename): 86 os.remove(self.filename) 87 self.filename = '' -
django/contrib/thumbnails/__init__.py
1 from base import * 2 from exceptions import * 3 from methods import * 4 No newline at end of file -
django/contrib/thumbnails/exceptions.py
1 class ThumbnailException(Exception): 2 pass 3 4 class ThumbnailNoData(ThumbnailException): 5 pass 6 7 class ThumbnailTooSmall(ThumbnailException): 8 pass 9 10 class ThumbnailInvalidImage(ThumbnailException): 11 pass -
django/contrib/thumbnails/methods.py
1 from PIL import Image 2 from exceptions import ThumbnailTooSmall 3 4 5 def scale(thumbnail): 6 """ Normal PIL thumbnail """ 7 img = thumbnail.original_image 8 size = thumbnail.size 9 if img.size[0] < size[0] and img.size[1] < size[1]: 10 raise ThumbnailTooSmall('Image should be at least %s wide or %s high' % size) 11 img.thumbnail(size, Image.ANTIALIAS) 12 return img 13 14 15 def crop(thumbnail): 16 """ Crop the image down to the same ratio as `size` """ 17 img = thumbnail.original_image 18 size = thumbnail.size 19 20 if img.size[0] < size[0] or img.size[1] < size[1]: 21 raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size) 22 23 image_x, image_y = img.size 24 25 crop_ratio = size[0] / float(size[1]) 26 image_ratio = image_x / float(image_y) 27 28 if crop_ratio < image_ratio: 29 # x needs to shrink 30 top = 0 31 bottom = image_y 32 crop_width = int(image_y * crop_ratio) 33 left = (image_x - crop_width) // 2 34 right = left + crop_width 35 else: 36 # y needs to shrink 37 left = 0 38 right = image_x 39 crop_height = int(image_x * crop_ratio) 40 top = (image_y - crop_height) // 2 41 bottom = top + crop_height 42 43 img = img.crop((left, top, right, bottom)) 44 return img.resize(size, Image.ANTIALIAS) 45 46 47 def squash(thumbnail): 48 """ Resize the image down to exactly `size` (changes ratio) """ 49 img = thumbnail.original_image 50 size = thumbnail.size 51 if img.size[0] < size[0] or img.size[1] < size[1]: 52 raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size) 53 return img.resize(size, Image.ANTIALIAS) -
django/contrib/thumbnails/__init__.py
1 from base import * 2 from exceptions import * 3 from methods import * 4 No newline at end of file -
django/contrib/thumbnails/base.py
1 from django.conf import settings 2 import os 3 from StringIO import StringIO 4 from PIL import Image 5 from exceptions import ThumbnailNoData, ThumbnailInvalidImage 6 from methods import scale 7 8 __all__ = ('Thumbnail',) 9 10 class Thumbnail(object): 11 method = scale 12 base_url = settings.MEDIA_URL 13 root = settings.MEDIA_ROOT 14 15 def __init__(self, filename='', data=None, overwrite=False, size=None): 16 self._data = data 17 if size: 18 self.size = size 19 self.filename = filename 20 self.overwrite = overwrite 21 if data: 22 # If data was given and the thumbnail does not already exist, 23 # generate thumbnail image now. 24 self.make_thumbnail(data) 25 26 def get_filename(self): 27 return self._filename 28 def set_filename(self, filename): 29 if filename: 30 filename = filename % {'x': self.size[0], 'y': self.size[1], 'method': self.method.__name__} 31 filename = os.path.normpath(filename).lstrip(os.sep) 32 if os.path.splitext(filename)[1] != '.jpg': 33 filename.append('.jpg') 34 filename = os.path.join(self.root, filename) 35 self._filename = filename 36 filename = property(get_filename, set_filename) 37 38 def get_thumbnail(self): 39 if hasattr(self, '_thumbnail'): 40 return self._thumbnail 41 try: 42 img = Image.open(self.filename) 43 except IOError, msg: 44 raise ThumbnailInvalidImage(msg) 45 self._thumbnail = img 46 return img 47 thumbnail = property(get_thumbnail) 48 49 def make_thumbnail(self, data): 50 if self.overwrite or not os.path.isfile(self.filename): 51 try: 52 original = Image.open(StringIO(data)) 53 except IOError, msg: 54 raise ThumbnailInvalidImage(msg) 55 self._original_image = original 56 thumbnail = self.method() 57 self._thumbnail = thumbnail 58 if self.filename: 59 thumbnail.save(self.filename, "JPEG") 60 61 def get_url(self): 62 if hasattr(self, '_url'): 63 return self._url 64 filename = self.filename 65 if not os.path.isfile(filename): 66 raise ThumbnailNoData 67 url = os.path.normpath(filename[len(self.root):]).lstrip(os.sep) 68 url = os.path.join(self.base_url, url) 69 if os.sep != '/': 70 url = url.replace(os.sep, '/') 71 self._url = url 72 return url 73 url = property(get_url) 74 75 # Make the object will output it's url to Django templates. 76 __str__ = get_url 77 78 def get_original_image(self): 79 if hasattr(self, '_original_image'): 80 return self._original_image 81 raise ThumbnailNoData 82 original_image = property(get_original_image) 83 84 def delete(self): 85 if os.path.isfile(self.filename): 86 os.remove(self.filename) 87 self.filename = '' -
django/contrib/thumbnails/exceptions.py
1 class ThumbnailException(Exception): 2 pass 3 4 class ThumbnailNoData(ThumbnailException): 5 pass 6 7 class ThumbnailTooSmall(ThumbnailException): 8 pass 9 10 class ThumbnailInvalidImage(ThumbnailException): 11 pass -
django/contrib/thumbnails/methods.py
1 from PIL import Image 2 from exceptions import ThumbnailTooSmall 3 4 5 def scale(thumbnail): 6 """ Normal PIL thumbnail """ 7 img = thumbnail.original_image 8 size = thumbnail.size 9 if img.size[0] < size[0] and img.size[1] < size[1]: 10 raise ThumbnailTooSmall('Image should be at least %s wide or %s high' % size) 11 img.thumbnail(size, Image.ANTIALIAS) 12 return img 13 14 15 def crop(thumbnail): 16 """ Crop the image down to the same ratio as `size` """ 17 img = thumbnail.original_image 18 size = thumbnail.size 19 20 if img.size[0] < size[0] or img.size[1] < size[1]: 21 raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size) 22 23 image_x, image_y = img.size 24 25 crop_ratio = size[0] / float(size[1]) 26 image_ratio = image_x / float(image_y) 27 28 if crop_ratio < image_ratio: 29 # x needs to shrink 30 top = 0 31 bottom = image_y 32 crop_width = int(image_y * crop_ratio) 33 left = (image_x - crop_width) // 2 34 right = left + crop_width 35 else: 36 # y needs to shrink 37 left = 0 38 right = image_x 39 crop_height = int(image_x * crop_ratio) 40 top = (image_y - crop_height) // 2 41 bottom = top + crop_height 42 43 img = img.crop((left, top, right, bottom)) 44 return img.resize(size, Image.ANTIALIAS) 45 46 47 def squash(thumbnail): 48 """ Resize the image down to exactly `size` (changes ratio) """ 49 img = thumbnail.original_image 50 size = thumbnail.size 51 if img.size[0] < size[0] or img.size[1] < size[1]: 52 raise ThumbnailTooSmall('Image must be at least %s wide and %s high' % size) 53 return img.resize(size, Image.ANTIALIAS) -
django/contrib/thumbnails/templatetags/thumbnails.py
1 from django.template import Library 2 from django.contrib.thumbnails import Thumbnail, crop, squash, ThumbnailException 3 from django.conf import settings 4 import os.path 5 6 register = Library() 7 8 class ThumbnailCrop(Thumbnail): 9 method = crop 10 11 class ThumbnailSquash(Thumbnail): 12 method = squash 13 14 15 #@register.filter 16 def thumbnail(file, size): 17 return create_thumbnail(Thumbnail, file, size) 18 register.filter(thumbnail) 19 20 21 #@register.filter 22 def thumbnail_crop(file, size): 23 return create_thumbnail(ThumbnailCrop, file, size) 24 register.filter(thumbnail_crop) 25 26 27 #@register.filter 28 def thumbnail_squash(file, size): 29 return create_thumbnail(ThumbnailSquash, file, size) 30 register.filter(thumbnail_squash) 31 32 33 def create_thumbnail(thumbnail_cls, file, size_string): 34 """ 35 Creates a thumbnail image for the file (which must exist on MEDIA_ROOT) 36 and returns a url to this image. 37 38 If the thumbnail image is not found, an empty string will be returned. 39 """ 40 # Define the size. 41 size = [int(x) for x in size_string.split('x')] 42 43 # Define the filename, then create the thumbnail object. 44 basename, ext = os.path.splitext(file) 45 thumbnail_filename = '%s_%s_%%(method)s%s' % (basename, size_string, ext) 46 original_filename = os.path.join(settings.MEDIA_ROOT, file) 47 48 # See if the thumbnail exists already (and is newer than the 49 # original filename). 50 try: 51 thumbnail = thumbnail_cls(thumbnail_filename, size=size) 52 if os.path.getmtime(original_filename) > os.path.getmtime(thumbnail.filename): 53 thumbnail.delete() 54 else: 55 return thumbnail.url 56 except (ThumbnailException, OSError): 57 # Couldn't get the thumbnail (or something else went wrong). 58 pass 59 60 # Read the original file from disk. 61 try: 62 data = open(original_filename, 'rb').read() 63 except OSError: 64 # Couldn't read the original file. 65 return '' 66 67 # Generate the thumbnail. 68 try: 69 thumbnail = thumbnail_cls(thumbnail_filename, data, size=size) 70 except ThumbnailException: 71 return '' 72 73 return thumbnail -
django/contrib/thumbnails/templatetags/thumbnails.py
1 from django.template import Library 2 from django.contrib.thumbnails import Thumbnail, crop, squash, ThumbnailException 3 from django.conf import settings 4 import os.path 5 6 register = Library() 7 8 class ThumbnailCrop(Thumbnail): 9 method = crop 10 11 class ThumbnailSquash(Thumbnail): 12 method = squash 13 14 15 #@register.filter 16 def thumbnail(file, size): 17 return create_thumbnail(Thumbnail, file, size) 18 register.filter(thumbnail) 19 20 21 #@register.filter 22 def thumbnail_crop(file, size): 23 return create_thumbnail(ThumbnailCrop, file, size) 24 register.filter(thumbnail_crop) 25 26 27 #@register.filter 28 def thumbnail_squash(file, size): 29 return create_thumbnail(ThumbnailSquash, file, size) 30 register.filter(thumbnail_squash) 31 32 33 def create_thumbnail(thumbnail_cls, file, size_string): 34 """ 35 Creates a thumbnail image for the file (which must exist on MEDIA_ROOT) 36 and returns a url to this image. 37 38 If the thumbnail image is not found, an empty string will be returned. 39 """ 40 # Define the size. 41 size = [int(x) for x in size_string.split('x')] 42 43 # Define the filename, then create the thumbnail object. 44 basename, ext = os.path.splitext(file) 45 thumbnail_filename = '%s_%s_%%(method)s%s' % (basename, size_string, ext) 46 original_filename = os.path.join(settings.MEDIA_ROOT, file) 47 48 # See if the thumbnail exists already (and is newer than the 49 # original filename). 50 try: 51 thumbnail = thumbnail_cls(thumbnail_filename, size=size) 52 if os.path.getmtime(original_filename) > os.path.getmtime(thumbnail.filename): 53 thumbnail.delete() 54 else: 55 return thumbnail.url 56 except (ThumbnailException, OSError): 57 # Couldn't get the thumbnail (or something else went wrong). 58 pass 59 60 # Read the original file from disk. 61 try: 62 data = open(original_filename, 'rb').read() 63 except OSError: 64 # Couldn't read the original file. 65 return '' 66 67 # Generate the thumbnail. 68 try: 69 thumbnail = thumbnail_cls(thumbnail_filename, data, size=size) 70 except ThumbnailException: 71 return '' 72 73 return thumbnail -
docs/thumbnails.txt
1 ========================= 2 django.contrib.thumbnails 3 ========================= 4 5 The ``django.contrib.thumbnails`` package, part of the `"django.contrib" add-ons`_, 6 provides a way of thumbnailing images. 7 8 It requires the Python Imaging Library (PIL_). 9 10 .. _"django.contrib" add-ons: ../add_ons/ 11 .. _PIL: http://www.pythonware.com/products/pil/ 12 13 Template filters 14 ================ 15 16 To use these template filters, add ``'django.contrib.thumbnails'`` to your 17 ``INSTALLED_APPS`` setting. Once you've done that, use 18 ``{% load thumbnails %}`` in a template to give your template access to the 19 filters. 20 21 The filters, all very similar in behaviour, are: 22 23 * ``thumbnail`` 24 * ``thumbnail_crop`` 25 * ``thumbnail_squash`` 26 27 The only difference between them is the `Thumbnail methods`_ that they use. 28 29 Using the thumbnail filters 30 --------------------------- 31 32 Usage:: 33 34 <img src="{{ object.imagefield|thumbnail:"150x100" }}" /> 35 36 The filter is applied to a image field (not the url get from 37 ``get_[field]_url`` method of the model). Supposing the imagefield filename is 38 ``'image.jpg'``, it creates a thumbnailed image file proportionally resized 39 down to a maximum of 150 pixels wide and 100 pixels high called 40 ``'image_150x100_scale.jpg'`` in the same location as the original image 41 and returns the URL to this thumbnail image. 42 43 The ``thumbnail_crop`` works exactly the same way but uses the crop method 44 (and the filename would be called ``'image_150x100_crop.jpg'``). Similarly, 45 ``thumbnail_squash`` resizes the image to exactly the dimensions given 46 (``'image_150x100_squash.jpg'``). 47 48 If the thumbnail filename already exists, it is only overwritten if the date 49 of the the original image file is newer than the thumbnail file. 50 51 Rather than just outputting the url, you can reference any other properties 52 (see the ```Thumbnail`` object properties`_ section below for a complete 53 list):: 54 55 {% with object.imagefield|thumbnail:"150x100" as thumb %} 56 <img src="{{ thumb.url }}" width="{{ thumb.thumbnail.size.0 }}" height="{{ thumb.size.1 }}" /> 57 {% endwith %} 58 59 60 Creating a thumbnail 61 ==================== 62 63 The rest of this documentation deals with lower-level usage of thumbnails. 64 65 To create a thumbnail object, simply call the ``Thumbnail`` class:: 66 67 >>> from django.contrib.thumbnails import * 68 >>> thumbnail = Thumbnail(filename, data, size=(100, 100)) 69 70 The thumbnail object takes the following arguments: 71 72 =============== =========================================================== 73 Argument Description 74 =============== =========================================================== 75 76 ``filename`` A string containing the path and filename to use when 77 saving or retreiving this thumbnail image from disk 78 (relative to the ``Thumbnail`` object's ``root`` property 79 which defaults to ``settings.MEDIA_ROOT``). 80 81 For advanced usage, see the ```Thumbnail```_ property 82 section. 83 84 ``data`` A string or stream of the original image object to be 85 thumbnailed. If not provided and a file matching the 86 thumbnail can not be found, ``TemplateNoData`` will be 87 raised. 88 89 Example:: 90 91 >>> Thumbnail('%(method)/s%(x)sx%(y)s/test.jpg', size=(60, 40)).filename 92 '.../scale/60x40/test.jpg' 93 94 ``overwrite`` Set to ``True`` to overwrite the thumbnail with ``data`` 95 even if an existing cached thumbnail is found. Defaults to 96 ``False``. 97 98 ``size`` The size for the thumbnail image. Required unless using a 99 subclass which provides a default ``size`` (see the 100 `Custom thumbnails`_ section below). 101 102 ``Thumbnail`` object properties 103 =============================== 104 105 The thumbnail object which is created provides the following properties and 106 functions: 107 108 ``filename`` 109 ------------ 110 111 Reading this property returns the full path and filename to this thumbnail 112 image. 113 114 When you set this property, the filename string you provide is internally 115 appended to the ``Thumbnail`` object's ``root`` property. 116 117 You can use string formatting to generate the filename based on the 118 thumbnailing method and size: 119 120 * ``%(x)s`` for the thumbnail target width, 121 * ``%(y)s`` for the thumbnail target height, 122 * ``%(method)s`` for the thumbnailing method. 123 124 For example:: 125 126 >>> Thumbnail('%(media)/s%(x)sx%(y)s/test.jpg', size=(60, 40)).filename 127 '.../scale/60x40/test.jpg' 128 129 Note: thumbnailed images are always saved as JPEG images, so if the filename 130 string does not end in `'.jpg'`, this will be automatically appended to the 131 thumbnail's filename. 132 133 ``original_image`` 134 ------------------ 135 136 This read-only property returns a PIL ``Image`` containing the original 137 image (passed in with ``data``). 138 139 ``thumbnail`` 140 ------------- 141 142 This read-only property returns a PIL ``Image`` containing the thumbnail 143 image. 144 145 ``url`` 146 ------- 147 148 This read-only property returns the full url this thumbnail image. 149 150 It is generated by appending the parsed ``filename`` string to the 151 ``Template`` object's ``base_url`` property. 152 153 ``delete()`` 154 ------------ 155 156 Call this function to delete the thumbnail file if it exists on the disk. 157 158 Custom thumbnails 159 ================= 160 161 Similar to newforms, you can create a subclass to override the default 162 properties of the ``Thumbnail`` base class:: 163 164 from django.contrib.thumbnails import Thumbnail 165 166 class MyThumbnail(Thumbnail): 167 size = (100, 100) 168 169 Here are the properties you can provide to your subclass: 170 171 =============== =========================================================== 172 Property Description 173 =============== =========================================================== 174 175 ``size`` Default size for creating thumbnails (no default). 176 ``base_url`` Base url for thumbnails (default is ``settings.MEDIA_URL``). 177 ``root`` Base directory for thumbnails (default is 178 ``settings.MEDIA_ROOT``). 179 ``method`` The thumbnailing funciton to use (default is ``scale``). 180 See the `Thumbnail methods`_ section below. 181 182 Thumbnail methods 183 ================= 184 185 There are two thumbnailing methods available in 186 ``django.contrib.thumbnails.methods`` 187 188 ``crop()`` 189 ---------- 190 191 This method crops the image height or width to match the ratio of the thumbnail 192 ``size`` and then resizes it down to exactly the dimensions of ``size``. 193 194 It requires the original image to be both as wide and as high as ``size``. 195 196 ``scale()`` 197 ----------- 198 199 This is the normal PIL scaling method of proportionally resizing the image down 200 to no greater than the thumbnail ``size`` dimensions. 201 202 It requires the original image to be either as wide or as high as ``size``. 203 204 ``squash()`` 205 ------------ 206 207 This method resizes the image down to exactly the dimensions given. This will 208 potentially squash or stretch the image. 209 210 It requires the original image to be both as wide and as high as ``size``. 211 212 Making your own methods 213 ----------------------- 214 215 To make your own thumbnailing function, create a function which accepts one 216 parameter (``thumbnail``) and returns a PIL ``Image``. 217 218 The ``thumbnail`` parameter will be a ``Thumbnail`` object, so you can use it 219 to get the original image (it will raise ``ThumbnailNoData`` if no data was 220 provided) and the thumbnail size:: 221 222 img = thumbnail.original_image 223 size = thumbnail.size 224 225 Exceptions 226 ========== 227 228 The following exceptions (all found in ``django.contrib.thumbnails.exceptions`` 229 and all subclasses of ``ThumbnailException``) could be raised when using the 230 ``Thumbnail`` object: 231 232 ========================= ================================================ 233 Exception Reason 234 ========================= ================================================ 235 236 ``ThumbnailNoData`` Tried to get the ``original_image`` when no 237 ``data`` was provided or tried to get the 238 ``url`` when the file did not exist and no 239 ``data`` was provided. 240 ``ThumbnailTooSmall`` The ``original_image`` was too small to 241 thumbnail using the given thumbnailing method. 242 ``ThumbnailInvalidImage`` The ``data`` provided could not be decoded to 243 a valid image format (or more rarely, using 244 ``thumbnail`` to retreive an existing thumbnail 245 file from disk which could not be decoded to a 246 valid image format). 247 248 Putting it all together 249 ======================= 250 251 Here is a snippet of an example view which receives an image file from the user 252 and saves a thumbnail of this image to a file named ``[userid].jpg``:: 253 254 from django.contrib.thumbnails import Thumbnail, crop, ThumbnailException 255 256 class ProfileThumbnail(Thumbnail): 257 size = (100, 100) 258 method = crop 259 260 def profile_image(request, id): 261 profile = get_object_or_404(Profile, pk=id) 262 if request.method == 'POST': 263 image = request.FILES.get('profile_image') 264 profile.has_image = False 265 if image: 266 filename = str(profile.id) 267 try: 268 thumbnail = ProfileThumbnail(filename, image['content']) 269 profile.has_image = True 270 except ThumbnailException: 271 pass 272 profile.save() 273 return HttpResponseRedirect('../') 274 ...