|
1 import os |
|
2 import shutil |
|
3 |
|
4 from django.core.files import File |
|
5 from django.core.files.base import ContentFile |
|
6 from django.core.files.images import ImageFile |
|
7 from django.test import TestCase |
|
8 |
|
9 from models import Image, Person, PersonWithHeight, PersonWithHeightAndWidth, \ |
|
10 PersonDimensionsFirst, PersonTwoImages, TestImageFieldFile |
|
11 |
|
12 |
|
13 # If PIL available, do these tests. |
|
14 if Image: |
|
15 |
|
16 from models import temp_storage_dir |
|
17 |
|
18 |
|
19 class ImageFieldTestMixin(object): |
|
20 """ |
|
21 Mixin class to provide common functionality to ImageField test classes. |
|
22 """ |
|
23 |
|
24 # Person model to use for tests. |
|
25 PersonModel = PersonWithHeightAndWidth |
|
26 # File class to use for file instances. |
|
27 File = ImageFile |
|
28 |
|
29 def setUp(self): |
|
30 """ |
|
31 Creates a pristine temp directory (or deletes and recreates if it |
|
32 already exists) that the model uses as its storage directory. |
|
33 |
|
34 Sets up two ImageFile instances for use in tests. |
|
35 """ |
|
36 if os.path.exists(temp_storage_dir): |
|
37 shutil.rmtree(temp_storage_dir) |
|
38 os.mkdir(temp_storage_dir) |
|
39 |
|
40 file_path1 = os.path.join(os.path.dirname(__file__), "4x8.png") |
|
41 self.file1 = self.File(open(file_path1, 'rb')) |
|
42 |
|
43 file_path2 = os.path.join(os.path.dirname(__file__), "8x4.png") |
|
44 self.file2 = self.File(open(file_path2, 'rb')) |
|
45 |
|
46 def tearDown(self): |
|
47 """ |
|
48 Removes temp directory and all its contents. |
|
49 """ |
|
50 shutil.rmtree(temp_storage_dir) |
|
51 |
|
52 def check_dimensions(self, instance, width, height, |
|
53 field_name='mugshot'): |
|
54 """ |
|
55 Asserts that the given width and height values match both the |
|
56 field's height and width attributes and the height and width fields |
|
57 (if defined) the image field is caching to. |
|
58 |
|
59 Note, this method will check for dimension fields named by adding |
|
60 "_width" or "_height" to the name of the ImageField. So, the |
|
61 models used in these tests must have their fields named |
|
62 accordingly. |
|
63 |
|
64 By default, we check the field named "mugshot", but this can be |
|
65 specified by passing the field_name parameter. |
|
66 """ |
|
67 field = getattr(instance, field_name) |
|
68 # Check height/width attributes of field. |
|
69 if width is None and height is None: |
|
70 self.assertRaises(ValueError, getattr, field, 'width') |
|
71 self.assertRaises(ValueError, getattr, field, 'height') |
|
72 else: |
|
73 self.assertEqual(field.width, width) |
|
74 self.assertEqual(field.height, height) |
|
75 |
|
76 # Check height/width fields of model, if defined. |
|
77 width_field_name = field_name + '_width' |
|
78 if hasattr(instance, width_field_name): |
|
79 self.assertEqual(getattr(instance, width_field_name), width) |
|
80 height_field_name = field_name + '_height' |
|
81 if hasattr(instance, height_field_name): |
|
82 self.assertEqual(getattr(instance, height_field_name), height) |
|
83 |
|
84 |
|
85 class ImageFieldTests(ImageFieldTestMixin, TestCase): |
|
86 """ |
|
87 Tests for ImageField that don't need to be run with each of the |
|
88 different test model classes. |
|
89 """ |
|
90 |
|
91 def test_equal_notequal_hash(self): |
|
92 """ |
|
93 Bug #9786: Ensure '==' and '!=' work correctly. |
|
94 Bug #9508: make sure hash() works as expected (equal items must |
|
95 hash to the same value). |
|
96 """ |
|
97 # Create two Persons with different mugshots. |
|
98 p1 = self.PersonModel(name="Joe") |
|
99 p1.mugshot.save("mug", self.file1) |
|
100 p2 = self.PersonModel(name="Bob") |
|
101 p2.mugshot.save("mug", self.file2) |
|
102 self.assertEqual(p1.mugshot == p2.mugshot, False) |
|
103 self.assertEqual(p1.mugshot != p2.mugshot, True) |
|
104 |
|
105 # Test again with an instance fetched from the db. |
|
106 p1_db = self.PersonModel.objects.get(name="Joe") |
|
107 self.assertEqual(p1_db.mugshot == p2.mugshot, False) |
|
108 self.assertEqual(p1_db.mugshot != p2.mugshot, True) |
|
109 |
|
110 # Instance from db should match the local instance. |
|
111 self.assertEqual(p1_db.mugshot == p1.mugshot, True) |
|
112 self.assertEqual(hash(p1_db.mugshot), hash(p1.mugshot)) |
|
113 self.assertEqual(p1_db.mugshot != p1.mugshot, False) |
|
114 |
|
115 def test_instantiate_missing(self): |
|
116 """ |
|
117 If the underlying file is unavailable, still create instantiate the |
|
118 object without error. |
|
119 """ |
|
120 p = self.PersonModel(name="Joan") |
|
121 p.mugshot.save("shot", self.file1) |
|
122 p = self.PersonModel.objects.get(name="Joan") |
|
123 path = p.mugshot.path |
|
124 shutil.move(path, path + '.moved') |
|
125 p2 = self.PersonModel.objects.get(name="Joan") |
|
126 |
|
127 def test_delete_when_missing(self): |
|
128 """ |
|
129 Bug #8175: correctly delete an object where the file no longer |
|
130 exists on the file system. |
|
131 """ |
|
132 p = self.PersonModel(name="Fred") |
|
133 p.mugshot.save("shot", self.file1) |
|
134 os.remove(p.mugshot.path) |
|
135 p.delete() |
|
136 |
|
137 def test_size_method(self): |
|
138 """ |
|
139 Bug #8534: FileField.size should not leave the file open. |
|
140 """ |
|
141 p = self.PersonModel(name="Joan") |
|
142 p.mugshot.save("shot", self.file1) |
|
143 |
|
144 # Get a "clean" model instance |
|
145 p = self.PersonModel.objects.get(name="Joan") |
|
146 # It won't have an opened file. |
|
147 self.assertEqual(p.mugshot.closed, True) |
|
148 |
|
149 # After asking for the size, the file should still be closed. |
|
150 _ = p.mugshot.size |
|
151 self.assertEqual(p.mugshot.closed, True) |
|
152 |
|
153 def test_pickle(self): |
|
154 """ |
|
155 Tests that ImageField can be pickled, unpickled, and that the |
|
156 image of the unpickled version is the same as the original. |
|
157 """ |
|
158 import pickle |
|
159 |
|
160 p = Person(name="Joe") |
|
161 p.mugshot.save("mug", self.file1) |
|
162 dump = pickle.dumps(p) |
|
163 |
|
164 p2 = Person(name="Bob") |
|
165 p2.mugshot = self.file1 |
|
166 |
|
167 loaded_p = pickle.loads(dump) |
|
168 self.assertEqual(p.mugshot, loaded_p.mugshot) |
|
169 |
|
170 |
|
171 class ImageFieldTwoDimensionsTests(ImageFieldTestMixin, TestCase): |
|
172 """ |
|
173 Tests behavior of an ImageField and its dimensions fields. |
|
174 """ |
|
175 |
|
176 def test_constructor(self): |
|
177 """ |
|
178 Tests assigning an image field through the model's constructor. |
|
179 """ |
|
180 p = self.PersonModel(name='Joe', mugshot=self.file1) |
|
181 self.check_dimensions(p, 4, 8) |
|
182 p.save() |
|
183 self.check_dimensions(p, 4, 8) |
|
184 |
|
185 def test_image_after_constructor(self): |
|
186 """ |
|
187 Tests behavior when image is not passed in constructor. |
|
188 """ |
|
189 p = self.PersonModel(name='Joe') |
|
190 # TestImageField value will default to being an instance of its |
|
191 # attr_class, a TestImageFieldFile, with name == None, which will |
|
192 # cause it to evaluate as False. |
|
193 self.assertEqual(isinstance(p.mugshot, TestImageFieldFile), True) |
|
194 self.assertEqual(bool(p.mugshot), False) |
|
195 |
|
196 # Test setting a fresh created model instance. |
|
197 p = self.PersonModel(name='Joe') |
|
198 p.mugshot = self.file1 |
|
199 self.check_dimensions(p, 4, 8) |
|
200 |
|
201 def test_create(self): |
|
202 """ |
|
203 Tests assigning an image in Manager.create(). |
|
204 """ |
|
205 p = self.PersonModel.objects.create(name='Joe', mugshot=self.file1) |
|
206 self.check_dimensions(p, 4, 8) |
|
207 |
|
208 def test_default_value(self): |
|
209 """ |
|
210 Tests that the default value for an ImageField is an instance of |
|
211 the field's attr_class (TestImageFieldFile in this case) with no |
|
212 name (name set to None). |
|
213 """ |
|
214 p = self.PersonModel() |
|
215 self.assertEqual(isinstance(p.mugshot, TestImageFieldFile), True) |
|
216 self.assertEqual(bool(p.mugshot), False) |
|
217 |
|
218 def test_assignment_to_None(self): |
|
219 """ |
|
220 Tests that assigning ImageField to None clears dimensions. |
|
221 """ |
|
222 p = self.PersonModel(name='Joe', mugshot=self.file1) |
|
223 self.check_dimensions(p, 4, 8) |
|
224 |
|
225 # If image assigned to None, dimension fields should be cleared. |
|
226 p.mugshot = None |
|
227 self.check_dimensions(p, None, None) |
|
228 |
|
229 p.mugshot = self.file2 |
|
230 self.check_dimensions(p, 8, 4) |
|
231 |
|
232 def test_field_save_and_delete_methods(self): |
|
233 """ |
|
234 Tests assignment using the field's save method and deletion using |
|
235 the field's delete method. |
|
236 """ |
|
237 p = self.PersonModel(name='Joe') |
|
238 p.mugshot.save("mug", self.file1) |
|
239 self.check_dimensions(p, 4, 8) |
|
240 |
|
241 # A new file should update dimensions. |
|
242 p.mugshot.save("mug", self.file2) |
|
243 self.check_dimensions(p, 8, 4) |
|
244 |
|
245 # Field and dimensions should be cleared after a delete. |
|
246 p.mugshot.delete(save=False) |
|
247 self.assertEqual(p.mugshot, None) |
|
248 self.check_dimensions(p, None, None) |
|
249 |
|
250 def test_dimensions(self): |
|
251 """ |
|
252 Checks that dimensions are updated correctly in various situations. |
|
253 """ |
|
254 p = self.PersonModel(name='Joe') |
|
255 |
|
256 # Dimensions should get set if file is saved. |
|
257 p.mugshot.save("mug", self.file1) |
|
258 self.check_dimensions(p, 4, 8) |
|
259 |
|
260 # Test dimensions after fetching from database. |
|
261 p = self.PersonModel.objects.get(name='Joe') |
|
262 # Bug 11084: Dimensions should not get recalculated if file is |
|
263 # coming from the database. We test this by checking if the file |
|
264 # was opened. |
|
265 self.assertEqual(p.mugshot.was_opened, False) |
|
266 self.check_dimensions(p, 4, 8) |
|
267 # After checking dimensions on the image field, the file will have |
|
268 # opened. |
|
269 self.assertEqual(p.mugshot.was_opened, True) |
|
270 # Dimensions should now be cached, and if we reset was_opened and |
|
271 # check dimensions again, the file should not have opened. |
|
272 p.mugshot.was_opened = False |
|
273 self.check_dimensions(p, 4, 8) |
|
274 self.assertEqual(p.mugshot.was_opened, False) |
|
275 |
|
276 # If we assign a new image to the instance, the dimensions should |
|
277 # update. |
|
278 p.mugshot = self.file2 |
|
279 self.check_dimensions(p, 8, 4) |
|
280 # Dimensions were recalculated, and hence file should have opened. |
|
281 self.assertEqual(p.mugshot.was_opened, True) |
|
282 |
|
283 |
|
284 class ImageFieldNoDimensionsTests(ImageFieldTwoDimensionsTests): |
|
285 """ |
|
286 Tests behavior of an ImageField with no dimension fields. |
|
287 """ |
|
288 |
|
289 PersonModel = Person |
|
290 |
|
291 |
|
292 class ImageFieldOneDimensionTests(ImageFieldTwoDimensionsTests): |
|
293 """ |
|
294 Tests behavior of an ImageField with one dimensions field. |
|
295 """ |
|
296 |
|
297 PersonModel = PersonWithHeight |
|
298 |
|
299 |
|
300 class ImageFieldDimensionsFirstTests(ImageFieldTwoDimensionsTests): |
|
301 """ |
|
302 Tests behavior of an ImageField where the dimensions fields are |
|
303 defined before the ImageField. |
|
304 """ |
|
305 |
|
306 PersonModel = PersonDimensionsFirst |
|
307 |
|
308 |
|
309 class ImageFieldUsingFileTests(ImageFieldTwoDimensionsTests): |
|
310 """ |
|
311 Tests behavior of an ImageField when assigning it a File instance |
|
312 rather than an ImageFile instance. |
|
313 """ |
|
314 |
|
315 PersonModel = PersonDimensionsFirst |
|
316 File = File |
|
317 |
|
318 |
|
319 class TwoImageFieldTests(ImageFieldTestMixin, TestCase): |
|
320 """ |
|
321 Tests a model with two ImageFields. |
|
322 """ |
|
323 |
|
324 PersonModel = PersonTwoImages |
|
325 |
|
326 def test_constructor(self): |
|
327 p = self.PersonModel(mugshot=self.file1, headshot=self.file2) |
|
328 self.check_dimensions(p, 4, 8, 'mugshot') |
|
329 self.check_dimensions(p, 8, 4, 'headshot') |
|
330 p.save() |
|
331 self.check_dimensions(p, 4, 8, 'mugshot') |
|
332 self.check_dimensions(p, 8, 4, 'headshot') |
|
333 |
|
334 def test_create(self): |
|
335 p = self.PersonModel.objects.create(mugshot=self.file1, |
|
336 headshot=self.file2) |
|
337 self.check_dimensions(p, 4, 8) |
|
338 self.check_dimensions(p, 8, 4, 'headshot') |
|
339 |
|
340 def test_assignment(self): |
|
341 p = self.PersonModel() |
|
342 self.check_dimensions(p, None, None, 'mugshot') |
|
343 self.check_dimensions(p, None, None, 'headshot') |
|
344 |
|
345 p.mugshot = self.file1 |
|
346 self.check_dimensions(p, 4, 8, 'mugshot') |
|
347 self.check_dimensions(p, None, None, 'headshot') |
|
348 p.headshot = self.file2 |
|
349 self.check_dimensions(p, 4, 8, 'mugshot') |
|
350 self.check_dimensions(p, 8, 4, 'headshot') |
|
351 |
|
352 # Clear the ImageFields one at a time. |
|
353 p.mugshot = None |
|
354 self.check_dimensions(p, None, None, 'mugshot') |
|
355 self.check_dimensions(p, 8, 4, 'headshot') |
|
356 p.headshot = None |
|
357 self.check_dimensions(p, None, None, 'mugshot') |
|
358 self.check_dimensions(p, None, None, 'headshot') |
|
359 |
|
360 def test_field_save_and_delete_methods(self): |
|
361 p = self.PersonModel(name='Joe') |
|
362 p.mugshot.save("mug", self.file1) |
|
363 self.check_dimensions(p, 4, 8, 'mugshot') |
|
364 self.check_dimensions(p, None, None, 'headshot') |
|
365 p.headshot.save("head", self.file2) |
|
366 self.check_dimensions(p, 4, 8, 'mugshot') |
|
367 self.check_dimensions(p, 8, 4, 'headshot') |
|
368 |
|
369 # We can use save=True when deleting the image field with null=True |
|
370 # dimension fields and the other field has an image. |
|
371 p.headshot.delete(save=True) |
|
372 self.check_dimensions(p, 4, 8, 'mugshot') |
|
373 self.check_dimensions(p, None, None, 'headshot') |
|
374 p.mugshot.delete(save=False) |
|
375 self.check_dimensions(p, None, None, 'mugshot') |
|
376 self.check_dimensions(p, None, None, 'headshot') |
|
377 |
|
378 def test_dimensions(self): |
|
379 """ |
|
380 Checks that dimensions are updated correctly in various situations. |
|
381 """ |
|
382 p = self.PersonModel(name='Joe') |
|
383 |
|
384 # Dimensions should get set for the saved file. |
|
385 p.mugshot.save("mug", self.file1) |
|
386 p.headshot.save("head", self.file2) |
|
387 self.check_dimensions(p, 4, 8, 'mugshot') |
|
388 self.check_dimensions(p, 8, 4, 'headshot') |
|
389 |
|
390 # Test dimensions after fetching from database. |
|
391 p = self.PersonModel.objects.get(name='Joe') |
|
392 # Bug 11084: Dimensions should not get recalculated if file is |
|
393 # coming from the database. We test this by checking if the file |
|
394 # was opened. |
|
395 self.assertEqual(p.mugshot.was_opened, False) |
|
396 self.assertEqual(p.headshot.was_opened, False) |
|
397 self.check_dimensions(p, 4, 8,'mugshot') |
|
398 self.check_dimensions(p, 8, 4, 'headshot') |
|
399 # After checking dimensions on the image fields, the files will |
|
400 # have been opened. |
|
401 self.assertEqual(p.mugshot.was_opened, True) |
|
402 self.assertEqual(p.headshot.was_opened, True) |
|
403 # Dimensions should now be cached, and if we reset was_opened and |
|
404 # check dimensions again, the file should not have opened. |
|
405 p.mugshot.was_opened = False |
|
406 p.headshot.was_opened = False |
|
407 self.check_dimensions(p, 4, 8,'mugshot') |
|
408 self.check_dimensions(p, 8, 4, 'headshot') |
|
409 self.assertEqual(p.mugshot.was_opened, False) |
|
410 self.assertEqual(p.headshot.was_opened, False) |
|
411 |
|
412 # If we assign a new image to the instance, the dimensions should |
|
413 # update. |
|
414 p.mugshot = self.file2 |
|
415 p.headshot = self.file1 |
|
416 self.check_dimensions(p, 8, 4, 'mugshot') |
|
417 self.check_dimensions(p, 4, 8, 'headshot') |
|
418 # Dimensions were recalculated, and hence file should have opened. |
|
419 self.assertEqual(p.mugshot.was_opened, True) |
|
420 self.assertEqual(p.headshot.was_opened, True) |