/tests/regressiontests/file_storage/tests.py
Python | 452 lines | 426 code | 7 blank | 19 comment | 4 complexity | 6994f1b2968c18a7f3ca0f741ee5aac6 MD5 | raw file
1# -*- coding: utf-8 -*- 2import os 3import shutil 4import sys 5import tempfile 6import time 7from datetime import datetime, timedelta 8try: 9 from cStringIO import StringIO 10except ImportError: 11 from StringIO import StringIO 12 13try: 14 import threading 15except ImportError: 16 import dummy_threading as threading 17 18from django.conf import settings 19from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured 20from django.core.files.base import ContentFile, File 21from django.core.files.images import get_image_dimensions 22from django.core.files.storage import FileSystemStorage, get_storage_class 23from django.core.files.uploadedfile import UploadedFile 24from django.utils import unittest 25 26# Try to import PIL in either of the two ways it can end up installed. 27# Checking for the existence of Image is enough for CPython, but 28# for PyPy, you need to check for the underlying modules 29try: 30 from PIL import Image, _imaging 31except ImportError: 32 try: 33 import Image, _imaging 34 except ImportError: 35 Image = None 36 37 38class GetStorageClassTests(unittest.TestCase): 39 def assertRaisesErrorWithMessage(self, error, message, callable, 40 *args, **kwargs): 41 self.assertRaises(error, callable, *args, **kwargs) 42 try: 43 callable(*args, **kwargs) 44 except error, e: 45 self.assertEqual(message, str(e)) 46 47 def test_get_filesystem_storage(self): 48 """ 49 get_storage_class returns the class for a storage backend name/path. 50 """ 51 self.assertEqual( 52 get_storage_class('django.core.files.storage.FileSystemStorage'), 53 FileSystemStorage) 54 55 def test_get_invalid_storage_module(self): 56 """ 57 get_storage_class raises an error if the requested import don't exist. 58 """ 59 self.assertRaisesErrorWithMessage( 60 ImproperlyConfigured, 61 "NonExistingStorage isn't a storage module.", 62 get_storage_class, 63 'NonExistingStorage') 64 65 def test_get_nonexisting_storage_class(self): 66 """ 67 get_storage_class raises an error if the requested class don't exist. 68 """ 69 self.assertRaisesErrorWithMessage( 70 ImproperlyConfigured, 71 'Storage module "django.core.files.storage" does not define a '\ 72 '"NonExistingStorage" class.', 73 get_storage_class, 74 'django.core.files.storage.NonExistingStorage') 75 76 def test_get_nonexisting_storage_module(self): 77 """ 78 get_storage_class raises an error if the requested module don't exist. 79 """ 80 # Error message may or may not be the fully qualified path. 81 self.assertRaisesRegexp( 82 ImproperlyConfigured, 83 ('Error importing storage module django.core.files.non_existing_' 84 'storage: "No module named .*non_existing_storage"'), 85 get_storage_class, 86 'django.core.files.non_existing_storage.NonExistingStorage' 87 ) 88 89class FileStorageTests(unittest.TestCase): 90 storage_class = FileSystemStorage 91 92 def setUp(self): 93 self.temp_dir = tempfile.mktemp() 94 os.makedirs(self.temp_dir) 95 self.storage = self.storage_class(location=self.temp_dir, 96 base_url='/test_media_url/') 97 98 def tearDown(self): 99 shutil.rmtree(self.temp_dir) 100 101 def test_file_access_options(self): 102 """ 103 Standard file access options are available, and work as expected. 104 """ 105 self.assertFalse(self.storage.exists('storage_test')) 106 f = self.storage.open('storage_test', 'w') 107 f.write('storage contents') 108 f.close() 109 self.assertTrue(self.storage.exists('storage_test')) 110 111 f = self.storage.open('storage_test', 'r') 112 self.assertEqual(f.read(), 'storage contents') 113 f.close() 114 115 self.storage.delete('storage_test') 116 self.assertFalse(self.storage.exists('storage_test')) 117 118 def test_file_accessed_time(self): 119 """ 120 File storage returns a Datetime object for the last accessed time of 121 a file. 122 """ 123 self.assertFalse(self.storage.exists('test.file')) 124 125 f = ContentFile('custom contents') 126 f_name = self.storage.save('test.file', f) 127 atime = self.storage.accessed_time(f_name) 128 129 self.assertEqual(atime, datetime.fromtimestamp( 130 os.path.getatime(self.storage.path(f_name)))) 131 self.assertTrue(datetime.now() - self.storage.accessed_time(f_name) < timedelta(seconds=2)) 132 self.storage.delete(f_name) 133 134 def test_file_created_time(self): 135 """ 136 File storage returns a Datetime object for the creation time of 137 a file. 138 """ 139 self.assertFalse(self.storage.exists('test.file')) 140 141 f = ContentFile('custom contents') 142 f_name = self.storage.save('test.file', f) 143 ctime = self.storage.created_time(f_name) 144 145 self.assertEqual(ctime, datetime.fromtimestamp( 146 os.path.getctime(self.storage.path(f_name)))) 147 self.assertTrue(datetime.now() - self.storage.created_time(f_name) < timedelta(seconds=2)) 148 149 self.storage.delete(f_name) 150 151 def test_file_modified_time(self): 152 """ 153 File storage returns a Datetime object for the last modified time of 154 a file. 155 """ 156 self.assertFalse(self.storage.exists('test.file')) 157 158 f = ContentFile('custom contents') 159 f_name = self.storage.save('test.file', f) 160 mtime = self.storage.modified_time(f_name) 161 162 self.assertEqual(mtime, datetime.fromtimestamp( 163 os.path.getmtime(self.storage.path(f_name)))) 164 self.assertTrue(datetime.now() - self.storage.modified_time(f_name) < timedelta(seconds=2)) 165 166 self.storage.delete(f_name) 167 168 def test_file_save_without_name(self): 169 """ 170 File storage extracts the filename from the content object if no 171 name is given explicitly. 172 """ 173 self.assertFalse(self.storage.exists('test.file')) 174 175 f = ContentFile('custom contents') 176 f.name = 'test.file' 177 178 storage_f_name = self.storage.save(None, f) 179 180 self.assertEqual(storage_f_name, f.name) 181 182 self.assertTrue(os.path.exists(os.path.join(self.temp_dir, f.name))) 183 184 self.storage.delete(storage_f_name) 185 186 def test_file_path(self): 187 """ 188 File storage returns the full path of a file 189 """ 190 self.assertFalse(self.storage.exists('test.file')) 191 192 f = ContentFile('custom contents') 193 f_name = self.storage.save('test.file', f) 194 195 self.assertEqual(self.storage.path(f_name), 196 os.path.join(self.temp_dir, f_name)) 197 198 self.storage.delete(f_name) 199 200 def test_file_url(self): 201 """ 202 File storage returns a url to access a given file from the Web. 203 """ 204 self.assertEqual(self.storage.url('test.file'), 205 '%s%s' % (self.storage.base_url, 'test.file')) 206 207 # should encode special chars except ~!*()' 208 # like encodeURIComponent() JavaScript function do 209 self.assertEqual(self.storage.url(r"""~!*()'@#$%^&*abc`+=.file"""), 210 """/test_media_url/~!*()'%40%23%24%25%5E%26*abc%60%2B%3D.file""") 211 212 # should stanslate os path separator(s) to the url path separator 213 self.assertEqual(self.storage.url("""a/b\\c.file"""), 214 """/test_media_url/a/b/c.file""") 215 216 self.storage.base_url = None 217 self.assertRaises(ValueError, self.storage.url, 'test.file') 218 219 def test_file_with_mixin(self): 220 """ 221 File storage can get a mixin to extend the functionality of the 222 returned file. 223 """ 224 self.assertFalse(self.storage.exists('test.file')) 225 226 class TestFileMixin(object): 227 mixed_in = True 228 229 f = ContentFile('custom contents') 230 f_name = self.storage.save('test.file', f) 231 232 self.assertTrue(isinstance( 233 self.storage.open('test.file', mixin=TestFileMixin), 234 TestFileMixin 235 )) 236 237 self.storage.delete('test.file') 238 239 def test_listdir(self): 240 """ 241 File storage returns a tuple containing directories and files. 242 """ 243 self.assertFalse(self.storage.exists('storage_test_1')) 244 self.assertFalse(self.storage.exists('storage_test_2')) 245 self.assertFalse(self.storage.exists('storage_dir_1')) 246 247 f = self.storage.save('storage_test_1', ContentFile('custom content')) 248 f = self.storage.save('storage_test_2', ContentFile('custom content')) 249 os.mkdir(os.path.join(self.temp_dir, 'storage_dir_1')) 250 251 dirs, files = self.storage.listdir('') 252 self.assertEqual(set(dirs), set([u'storage_dir_1'])) 253 self.assertEqual(set(files), 254 set([u'storage_test_1', u'storage_test_2'])) 255 256 self.storage.delete('storage_test_1') 257 self.storage.delete('storage_test_2') 258 os.rmdir(os.path.join(self.temp_dir, 'storage_dir_1')) 259 260 def test_file_storage_prevents_directory_traversal(self): 261 """ 262 File storage prevents directory traversal (files can only be accessed if 263 they're below the storage location). 264 """ 265 self.assertRaises(SuspiciousOperation, self.storage.exists, '..') 266 self.assertRaises(SuspiciousOperation, self.storage.exists, '/etc/passwd') 267 268class CustomStorage(FileSystemStorage): 269 def get_available_name(self, name): 270 """ 271 Append numbers to duplicate files rather than underscores, like Trac. 272 """ 273 parts = name.split('.') 274 basename, ext = parts[0], parts[1:] 275 number = 2 276 while self.exists(name): 277 name = '.'.join([basename, str(number)] + ext) 278 number += 1 279 280 return name 281 282class CustomStorageTests(FileStorageTests): 283 storage_class = CustomStorage 284 285 def test_custom_get_available_name(self): 286 first = self.storage.save('custom_storage', ContentFile('custom contents')) 287 self.assertEqual(first, 'custom_storage') 288 second = self.storage.save('custom_storage', ContentFile('more contents')) 289 self.assertEqual(second, 'custom_storage.2') 290 self.storage.delete(first) 291 self.storage.delete(second) 292 293class UnicodeFileNameTests(unittest.TestCase): 294 def test_unicode_file_names(self): 295 """ 296 Regression test for #8156: files with unicode names I can't quite figure 297 out the encoding situation between doctest and this file, but the actual 298 repr doesn't matter; it just shouldn't return a unicode object. 299 """ 300 uf = UploadedFile(name=u'?C?mo?',content_type='text') 301 self.assertEqual(type(uf.__repr__()), str) 302 303# Tests for a race condition on file saving (#4948). 304# This is written in such a way that it'll always pass on platforms 305# without threading. 306 307class SlowFile(ContentFile): 308 def chunks(self): 309 time.sleep(1) 310 return super(ContentFile, self).chunks() 311 312class FileSaveRaceConditionTest(unittest.TestCase): 313 def setUp(self): 314 self.storage_dir = tempfile.mkdtemp() 315 self.storage = FileSystemStorage(self.storage_dir) 316 self.thread = threading.Thread(target=self.save_file, args=['conflict']) 317 318 def tearDown(self): 319 shutil.rmtree(self.storage_dir) 320 321 def save_file(self, name): 322 name = self.storage.save(name, SlowFile("Data")) 323 324 def test_race_condition(self): 325 self.thread.start() 326 name = self.save_file('conflict') 327 self.thread.join() 328 self.assertTrue(self.storage.exists('conflict')) 329 self.assertTrue(self.storage.exists('conflict_1')) 330 self.storage.delete('conflict') 331 self.storage.delete('conflict_1') 332 333class FileStoragePermissions(unittest.TestCase): 334 def setUp(self): 335 self.old_perms = settings.FILE_UPLOAD_PERMISSIONS 336 settings.FILE_UPLOAD_PERMISSIONS = 0666 337 self.storage_dir = tempfile.mkdtemp() 338 self.storage = FileSystemStorage(self.storage_dir) 339 340 def tearDown(self): 341 settings.FILE_UPLOAD_PERMISSIONS = self.old_perms 342 shutil.rmtree(self.storage_dir) 343 344 def test_file_upload_permissions(self): 345 name = self.storage.save("the_file", ContentFile("data")) 346 actual_mode = os.stat(self.storage.path(name))[0] & 0777 347 self.assertEqual(actual_mode, 0666) 348 349 350class FileStoragePathParsing(unittest.TestCase): 351 def setUp(self): 352 self.storage_dir = tempfile.mkdtemp() 353 self.storage = FileSystemStorage(self.storage_dir) 354 355 def tearDown(self): 356 shutil.rmtree(self.storage_dir) 357 358 def test_directory_with_dot(self): 359 """Regression test for #9610. 360 361 If the directory name contains a dot and the file name doesn't, make 362 sure we still mangle the file name instead of the directory name. 363 """ 364 365 self.storage.save('dotted.path/test', ContentFile("1")) 366 self.storage.save('dotted.path/test', ContentFile("2")) 367 368 self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path'))) 369 self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test'))) 370 self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_1'))) 371 372 def test_first_character_dot(self): 373 """ 374 File names with a dot as their first character don't have an extension, 375 and the underscore should get added to the end. 376 """ 377 self.storage.save('dotted.path/.test', ContentFile("1")) 378 self.storage.save('dotted.path/.test', ContentFile("2")) 379 380 self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test'))) 381 # Before 2.6, a leading dot was treated as an extension, and so 382 # underscore gets added to beginning instead of end. 383 if sys.version_info < (2, 6): 384 self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_1.test'))) 385 else: 386 self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1'))) 387 388class DimensionClosingBug(unittest.TestCase): 389 """ 390 Test that get_image_dimensions() properly closes files (#8817) 391 """ 392 @unittest.skipUnless(Image, "PIL not installed") 393 def test_not_closing_of_files(self): 394 """ 395 Open files passed into get_image_dimensions() should stay opened. 396 """ 397 empty_io = StringIO() 398 try: 399 get_image_dimensions(empty_io) 400 finally: 401 self.assertTrue(not empty_io.closed) 402 403 @unittest.skipUnless(Image, "PIL not installed") 404 def test_closing_of_filenames(self): 405 """ 406 get_image_dimensions() called with a filename should closed the file. 407 """ 408 # We need to inject a modified open() builtin into the images module 409 # that checks if the file was closed properly if the function is 410 # called with a filename instead of an file object. 411 # get_image_dimensions will call our catching_open instead of the 412 # regular builtin one. 413 414 class FileWrapper(object): 415 _closed = [] 416 def __init__(self, f): 417 self.f = f 418 def __getattr__(self, name): 419 return getattr(self.f, name) 420 def close(self): 421 self._closed.append(True) 422 self.f.close() 423 424 def catching_open(*args): 425 return FileWrapper(open(*args)) 426 427 from django.core.files import images 428 images.open = catching_open 429 try: 430 get_image_dimensions(os.path.join(os.path.dirname(__file__), "test1.png")) 431 finally: 432 del images.open 433 self.assertTrue(FileWrapper._closed) 434 435class InconsistentGetImageDimensionsBug(unittest.TestCase): 436 """ 437 Test that get_image_dimensions() works properly after various calls 438 using a file handler (#11158) 439 """ 440 @unittest.skipUnless(Image, "PIL not installed") 441 def test_multiple_calls(self): 442 """ 443 Multiple calls of get_image_dimensions() should return the same size. 444 """ 445 from django.core.files.images import ImageFile 446 447 img_path = os.path.join(os.path.dirname(__file__), "test.png") 448 image = ImageFile(open(img_path, 'rb')) 449 image_pil = Image.open(img_path) 450 size_1, size_2 = get_image_dimensions(image), get_image_dimensions(image) 451 self.assertEqual(image_pil.size, size_1) 452 self.assertEqual(size_1, size_2)