##// END OF EJS Templates
Raise an error whena decompression bomb is launched to the thumbs generator
neko259 -
r1819:1bcad2dd default
parent child Browse files
Show More
@@ -1,215 +1,217 b''
1 # -*- encoding: utf-8 -*-
1 # -*- encoding: utf-8 -*-
2 """
2 """
3 django-thumbs by Antonio Melé
3 django-thumbs by Antonio Melé
4 http://django.es
4 http://django.es
5 """
5 """
6 from django.core.files.images import ImageFile
6 from django.core.files.images import ImageFile
7 from django.db.models import ImageField
7 from django.db.models import ImageField
8 from django.db.models.fields.files import ImageFieldFile
8 from django.db.models.fields.files import ImageFieldFile
9 from PIL import Image
9 from PIL import Image
10 from django.core.files.base import ContentFile
10 from django.core.files.base import ContentFile
11 import io
11 import io
12
12
13
13
14 def generate_thumb(img, thumb_size, format):
14 def generate_thumb(img, thumb_size, format):
15 """
15 """
16 Generates a thumbnail image and returns a ContentFile object with the thumbnail
16 Generates a thumbnail image and returns a ContentFile object with the thumbnail
17
17
18 Parameters:
18 Parameters:
19 ===========
19 ===========
20 img File object
20 img File object
21
21
22 thumb_size desired thumbnail size, ie: (200,120)
22 thumb_size desired thumbnail size, ie: (200,120)
23
23
24 format format of the original image ('jpeg','gif','png',...)
24 format format of the original image ('jpeg','gif','png',...)
25 (this format will be used for the generated thumbnail, too)
25 (this format will be used for the generated thumbnail, too)
26 """
26 """
27
27
28 Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
29
28 img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details
30 img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details
29 image = Image.open(img)
31 image = Image.open(img)
30
32
31 # get size
33 # get size
32 thumb_w, thumb_h = thumb_size
34 thumb_w, thumb_h = thumb_size
33 # If you want to generate a square thumbnail
35 # If you want to generate a square thumbnail
34 if thumb_w == thumb_h:
36 if thumb_w == thumb_h:
35 # quad
37 # quad
36 xsize, ysize = image.size
38 xsize, ysize = image.size
37 # get minimum size
39 # get minimum size
38 minsize = min(xsize, ysize)
40 minsize = min(xsize, ysize)
39 # largest square possible in the image
41 # largest square possible in the image
40 xnewsize = (xsize - minsize) / 2
42 xnewsize = (xsize - minsize) / 2
41 ynewsize = (ysize - minsize) / 2
43 ynewsize = (ysize - minsize) / 2
42 # crop it
44 # crop it
43 image2 = image.crop(
45 image2 = image.crop(
44 (xnewsize, ynewsize, xsize - xnewsize, ysize - ynewsize))
46 (xnewsize, ynewsize, xsize - xnewsize, ysize - ynewsize))
45 # load is necessary after crop
47 # load is necessary after crop
46 image2.load()
48 image2.load()
47 # thumbnail of the cropped image (with ANTIALIAS to make it look better)
49 # thumbnail of the cropped image (with ANTIALIAS to make it look better)
48 image2.thumbnail(thumb_size, Image.ANTIALIAS)
50 image2.thumbnail(thumb_size, Image.ANTIALIAS)
49 else:
51 else:
50 # not quad
52 # not quad
51 image2 = image
53 image2 = image
52 image2.thumbnail(thumb_size, Image.ANTIALIAS)
54 image2.thumbnail(thumb_size, Image.ANTIALIAS)
53
55
54 output = io.BytesIO()
56 output = io.BytesIO()
55 # PNG and GIF are the same, JPG is JPEG
57 # PNG and GIF are the same, JPG is JPEG
56 if format.upper() == 'JPG':
58 if format.upper() == 'JPG':
57 format = 'JPEG'
59 format = 'JPEG'
58
60
59 image2.save(output, format)
61 image2.save(output, format)
60 return ContentFile(output.getvalue())
62 return ContentFile(output.getvalue())
61
63
62
64
63 class ImageWithThumbsFieldFile(ImageFieldFile):
65 class ImageWithThumbsFieldFile(ImageFieldFile):
64 """
66 """
65 See ImageWithThumbsField for usage example
67 See ImageWithThumbsField for usage example
66 """
68 """
67
69
68 def __init__(self, *args, **kwargs):
70 def __init__(self, *args, **kwargs):
69 super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs)
71 super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs)
70 self.sizes = self.field.sizes
72 self.sizes = self.field.sizes
71
73
72 if self.sizes:
74 if self.sizes:
73 def get_size(self, size):
75 def get_size(self, size):
74 if not self:
76 if not self:
75 return ''
77 return ''
76 else:
78 else:
77 split = self.url.rsplit('.', 1)
79 split = self.url.rsplit('.', 1)
78 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
80 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
79 return thumb_url
81 return thumb_url
80
82
81 for size in self.sizes:
83 for size in self.sizes:
82 (w, h) = size
84 (w, h) = size
83 setattr(self, 'url_%sx%s' % (w, h), get_size(self, size))
85 setattr(self, 'url_%sx%s' % (w, h), get_size(self, size))
84
86
85 def save(self, name, content, save=True):
87 def save(self, name, content, save=True):
86 super(ImageWithThumbsFieldFile, self).save(name, content, save)
88 super(ImageWithThumbsFieldFile, self).save(name, content, save)
87
89
88 if self.sizes:
90 if self.sizes:
89 for size in self.sizes:
91 for size in self.sizes:
90 (w, h) = size
92 (w, h) = size
91 split = self.name.rsplit('.', 1)
93 split = self.name.rsplit('.', 1)
92 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
94 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
93
95
94 # you can use another thumbnailing function if you like
96 # you can use another thumbnailing function if you like
95 thumb_content = generate_thumb(content, size, split[1])
97 thumb_content = generate_thumb(content, size, split[1])
96
98
97 thumb_name_ = self.storage.save(thumb_name, thumb_content)
99 thumb_name_ = self.storage.save(thumb_name, thumb_content)
98
100
99 if not thumb_name == thumb_name_:
101 if not thumb_name == thumb_name_:
100 raise ValueError(
102 raise ValueError(
101 'There is already a file named %s' % thumb_name)
103 'There is already a file named %s' % thumb_name)
102
104
103 def delete(self, save=True):
105 def delete(self, save=True):
104 name = self.name
106 name = self.name
105 super(ImageWithThumbsFieldFile, self).delete(save)
107 super(ImageWithThumbsFieldFile, self).delete(save)
106 if self.sizes:
108 if self.sizes:
107 for size in self.sizes:
109 for size in self.sizes:
108 (w, h) = size
110 (w, h) = size
109 split = name.rsplit('.', 1)
111 split = name.rsplit('.', 1)
110 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
112 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
111 try:
113 try:
112 self.storage.delete(thumb_name)
114 self.storage.delete(thumb_name)
113 except:
115 except:
114 pass
116 pass
115
117
116
118
117 class ImageWithThumbsField(ImageField):
119 class ImageWithThumbsField(ImageField):
118 attr_class = ImageWithThumbsFieldFile
120 attr_class = ImageWithThumbsFieldFile
119 """
121 """
120 Usage example:
122 Usage example:
121 ==============
123 ==============
122 photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),)
124 photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),)
123
125
124 To retrieve image URL, exactly the same way as with ImageField:
126 To retrieve image URL, exactly the same way as with ImageField:
125 my_object.photo.url
127 my_object.photo.url
126 To retrieve thumbnails URL's just add the size to it:
128 To retrieve thumbnails URL's just add the size to it:
127 my_object.photo.url_125x125
129 my_object.photo.url_125x125
128 my_object.photo.url_300x200
130 my_object.photo.url_300x200
129
131
130 Note: The 'sizes' attribute is not required. If you don't provide it,
132 Note: The 'sizes' attribute is not required. If you don't provide it,
131 ImageWithThumbsField will act as a normal ImageField
133 ImageWithThumbsField will act as a normal ImageField
132
134
133 How it works:
135 How it works:
134 =============
136 =============
135 For each size in the 'sizes' atribute of the field it generates a
137 For each size in the 'sizes' atribute of the field it generates a
136 thumbnail with that size and stores it following this format:
138 thumbnail with that size and stores it following this format:
137
139
138 available_filename.[width]x[height].extension
140 available_filename.[width]x[height].extension
139
141
140 Where 'available_filename' is the available filename returned by the storage
142 Where 'available_filename' is the available filename returned by the storage
141 backend for saving the original file.
143 backend for saving the original file.
142
144
143 Following the usage example above: For storing a file called "photo.jpg" it saves:
145 Following the usage example above: For storing a file called "photo.jpg" it saves:
144 photo.jpg (original file)
146 photo.jpg (original file)
145 photo.125x125.jpg (first thumbnail)
147 photo.125x125.jpg (first thumbnail)
146 photo.300x200.jpg (second thumbnail)
148 photo.300x200.jpg (second thumbnail)
147
149
148 With the default storage backend if photo.jpg already exists it will use these filenames:
150 With the default storage backend if photo.jpg already exists it will use these filenames:
149 photo_.jpg
151 photo_.jpg
150 photo_.125x125.jpg
152 photo_.125x125.jpg
151 photo_.300x200.jpg
153 photo_.300x200.jpg
152
154
153 Note: django-thumbs assumes that if filename "any_filename.jpg" is available
155 Note: django-thumbs assumes that if filename "any_filename.jpg" is available
154 filenames with this format "any_filename.[widht]x[height].jpg" will be available, too.
156 filenames with this format "any_filename.[widht]x[height].jpg" will be available, too.
155
157
156 To do:
158 To do:
157 ======
159 ======
158 Add method to regenerate thubmnails
160 Add method to regenerate thubmnails
159
161
160
162
161 """
163 """
162
164
163 preview_width_field = None
165 preview_width_field = None
164 preview_height_field = None
166 preview_height_field = None
165
167
166 def __init__(self, verbose_name=None, name=None, width_field=None,
168 def __init__(self, verbose_name=None, name=None, width_field=None,
167 height_field=None, sizes=None,
169 height_field=None, sizes=None,
168 preview_width_field=None, preview_height_field=None,
170 preview_width_field=None, preview_height_field=None,
169 **kwargs):
171 **kwargs):
170 self.verbose_name = verbose_name
172 self.verbose_name = verbose_name
171 self.name = name
173 self.name = name
172 self.width_field = width_field
174 self.width_field = width_field
173 self.height_field = height_field
175 self.height_field = height_field
174 self.sizes = sizes
176 self.sizes = sizes
175 super(ImageField, self).__init__(**kwargs)
177 super(ImageField, self).__init__(**kwargs)
176
178
177 if sizes is not None and len(sizes) == 1:
179 if sizes is not None and len(sizes) == 1:
178 self.preview_width_field = preview_width_field
180 self.preview_width_field = preview_width_field
179 self.preview_height_field = preview_height_field
181 self.preview_height_field = preview_height_field
180
182
181 def update_dimension_fields(self, instance, force=False, *args, **kwargs):
183 def update_dimension_fields(self, instance, force=False, *args, **kwargs):
182 """
184 """
183 Update original image dimension fields and thumb dimension fields
185 Update original image dimension fields and thumb dimension fields
184 (only if 1 thumb size is defined)
186 (only if 1 thumb size is defined)
185 """
187 """
186
188
187 super(ImageWithThumbsField, self).update_dimension_fields(instance,
189 super(ImageWithThumbsField, self).update_dimension_fields(instance,
188 force, *args,
190 force, *args,
189 **kwargs)
191 **kwargs)
190 thumb_width_field = self.preview_width_field
192 thumb_width_field = self.preview_width_field
191 thumb_height_field = self.preview_height_field
193 thumb_height_field = self.preview_height_field
192
194
193 if thumb_width_field is None or thumb_height_field is None \
195 if thumb_width_field is None or thumb_height_field is None \
194 or len(self.sizes) != 1:
196 or len(self.sizes) != 1:
195 return
197 return
196
198
197 original_width = getattr(instance, self.width_field)
199 original_width = getattr(instance, self.width_field)
198 original_height = getattr(instance, self.height_field)
200 original_height = getattr(instance, self.height_field)
199
201
200 if original_width > 0 and original_height > 0:
202 if original_width > 0 and original_height > 0:
201 thumb_width, thumb_height = self.sizes[0]
203 thumb_width, thumb_height = self.sizes[0]
202
204
203 w_scale = float(thumb_width) / original_width
205 w_scale = float(thumb_width) / original_width
204 h_scale = float(thumb_height) / original_height
206 h_scale = float(thumb_height) / original_height
205 scale_ratio = min(w_scale, h_scale)
207 scale_ratio = min(w_scale, h_scale)
206
208
207 if scale_ratio >= 1:
209 if scale_ratio >= 1:
208 thumb_width_ratio = original_width
210 thumb_width_ratio = original_width
209 thumb_height_ratio = original_height
211 thumb_height_ratio = original_height
210 else:
212 else:
211 thumb_width_ratio = int(original_width * scale_ratio)
213 thumb_width_ratio = int(original_width * scale_ratio)
212 thumb_height_ratio = int(original_height * scale_ratio)
214 thumb_height_ratio = int(original_height * scale_ratio)
213
215
214 setattr(instance, thumb_width_field, thumb_width_ratio)
216 setattr(instance, thumb_width_field, thumb_width_ratio)
215 setattr(instance, thumb_height_field, thumb_height_ratio) No newline at end of file
217 setattr(instance, thumb_height_field, thumb_height_ratio)
General Comments 0
You need to be logged in to leave comments. Login now