/tests/regressiontests/model_fields/imagefield.py
Python | 420 lines | 378 code | 15 blank | 27 comment | 8 complexity | c6664304d43876687a0550a56eb403a9 MD5 | raw file
1import os 2import shutil 3 4from django.core.files import File 5from django.core.files.base import ContentFile 6from django.core.files.images import ImageFile 7from django.test import TestCase 8 9from models import Image, Person, PersonWithHeight, PersonWithHeightAndWidth, \ 10 PersonDimensionsFirst, PersonTwoImages, TestImageFieldFile 11 12 13# If PIL available, do these tests. 14if 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)