PageRenderTime 54ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/thumbs.py

https://github.com/floppya/django-thumbs
Python | 263 lines | 240 code | 2 blank | 21 comment | 1 complexity | f0cbb0d9026d6cb1719b58645d767acf MD5 | raw file
  1. # -*- encoding: utf-8 -*-
  2. """
  3. django-thumbs on-the-fly
  4. https://github.com/madmw/django-thumbs
  5. A fork of django-thumbs [http://code.google.com/p/django-thumbs/] by Antonio Melé [http://django.es].
  6. Settings
  7. ========
  8. THUMBS_GENERATE_THUMBNAILS
  9. Generate thumbnails when saving objects.
  10. Default: True
  11. THUMBS_GENERATE_MISSING_THUMBNAILS
  12. Generate thumbnail when its url is accessed and the the file doesn't exist.
  13. Set this option when you are replacing ImageField with ImageWithThumbsField on a populated database where the thumbnails doesn't exist.
  14. Default: True
  15. THUMBS_GENERATE_ANY_SIZE
  16. Generate the thumbnail even if it's not in the configured `sizes` argument.
  17. Default: False
  18. """
  19. import cStringIO
  20. from django.db.models import ImageField
  21. from django.db.models.fields.files import ImageFieldFile
  22. from django.core.files.base import ContentFile
  23. from django.conf import settings
  24. THUMBS_GENERATE_THUMBNAILS = getattr(settings, "THUMBS_GENERATE_THUMBNAILS", True)
  25. THUMBS_GENERATE_MISSING_THUMBNAILS = getattr(settings, "THUMBS_GENERATE_MISSING_THUMBNAILS", True)
  26. THUMBS_GENERATE_ANY_SIZE = getattr(settings, "THUMBS_GENERATE_ANY_SIZE", False)
  27. try:
  28. from PIL import Image, ImageOps
  29. except:
  30. # Mac OSX
  31. import Image, ImageOps
  32. def generate_thumb(original, size, format='JPEG'):
  33. """
  34. Generates a thumbnail image and returns a ContentFile object with the thumbnail
  35. Arguments:
  36. original -- The image being resized as `File`.
  37. size -- Desired thumbnail size as `tuple`. Example: (70, 100)
  38. format -- Format of the original image ('JPEG', 'PNG', ...) The thumbnail will be generated using this same format.
  39. """
  40. original.seek(0) # see http://code.djangoproject.com/ticket/8222 for details
  41. image = Image.open(original)
  42. if image.mode not in ('L', 'RGB', 'RGBA'):
  43. image = image.convert('RGB')
  44. thumbnail = ImageOps.fit(image, size, Image.ANTIALIAS)
  45. io = cStringIO.StringIO()
  46. if format.upper() == 'JPG':
  47. format = 'JPEG'
  48. thumbnail.save(io, format)
  49. return ContentFile(io.getvalue())
  50. class ImageWithThumbsFieldFile(ImageFieldFile):
  51. """Django `ImageField` replacement with automatic generation of thumbnail images.
  52. See `ImageWithThumbsField` for usage example.
  53. """
  54. THUMB_SUFFIX = '%s.%sx%s.%s'
  55. def __init__(self, *args, **kwargs):
  56. super(ImageFieldFile, self).__init__(*args, **kwargs)
  57. def _url_for_size(self, size):
  58. """Return a URL pointing to the thumbnail image of the requested size.
  59. If `THUMBS_GENERATE_MISSING_THUMBNAILS` is True, the thumbnail will be created if it doesn't exist on disk.
  60. Arguments:
  61. size -- A tuple with the desired width and height. Example: (100, 100)
  62. """
  63. if not self:
  64. return ''
  65. else:
  66. # generate missing thumbnail if needed
  67. fileBase, extension = self.name.rsplit('.', 1)
  68. thumb_file = self.THUMB_SUFFIX % (fileBase, size[0], size[1], extension)
  69. if THUMBS_GENERATE_MISSING_THUMBNAILS:
  70. if not self.storage.exists(thumb_file):
  71. try:
  72. self._generate_thumb(self.storage.open(self.name), size)
  73. except:
  74. if settings.DEBUG:
  75. import sys
  76. print "Exception generating thumbnail"
  77. print sys.exc_info()
  78. urlBase, extension = self.url.rsplit('.', 1)
  79. thumb_url = self.THUMB_SUFFIX % (urlBase, size[0], size[1], extension)
  80. return thumb_url
  81. def __getattr__(self, name):
  82. """Return the url for the requested size.
  83. Arguments:
  84. name -- The field `url` with size suffix formatted as _WxH. Example: instance.url_100x70
  85. """
  86. sizeStr = name.replace("url_", "")
  87. width, height = sizeStr.split("x")
  88. requestedSize = (int(width), int(height))
  89. acceptedSize = None
  90. if THUMBS_GENERATE_ANY_SIZE:
  91. acceptedSize = requestedSize
  92. else:
  93. for configuredSize in self.field.sizes:
  94. # FIXME: fuzzy search, accept nearest size
  95. if requestedSize == configuredSize:
  96. acceptedSize = requestedSize
  97. if acceptedSize is not None:
  98. return self._url_for_size(acceptedSize)
  99. raise ValueError("The requested thumbnail size %s doesn't exist" % sizeStr)
  100. def _generate_thumb(self, image, size):
  101. """Generates a thumbnail of `size`.
  102. Arguments:
  103. image -- An `File` object with the image in its original size.
  104. size -- A tuple with the desired width and height. Example: (100, 100)
  105. """
  106. base, extension = self.name.rsplit('.', 1)
  107. thumb_name = self.THUMB_SUFFIX % (base, size[0], size[1], extension)
  108. thumbnail = generate_thumb(image, size, extension)
  109. saved_as = self.storage.save(thumb_name, thumbnail)
  110. if thumb_name != saved_as:
  111. raise ValueError('There is already a file named %s' % thumb_name)
  112. def save(self, name, content, save=True):
  113. super(ImageFieldFile, self).save(name, content, save)
  114. if THUMBS_GENERATE_THUMBNAILS:
  115. if self.field.sizes:
  116. for size in self.field.sizes:
  117. try:
  118. self._generate_thumb(content, size)
  119. except:
  120. if settings.DEBUG:
  121. import sys
  122. print "Exception generating thumbnail"
  123. print sys.exc_info()
  124. def delete(self, save=True):
  125. if self.name and self.field.sizes:
  126. for size in self.field.sizes:
  127. base, extension = self.name.rsplit('.', 1)
  128. thumb_name = self.THUMB_SUFFIX % (base, size[0], size[1], extension)
  129. try:
  130. self.storage.delete(thumb_name)
  131. except:
  132. if settings.DEBUG:
  133. import sys
  134. print "Exception deleting thumbnails"
  135. print sys.exc_info()
  136. super(ImageFieldFile, self).delete(save)
  137. def generate_thumbnails(self):
  138. """
  139. """
  140. if self.field.sizes:
  141. for size in self.field.sizes:
  142. try:
  143. self._generate_thumb(self.storage.open(self.name), size)
  144. except:
  145. if settings.DEBUG:
  146. import sys
  147. print "Exception generating thumbnail"
  148. print sys.exc_info()
  149. def thumbnail(self, widthOrSize, height=None):
  150. """
  151. Return the thumbnail url for an specific size. The same thing as url_[width]x[height] without the magic.
  152. Arguments:
  153. widthOrSize -- Width as integer or size as tuple.
  154. height -- Height as integer. Optional, will use `widthOrSize` as height if missing.
  155. Usage:
  156. instance.thumbnail(48, 48)
  157. instance.thumbnail(64)
  158. instance.thumbnail( (100, 70) )
  159. """
  160. if type(widthOrSize) is tuple:
  161. size = widthOrSize
  162. else:
  163. if height is None:
  164. height = widthOrSize
  165. size = (widthOrSize, height)
  166. return self.__getattr__('url_%sx%s' % (size[0], size[1]))
  167. class ImageWithThumbsField(ImageField):
  168. """
  169. Usage example:
  170. ==============
  171. photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),)
  172. To retrieve image URL, exactly the same way as with ImageField:
  173. my_object.photo.url
  174. To retrieve thumbnails URL's just add the size to it:
  175. my_object.photo.url_125x125
  176. my_object.photo.url_300x200
  177. Note: The 'sizes' attribute is not required. If you don't provide it,
  178. ImageWithThumbsField will act as a normal ImageField
  179. How it works:
  180. =============
  181. For each size in the 'sizes' atribute of the field it generates a
  182. thumbnail with that size and stores it following this format:
  183. available_filename.[width]x[height].extension
  184. Where 'available_filename' is the available filename returned by the storage
  185. backend for saving the original file.
  186. Following the usage example above: For storing a file called "photo.jpg" it saves:
  187. photo.jpg (original file)
  188. photo.125x125.jpg (first thumbnail)
  189. photo.300x200.jpg (second thumbnail)
  190. With the default storage backend if photo.jpg already exists it will use these filenames:
  191. photo_.jpg
  192. photo_.125x125.jpg
  193. photo_.300x200.jpg
  194. Note: django-thumbs assumes that if filename "any_filename.jpg" is available
  195. filenames with this format "any_filename.[widht]x[height].jpg" will be available, too.
  196. """
  197. attr_class = ImageWithThumbsFieldFile
  198. def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, sizes=None, **kwargs):
  199. self.verbose_name = verbose_name
  200. self.name = name
  201. self.width_field = width_field
  202. self.height_field = height_field
  203. self.sizes = sizes
  204. super(ImageField, self).__init__(**kwargs)
  205. try:
  206. from south.modelsinspector import add_introspection_rules
  207. except ImportError:
  208. pass
  209. else:
  210. add_introspection_rules([
  211. (
  212. [ImageWithThumbsField],
  213. [],
  214. {
  215. 'sizes': ['sizes', {}],
  216. },
  217. ),
  218. ],
  219. ['^%s.ImageWithThumbsField' % __name__.replace('\\', '.\\')]
  220. )