1 from UserDict import DictMixin
3 from cStringIO import StringIO
5 from StringIO import StringIO
7 from django.db.models.fields.files import ImageField, ImageFieldFile
8 from django.core.files.base import ContentFile
9 from django.utils.safestring import mark_safe
10 from django.utils.html import escape
12 from sorl.thumbnail.base import Thumbnail
13 from sorl.thumbnail.main import DjangoThumbnail, build_thumbnail_name
14 from sorl.thumbnail.utils import delete_thumbnails
17 REQUIRED_ARGS = ('size',)
19 'size': 'requested_size',
25 'extension': 'extension',
28 'size': 'requested_size',
32 TAG_HTML = '<img src="%(src)s" width="%(width)s" height="%(height)s" alt="" />'
35 class ThumbsDict(object, DictMixin):
36 def __init__(self, descriptor):
37 super(ThumbsDict, self).__init__()
38 self.descriptor = descriptor
41 return self.descriptor.field.extra_thumbnails.keys()
44 class LazyThumbs(ThumbsDict):
45 def __init__(self, *args, **kwargs):
46 super(LazyThumbs, self).__init__(*args, **kwargs)
49 def __getitem__(self, key):
50 thumb = self.cached.get(key)
52 args = self.descriptor.field.extra_thumbnails[key]
53 thumb = self.descriptor._build_thumbnail(args)
54 self.cached[key] = thumb
58 return self.descriptor.field.extra_thumbnails.keys()
61 class ThumbTags(ThumbsDict):
62 def __getitem__(self, key):
63 thumb = self.descriptor.extra_thumbnails[key]
64 return self.descriptor._build_thumbnail_tag(thumb)
67 class BaseThumbnailFieldFile(ImageFieldFile):
68 def _build_thumbnail(self, args):
69 # Build the DjangoThumbnail kwargs.
71 for k, v in args.items():
72 kwargs[ALL_ARGS[k]] = v
73 # Build the destination filename and return the thumbnail.
75 for key in ['size', 'options', 'quality', 'basedir', 'subdir',
76 'prefix', 'extension']:
77 name_kwargs[key] = args.get(key)
78 source = getattr(self.instance, self.field.name)
79 dest = build_thumbnail_name(source.name, **name_kwargs)
80 return DjangoThumbnail(source, relative_dest=dest, **kwargs)
82 def _build_thumbnail_tag(self, thumb):
83 opts = dict(src=escape(thumb), width=thumb.width(),
84 height=thumb.height())
85 return mark_safe(self.field.thumbnail_tag % opts)
87 def _get_extra_thumbnails(self):
88 if self.field.extra_thumbnails is None:
90 if not hasattr(self, '_extra_thumbnails'):
91 self._extra_thumbnails = LazyThumbs(self)
92 return self._extra_thumbnails
93 extra_thumbnails = property(_get_extra_thumbnails)
95 def _get_extra_thumbnails_tag(self):
96 if self.field.extra_thumbnails is None:
98 return ThumbTags(self)
99 extra_thumbnails_tag = property(_get_extra_thumbnails_tag)
101 def save(self, *args, **kwargs):
102 # Optionally generate the thumbnails after the image is saved.
103 super(BaseThumbnailFieldFile, self).save(*args, **kwargs)
104 if self.field.generate_on_save:
105 self.generate_thumbnails()
107 def delete(self, *args, **kwargs):
108 # Delete any thumbnails too (and not just ones defined here in case
109 # the {% thumbnail %} tag was used or the thumbnail sizes changed).
110 relative_source_path = getattr(self.instance, self.field.name).name
111 delete_thumbnails(relative_source_path)
112 super(BaseThumbnailFieldFile, self).delete(*args, **kwargs)
114 def generate_thumbnails(self):
115 # Getting the thumbs generates them.
116 if self.extra_thumbnails:
117 self.extra_thumbnails.values()
120 class ImageWithThumbnailsFieldFile(BaseThumbnailFieldFile):
121 def _get_thumbnail(self):
122 return self._build_thumbnail(self.field.thumbnail)
123 thumbnail = property(_get_thumbnail)
125 def _get_thumbnail_tag(self):
126 return self._build_thumbnail_tag(self.thumbnail)
127 thumbnail_tag = property(_get_thumbnail_tag)
129 def generate_thumbnails(self, *args, **kwargs):
130 self.thumbnail.generate()
131 Super = super(ImageWithThumbnailsFieldFile, self)
132 return Super.generate_thumbnails(*args, **kwargs)
135 class ThumbnailFieldFile(BaseThumbnailFieldFile):
136 def save(self, name, content, *args, **kwargs):
137 new_content = StringIO()
138 # Build the Thumbnail kwargs.
139 thumbnail_kwargs = {}
140 for k, argk in BASE_ARGS.items():
141 if not k in self.field.thumbnail:
143 thumbnail_kwargs[argk] = self.field.thumbnail[k]
144 Thumbnail(source=content, dest=new_content, **thumbnail_kwargs)
145 new_content = ContentFile(new_content.read())
146 super(ThumbnailFieldFile, self).save(name, new_content, *args,
149 def _get_thumbnail_tag(self):
150 opts = dict(src=escape(self.url), width=self.width,
152 return mark_safe(self.field.thumbnail_tag % opts)
153 thumbnail_tag = property(_get_thumbnail_tag)
156 class BaseThumbnailField(ImageField):
157 def __init__(self, *args, **kwargs):
158 # The new arguments for this field aren't explicitly defined so that
159 # users can still use normal ImageField positional arguments.
160 self.extra_thumbnails = kwargs.pop('extra_thumbnails', None)
161 self.thumbnail_tag = kwargs.pop('thumbnail_tag', TAG_HTML)
162 self.generate_on_save = kwargs.pop('generate_on_save', False)
164 super(BaseThumbnailField, self).__init__(*args, **kwargs)
165 _verify_thumbnail_attrs(self.thumbnail)
166 if self.extra_thumbnails:
167 for extra, attrs in self.extra_thumbnails.items():
168 name = "%r of 'extra_thumbnails'"
169 _verify_thumbnail_attrs(attrs, name)
171 def south_field_triple(self):
173 Return a suitable description of this field for South.
175 # We'll just introspect ourselves, since we inherit.
176 from south.modelsinspector import introspector
177 field_class = "django.db.models.fields.files.ImageField"
178 args, kwargs = introspector(self)
179 # That's our definition!
180 return (field_class, args, kwargs)
183 class ImageWithThumbnailsField(BaseThumbnailField):
185 photo = ImageWithThumbnailsField(
187 thumbnail={'size': (80, 80), 'options': ('crop', 'upscale'),
190 'admin': {'size': (70, 50), 'options': ('sharpen',)},
194 attr_class = ImageWithThumbnailsFieldFile
196 def __init__(self, *args, **kwargs):
197 self.thumbnail = kwargs.pop('thumbnail', None)
198 super(ImageWithThumbnailsField, self).__init__(*args, **kwargs)
201 class ThumbnailField(BaseThumbnailField):
203 avatar = ThumbnailField(
208 'admin': {'size': (70, 50), 'options': (crop, 'sharpen')},
212 attr_class = ThumbnailFieldFile
214 def __init__(self, *args, **kwargs):
216 for attr in ALL_ARGS:
218 self.thumbnail[attr] = kwargs.pop(attr)
219 super(ThumbnailField, self).__init__(*args, **kwargs)
222 def _verify_thumbnail_attrs(attrs, name="'thumbnail'"):
223 for arg in REQUIRED_ARGS:
225 raise TypeError('Required attr %r missing in %s arg' % (arg, name))
227 if attr not in ALL_ARGS:
228 raise TypeError('Invalid attr %r found in %s arg' % (arg, name))