Warn before leaving page.
[redakcja.git] / apps / sorl / thumbnail / fields.py
1 from UserDict import DictMixin
2 try:
3     from cStringIO import StringIO
4 except ImportError:
5     from StringIO import StringIO
6
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
11
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
15
16
17 REQUIRED_ARGS = ('size',)
18 ALL_ARGS = {
19     'size': 'requested_size',
20     'options': 'opts',
21     'quality': 'quality',
22     'basedir': 'basedir',
23     'subdir': 'subdir',
24     'prefix': 'prefix',
25     'extension': 'extension',
26 }
27 BASE_ARGS = {
28     'size': 'requested_size',
29     'options': 'opts',
30     'quality': 'quality',
31 }
32 TAG_HTML = '<img src="%(src)s" width="%(width)s" height="%(height)s" alt="" />'
33
34
35 class ThumbsDict(object, DictMixin):
36     def __init__(self, descriptor):
37         super(ThumbsDict, self).__init__()
38         self.descriptor = descriptor
39
40     def keys(self):
41         return self.descriptor.field.extra_thumbnails.keys()
42
43
44 class LazyThumbs(ThumbsDict):
45     def __init__(self, *args, **kwargs):
46         super(LazyThumbs, self).__init__(*args, **kwargs)
47         self.cached = {}
48
49     def __getitem__(self, key):
50         thumb = self.cached.get(key)
51         if not thumb:
52             args = self.descriptor.field.extra_thumbnails[key]
53             thumb = self.descriptor._build_thumbnail(args)
54             self.cached[key] = thumb
55         return thumb
56
57     def keys(self):
58         return self.descriptor.field.extra_thumbnails.keys()
59
60
61 class ThumbTags(ThumbsDict):
62     def __getitem__(self, key):
63         thumb = self.descriptor.extra_thumbnails[key]
64         return self.descriptor._build_thumbnail_tag(thumb)
65
66
67 class BaseThumbnailFieldFile(ImageFieldFile):
68     def _build_thumbnail(self, args):
69         # Build the DjangoThumbnail kwargs.
70         kwargs = {}
71         for k, v in args.items():
72             kwargs[ALL_ARGS[k]] = v
73         # Build the destination filename and return the thumbnail.
74         name_kwargs = {}
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)
81
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)
86
87     def _get_extra_thumbnails(self):
88         if self.field.extra_thumbnails is None:
89             return 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)
94
95     def _get_extra_thumbnails_tag(self):
96         if self.field.extra_thumbnails is None:
97             return None
98         return ThumbTags(self)
99     extra_thumbnails_tag = property(_get_extra_thumbnails_tag)
100
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()
106
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)
113
114     def generate_thumbnails(self):
115         # Getting the thumbs generates them.
116         if self.extra_thumbnails:
117             self.extra_thumbnails.values()
118
119
120 class ImageWithThumbnailsFieldFile(BaseThumbnailFieldFile):
121     def _get_thumbnail(self):
122         return self._build_thumbnail(self.field.thumbnail)
123     thumbnail = property(_get_thumbnail)
124
125     def _get_thumbnail_tag(self):
126         return self._build_thumbnail_tag(self.thumbnail)
127     thumbnail_tag = property(_get_thumbnail_tag)
128
129     def generate_thumbnails(self, *args, **kwargs):
130         self.thumbnail.generate()
131         Super = super(ImageWithThumbnailsFieldFile, self)
132         return Super.generate_thumbnails(*args, **kwargs)
133
134
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:
142                 continue
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,
147                                              **kwargs)
148
149     def _get_thumbnail_tag(self):
150         opts = dict(src=escape(self.url), width=self.width,
151                     height=self.height)
152         return mark_safe(self.field.thumbnail_tag % opts)
153     thumbnail_tag = property(_get_thumbnail_tag)
154
155
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)
163
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)
170
171     def south_field_triple(self):
172         """
173         Return a suitable description of this field for South.
174         """
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)
181
182
183 class ImageWithThumbnailsField(BaseThumbnailField):
184     """
185     photo = ImageWithThumbnailsField(
186         upload_to='uploads',
187         thumbnail={'size': (80, 80), 'options': ('crop', 'upscale'),
188                    'extension': 'png'},
189         extra_thumbnails={
190             'admin': {'size': (70, 50), 'options': ('sharpen',)},
191         }
192     )
193     """
194     attr_class = ImageWithThumbnailsFieldFile
195
196     def __init__(self, *args, **kwargs):
197         self.thumbnail = kwargs.pop('thumbnail', None)
198         super(ImageWithThumbnailsField, self).__init__(*args, **kwargs)
199
200
201 class ThumbnailField(BaseThumbnailField):
202     """
203     avatar = ThumbnailField(
204         upload_to='uploads',
205         size=(200, 200),
206         options=('crop',),
207         extra_thumbnails={
208             'admin': {'size': (70, 50), 'options': (crop, 'sharpen')},
209         }
210     )
211     """
212     attr_class = ThumbnailFieldFile
213
214     def __init__(self, *args, **kwargs):
215         self.thumbnail = {}
216         for attr in ALL_ARGS:
217             if attr in kwargs:
218                 self.thumbnail[attr] = kwargs.pop(attr)
219         super(ThumbnailField, self).__init__(*args, **kwargs)
220
221
222 def _verify_thumbnail_attrs(attrs, name="'thumbnail'"):
223     for arg in REQUIRED_ARGS:
224         if arg not in attrs:
225             raise TypeError('Required attr %r missing in %s arg' % (arg, name))
226     for attr in attrs:
227         if attr not in ALL_ARGS:
228             raise TypeError('Invalid attr %r found in %s arg' % (arg, name))