| 1 | # -*- encoding: utf-8 -*- |
| 2 | """ |
| 3 | django-thumbs by Antonio Melé |
| 4 | Modified by Arthur Darcet to support on demand resizing |
| 5 | """ |
| 6 | from django.db.models import ImageField |
| 7 | from django.db.models.fields.files import ImageFieldFile |
| 8 | from PIL import Image |
| 9 | from django.core.files.base import ContentFile |
| 10 | import cStringIO, re |
| 11 | |
| 12 | |
| 13 | def generate_thumb(img, thumb_size, format): |
| 14 | img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details |
| 15 | image = Image.open(img) |
| 16 | if image.mode not in ('L', 'RGB'): |
| 17 | image = image.convert('RGB') |
| 18 | thumb_w, thumb_h = thumb_size |
| 19 | if thumb_w == thumb_h and False: |
| 20 | xsize, ysize = image.size |
| 21 | minsize = min(xsize,ysize) |
| 22 | xnewsize = (xsize-minsize)/2 |
| 23 | ynewsize = (ysize-minsize)/2 |
| 24 | image2 = image.crop((xnewsize, ynewsize, xsize-xnewsize, ysize-ynewsize)) |
| 25 | image2.load() |
| 26 | image2.thumbnail(thumb_size, Image.ANTIALIAS) |
| 27 | else: |
| 28 | image2 = image |
| 29 | image2.thumbnail(thumb_size, Image.ANTIALIAS) |
| 30 | io = cStringIO.StringIO() |
| 31 | if format.upper()=='JPG': |
| 32 | format = 'JPEG' |
| 33 | image2.save(io, format) |
| 34 | return ContentFile(io.getvalue()) |
| 35 | |
| 36 | |
| 37 | class ImageWithThumbsFieldFile(ImageFieldFile): |
| 38 | @staticmethod |
| 39 | def _get_for_size(s, (w,h)): |
| 40 | split = s.rsplit('.',1) |
| 41 | return u'{}.{}x{}.{}'.format(split[0],w,h,split[1]) |
| 42 | |
| 43 | def __getattr__(self, attr): |
| 44 | s = attr.split('url_') |
| 45 | if len(s) != 2 or 'x' not in s[1]: |
| 46 | raise AttributeError |
| 47 | if not self.name: |
| 48 | raise AttributeError('The picture has no file associated with it.') |
| 49 | size = map(int, s[1].split('x',1)) |
| 50 | name = ImageWithThumbsFieldFile._get_for_size(self.name, size) |
| 51 | url = ImageWithThumbsFieldFile._get_for_size(self.url, size) |
| 52 | if not self.storage.exists(name): |
| 53 | thumb_content = generate_thumb(self, size, name.rsplit('.',1)[1]) |
| 54 | self.storage.save(name, thumb_content) |
| 55 | return url |
| 56 | |
| 57 | def delete(self, save=True): |
| 58 | directory, name = self.name.rsplit('/', 1) |
| 59 | _, files = self.storage.listdir(directory) |
| 60 | reg = re.compile(r'{}.[0-9]+x[0-9]+.{}'.format(*name.rsplit('.',1))) |
| 61 | for f in files: |
| 62 | if reg.match(f): |
| 63 | self.storage.delete(u'{}/{}'.format(directory, f)) |
| 64 | super(ImageWithThumbsFieldFile, self).delete(save) |
| 65 | |
| 66 | |
| 67 | class ImageWithThumbsField(ImageField): |
| 68 | attr_class = ImageWithThumbsFieldFile |
| 69 | """ |
| 70 | Usage example: |
| 71 | ============== |
| 72 | photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),) |
| 73 | |
| 74 | To retrieve image URL, exactly the same way as with ImageField: |
| 75 | my_object.photo.url |
| 76 | To retrieve thumbnails URL's just add the size to it: |
| 77 | my_object.photo.url_125x125 |
| 78 | my_object.photo.url_300x200 |
| 79 | |
| 80 | Note: The 'sizes' attribute is not required. If you don't provide it, |
| 81 | ImageWithThumbsField will act as a normal ImageField |
| 82 | |
| 83 | How it works: |
| 84 | ============= |
| 85 | For each size in the 'sizes' atribute of the field it generates a |
| 86 | thumbnail with that size and stores it following this format: |
| 87 | |
| 88 | available_filename.[width]x[height].extension |
| 89 | |
| 90 | Where 'available_filename' is the available filename returned by the storage |
| 91 | backend for saving the original file. |
| 92 | |
| 93 | Following the usage example above: For storing a file called "photo.jpg" it saves: |
| 94 | photo.jpg (original file) |
| 95 | photo.125x125.jpg (first thumbnail) |
| 96 | photo.300x200.jpg (second thumbnail) |
| 97 | |
| 98 | With the default storage backend if photo.jpg already exists it will use these filenames: |
| 99 | photo_.jpg |
| 100 | photo_.125x125.jpg |
| 101 | photo_.300x200.jpg |
| 102 | |
| 103 | Note: django-thumbs assumes that if filename "any_filename.jpg" is available |
| 104 | filenames with this format "any_filename.[widht]x[height].jpg" will be available, too. |
| 105 | |
| 106 | To do: |
| 107 | ====== |
| 108 | Add method to regenerate thubmnails |
| 109 | |
| 110 | """ |
| 111 | def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): |
| 112 | self.verbose_name=verbose_name |
| 113 | self.name=name |
| 114 | self.width_field=width_field |
| 115 | self.height_field=height_field |
| 116 | super(ImageField, self).__init__(**kwargs) |