|
@@ -8,6 +8,7 @@ import sys
|
|
|
import tempfile
|
|
|
import time
|
|
|
import unittest
|
|
|
+import warnings
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
|
try:
|
|
@@ -16,7 +17,7 @@ except ImportError:
|
|
|
import dummy_threading as threading
|
|
|
|
|
|
from django.core.cache import cache
|
|
|
-from django.core.exceptions import SuspiciousOperation
|
|
|
+from django.core.exceptions import SuspiciousOperation, SuspiciousFileOperation
|
|
|
from django.core.files.base import File, ContentFile
|
|
|
from django.core.files.storage import FileSystemStorage, get_storage_class
|
|
|
from django.core.files.uploadedfile import (InMemoryUploadedFile, SimpleUploadedFile,
|
|
@@ -407,7 +408,7 @@ class FileStorageTests(unittest.TestCase):
|
|
|
|
|
|
|
|
|
class CustomStorage(FileSystemStorage):
|
|
|
- def get_available_name(self, name):
|
|
|
+ def get_available_name(self, name, max_length=None):
|
|
|
"""
|
|
|
Append numbers to duplicate files rather than underscores, like Trac.
|
|
|
"""
|
|
@@ -433,7 +434,7 @@ class CustomStorageTests(FileStorageTests):
|
|
|
self.storage.delete(second)
|
|
|
|
|
|
|
|
|
-class FileFieldStorageTests(unittest.TestCase):
|
|
|
+class FileFieldStorageTests(SimpleTestCase):
|
|
|
def tearDown(self):
|
|
|
shutil.rmtree(temp_storage_location)
|
|
|
|
|
@@ -503,6 +504,69 @@ class FileFieldStorageTests(unittest.TestCase):
|
|
|
for o in objs:
|
|
|
o.delete()
|
|
|
|
|
|
+ def test_file_truncation(self):
|
|
|
+ # Given the max_length is limited, when multiple files get uploaded
|
|
|
+ # under the same name, then the filename get truncated in order to fit
|
|
|
+ # in _(7 random chars). When most of the max_length is taken by
|
|
|
+ # dirname + extension and there are not enough characters in the
|
|
|
+ # filename to truncate, an exception should be raised.
|
|
|
+ objs = [Storage() for i in range(2)]
|
|
|
+ filename = 'filename.ext'
|
|
|
+
|
|
|
+ for o in objs:
|
|
|
+ o.limited_length.save(filename, ContentFile('Same Content'))
|
|
|
+ try:
|
|
|
+ # Testing truncation.
|
|
|
+ names = [o.limited_length.name for o in objs]
|
|
|
+ self.assertEqual(names[0], 'tests/%s' % filename)
|
|
|
+ six.assertRegex(self, names[1], 'tests/fi_%s.ext' % FILE_SUFFIX_REGEX)
|
|
|
+
|
|
|
+ # Testing exception is raised when filename is too short to truncate.
|
|
|
+ filename = 'short.longext'
|
|
|
+ objs[0].limited_length.save(filename, ContentFile('Same Content'))
|
|
|
+ self.assertRaisesMessage(
|
|
|
+ SuspiciousFileOperation, 'Storage can not find an available filename',
|
|
|
+ objs[1].limited_length.save, *(filename, ContentFile('Same Content'))
|
|
|
+ )
|
|
|
+ finally:
|
|
|
+ for o in objs:
|
|
|
+ o.delete()
|
|
|
+
|
|
|
+ def test_extended_length_storage(self):
|
|
|
+ # Testing FileField with max_length > 255. Most systems have filename
|
|
|
+ # length limitation of 255. Path takes extra chars.
|
|
|
+ filename = 251 * 'a' # 4 chars for extension.
|
|
|
+ obj = Storage()
|
|
|
+ obj.extended_length.save('%s.txt' % filename, ContentFile('Same Content'))
|
|
|
+ self.assertEqual(obj.extended_length.name, 'tests/%s.txt' % filename)
|
|
|
+ self.assertEqual(obj.extended_length.read(), b'Same Content')
|
|
|
+ obj.extended_length.close()
|
|
|
+
|
|
|
+ def test_old_style_storage(self):
|
|
|
+ # Testing backward-compatibility with old-style storage backends that
|
|
|
+ # don't take ``max_length`` parameter in ``get_available_name()``
|
|
|
+ # and save(). A deprecation warning should be raised.
|
|
|
+ obj = Storage()
|
|
|
+ with warnings.catch_warnings(record=True) as warns:
|
|
|
+ warnings.simplefilter('always')
|
|
|
+ obj.old_style.save('deprecated_storage_test.txt', ContentFile('Same Content'))
|
|
|
+ self.assertEqual(len(warns), 2)
|
|
|
+ self.assertEqual(
|
|
|
+ str(warns[0].message),
|
|
|
+ 'Backwards compatibility for storage backends without support for '
|
|
|
+ 'the `max_length` argument in Storage.save() will be removed in '
|
|
|
+ 'Django 2.0.'
|
|
|
+ )
|
|
|
+ self.assertEqual(
|
|
|
+ str(warns[1].message),
|
|
|
+ 'Backwards compatibility for storage backends without support for '
|
|
|
+ 'the `max_length` argument in Storage.get_available_name() will '
|
|
|
+ 'be removed in Django 2.0.'
|
|
|
+ )
|
|
|
+ self.assertEqual(obj.old_style.name, 'tests/deprecated_storage_test.txt')
|
|
|
+ self.assertEqual(obj.old_style.read(), b'Same Content')
|
|
|
+ obj.old_style.close()
|
|
|
+
|
|
|
def test_filefield_default(self):
|
|
|
# Default values allow an object to access a single file.
|
|
|
temp_storage.save('tests/default.txt', ContentFile('default content'))
|