mirror of
https://github.com/django/django.git
synced 2025-06-05 11:39:13 +00:00
Fixed #9893 -- Allowed using a field's max_length in the Storage.
This commit is contained in:
parent
b5c1a85b50
commit
a7c256cb54
@ -1,8 +1,11 @@
|
|||||||
import os
|
|
||||||
import errno
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import errno
|
||||||
|
from inspect import getargspec
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import SuspiciousFileOperation
|
||||||
from django.core.files import locks, File
|
from django.core.files import locks, File
|
||||||
from django.core.files.move import file_move_safe
|
from django.core.files.move import file_move_safe
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
@ -13,6 +16,7 @@ from django.utils.six.moves.urllib.parse import urljoin
|
|||||||
from django.utils.text import get_valid_filename
|
from django.utils.text import get_valid_filename
|
||||||
from django.utils._os import safe_join, abspathu
|
from django.utils._os import safe_join, abspathu
|
||||||
from django.utils.deconstruct import deconstructible
|
from django.utils.deconstruct import deconstructible
|
||||||
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
|
|
||||||
|
|
||||||
__all__ = ('Storage', 'FileSystemStorage', 'DefaultStorage', 'default_storage')
|
__all__ = ('Storage', 'FileSystemStorage', 'DefaultStorage', 'default_storage')
|
||||||
@ -33,7 +37,7 @@ class Storage(object):
|
|||||||
"""
|
"""
|
||||||
return self._open(name, mode)
|
return self._open(name, mode)
|
||||||
|
|
||||||
def save(self, name, content):
|
def save(self, name, content, max_length=None):
|
||||||
"""
|
"""
|
||||||
Saves new content to the file specified by name. The content should be
|
Saves new content to the file specified by name. The content should be
|
||||||
a proper File object or any python file-like object, ready to be read
|
a proper File object or any python file-like object, ready to be read
|
||||||
@ -46,7 +50,18 @@ class Storage(object):
|
|||||||
if not hasattr(content, 'chunks'):
|
if not hasattr(content, 'chunks'):
|
||||||
content = File(content)
|
content = File(content)
|
||||||
|
|
||||||
name = self.get_available_name(name)
|
args, varargs, varkw, defaults = getargspec(self.get_available_name)
|
||||||
|
if 'max_length' in args:
|
||||||
|
name = self.get_available_name(name, max_length=max_length)
|
||||||
|
else:
|
||||||
|
warnings.warn(
|
||||||
|
'Backwards compatibility for storage backends without '
|
||||||
|
'support for the `max_length` argument in '
|
||||||
|
'Storage.get_available_name() will be removed in Django 2.0.',
|
||||||
|
RemovedInDjango20Warning, stacklevel=2
|
||||||
|
)
|
||||||
|
name = self.get_available_name(name)
|
||||||
|
|
||||||
name = self._save(name, content)
|
name = self._save(name, content)
|
||||||
|
|
||||||
# Store filenames with forward slashes, even on Windows
|
# Store filenames with forward slashes, even on Windows
|
||||||
@ -61,7 +76,7 @@ class Storage(object):
|
|||||||
"""
|
"""
|
||||||
return get_valid_filename(name)
|
return get_valid_filename(name)
|
||||||
|
|
||||||
def get_available_name(self, name):
|
def get_available_name(self, name, max_length=None):
|
||||||
"""
|
"""
|
||||||
Returns a filename that's free on the target storage system, and
|
Returns a filename that's free on the target storage system, and
|
||||||
available for new content to be written to.
|
available for new content to be written to.
|
||||||
@ -71,10 +86,25 @@ class Storage(object):
|
|||||||
# If the filename already exists, add an underscore and a random 7
|
# If the filename already exists, add an underscore and a random 7
|
||||||
# character alphanumeric string (before the file extension, if one
|
# character alphanumeric string (before the file extension, if one
|
||||||
# exists) to the filename until the generated filename doesn't exist.
|
# exists) to the filename until the generated filename doesn't exist.
|
||||||
while self.exists(name):
|
# Truncate original name if required, so the new filename does not
|
||||||
|
# exceed the max_length.
|
||||||
|
while self.exists(name) or (max_length and len(name) > max_length):
|
||||||
# file_ext includes the dot.
|
# file_ext includes the dot.
|
||||||
name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext))
|
name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext))
|
||||||
|
if max_length is None:
|
||||||
|
continue
|
||||||
|
# Truncate file_root if max_length exceeded.
|
||||||
|
truncation = len(name) - max_length
|
||||||
|
if truncation > 0:
|
||||||
|
file_root = file_root[:-truncation]
|
||||||
|
# Entire file_root was truncated in attempt to find an available filename.
|
||||||
|
if not file_root:
|
||||||
|
raise SuspiciousFileOperation(
|
||||||
|
'Storage can not find an available filename for "%s". '
|
||||||
|
'Please make sure that the corresponding file field '
|
||||||
|
'allows sufficient "max_length".' % name
|
||||||
|
)
|
||||||
|
name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext))
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def path(self, name):
|
def path(self, name):
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
from inspect import getargspec
|
||||||
import os
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models.fields import Field
|
from django.db.models.fields import Field
|
||||||
@ -11,6 +13,7 @@ from django.db.models import signals
|
|||||||
from django.utils.encoding import force_str, force_text
|
from django.utils.encoding import force_str, force_text
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
|
|
||||||
|
|
||||||
class FieldFile(File):
|
class FieldFile(File):
|
||||||
@ -85,7 +88,19 @@ class FieldFile(File):
|
|||||||
|
|
||||||
def save(self, name, content, save=True):
|
def save(self, name, content, save=True):
|
||||||
name = self.field.generate_filename(self.instance, name)
|
name = self.field.generate_filename(self.instance, name)
|
||||||
self.name = self.storage.save(name, content)
|
|
||||||
|
args, varargs, varkw, defaults = getargspec(self.storage.save)
|
||||||
|
if 'max_length' in args:
|
||||||
|
self.name = self.storage.save(name, content, max_length=self.field.max_length)
|
||||||
|
else:
|
||||||
|
warnings.warn(
|
||||||
|
'Backwards compatibility for storage backends without '
|
||||||
|
'support for the `max_length` argument in '
|
||||||
|
'Storage.save() will be removed in Django 2.0.',
|
||||||
|
RemovedInDjango20Warning, stacklevel=2
|
||||||
|
)
|
||||||
|
self.name = self.storage.save(name, content)
|
||||||
|
|
||||||
setattr(self.instance, self.field.name, self.name)
|
setattr(self.instance, self.field.name, self.name)
|
||||||
|
|
||||||
# Update the filesize cache
|
# Update the filesize cache
|
||||||
|
@ -50,7 +50,7 @@ typically have to be overridden:
|
|||||||
* :meth:`Storage.size`
|
* :meth:`Storage.size`
|
||||||
* :meth:`Storage.url`
|
* :meth:`Storage.url`
|
||||||
|
|
||||||
Note however that not all these methods are required and may be deliberately
|
Note however that not all these methods are required and may be deliberately
|
||||||
omitted. As it happens, it is possible to leave each method unimplemented and
|
omitted. As it happens, it is possible to leave each method unimplemented and
|
||||||
still have a working Storage.
|
still have a working Storage.
|
||||||
|
|
||||||
@ -87,7 +87,6 @@ instead).
|
|||||||
|
|
||||||
.. method:: get_valid_name(name)
|
.. method:: get_valid_name(name)
|
||||||
|
|
||||||
|
|
||||||
Returns a filename suitable for use with the underlying storage system. The
|
Returns a filename suitable for use with the underlying storage system. The
|
||||||
``name`` argument passed to this method is the original filename sent to the
|
``name`` argument passed to this method is the original filename sent to the
|
||||||
server, after having any path information removed. Override this to customize
|
server, after having any path information removed. Override this to customize
|
||||||
@ -96,21 +95,28 @@ how non-standard characters are converted to safe filenames.
|
|||||||
The code provided on ``Storage`` retains only alpha-numeric characters, periods
|
The code provided on ``Storage`` retains only alpha-numeric characters, periods
|
||||||
and underscores from the original filename, removing everything else.
|
and underscores from the original filename, removing everything else.
|
||||||
|
|
||||||
.. method:: get_available_name(name)
|
.. method:: get_available_name(name, max_length=None)
|
||||||
|
|
||||||
Returns a filename that is available in the storage mechanism, possibly taking
|
Returns a filename that is available in the storage mechanism, possibly taking
|
||||||
the provided filename into account. The ``name`` argument passed to this method
|
the provided filename into account. The ``name`` argument passed to this method
|
||||||
will have already cleaned to a filename valid for the storage system, according
|
will have already cleaned to a filename valid for the storage system, according
|
||||||
to the ``get_valid_name()`` method described above.
|
to the ``get_valid_name()`` method described above.
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
The length of the filename will not exceed ``max_length``, if provided. If a
|
||||||
|
free unique filename cannot be found, a :exc:`SuspiciousFileOperation
|
||||||
|
<django.core.exceptions.SuspiciousOperation>` exception is raised.
|
||||||
|
|
||||||
If a file with ``name`` already exists, an underscore plus a random 7
|
If a file with ``name`` already exists, an underscore plus a random 7 character
|
||||||
character alphanumeric string is appended to the filename before the
|
alphanumeric string is appended to the filename before the extension.
|
||||||
extension.
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``,
|
Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``,
|
||||||
etc.) was appended to the filename until an available name in the destination
|
etc.) was appended to the filename until an available name in the destination
|
||||||
directory was found. A malicious user could exploit this deterministic
|
directory was found. A malicious user could exploit this deterministic
|
||||||
algorithm to create a denial-of-service attack. This change was also made
|
algorithm to create a denial-of-service attack. This change was also made
|
||||||
in Django 1.6.6, 1.5.9, and 1.4.14.
|
in Django 1.6.6, 1.5.9, and 1.4.14.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.8
|
||||||
|
|
||||||
|
The ``max_length`` argument was added.
|
||||||
|
@ -143,6 +143,10 @@ details on these changes.
|
|||||||
* Support for the ``=`` comparison operator in the ``if`` template tag will be
|
* Support for the ``=`` comparison operator in the ``if`` template tag will be
|
||||||
removed.
|
removed.
|
||||||
|
|
||||||
|
* The backwards compatibility shims to allow ``Storage.get_available_name()``
|
||||||
|
and ``Storage.save()`` to be defined without a ``max_length`` argument will
|
||||||
|
be removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-1.9:
|
.. _deprecation-removed-in-1.9:
|
||||||
|
|
||||||
1.9
|
1.9
|
||||||
|
@ -114,12 +114,17 @@ The Storage Class
|
|||||||
in the storage system, or ``False`` if the name is available for a new
|
in the storage system, or ``False`` if the name is available for a new
|
||||||
file.
|
file.
|
||||||
|
|
||||||
.. method:: get_available_name(name)
|
.. method:: get_available_name(name, max_length=None)
|
||||||
|
|
||||||
Returns a filename based on the ``name`` parameter that's free and
|
Returns a filename based on the ``name`` parameter that's free and
|
||||||
available for new content to be written to on the target storage
|
available for new content to be written to on the target storage
|
||||||
system.
|
system.
|
||||||
|
|
||||||
|
The length of the filename will not exceed ``max_length``, if provided.
|
||||||
|
If a free unique filename cannot be found, a
|
||||||
|
:exc:`SuspiciousFileOperation
|
||||||
|
<django.core.exceptions.SuspiciousOperation>` exception will be raised.
|
||||||
|
|
||||||
If a file with ``name`` already exists, an underscore plus a random
|
If a file with ``name`` already exists, an underscore plus a random
|
||||||
7 character alphanumeric string is appended to the filename before
|
7 character alphanumeric string is appended to the filename before
|
||||||
the extension.
|
the extension.
|
||||||
@ -133,6 +138,10 @@ The Storage Class
|
|||||||
attack. This change was also made in Django 1.6.6, 1.5.9, and
|
attack. This change was also made in Django 1.6.6, 1.5.9, and
|
||||||
1.4.14.
|
1.4.14.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.8
|
||||||
|
|
||||||
|
The ``max_length`` argument was added.
|
||||||
|
|
||||||
.. method:: get_valid_name(name)
|
.. method:: get_valid_name(name)
|
||||||
|
|
||||||
Returns a filename based on the ``name`` parameter that's suitable
|
Returns a filename based on the ``name`` parameter that's suitable
|
||||||
@ -165,17 +174,24 @@ The Storage Class
|
|||||||
standard ``open()``. For storage systems that aren't accessible from
|
standard ``open()``. For storage systems that aren't accessible from
|
||||||
the local filesystem, this will raise ``NotImplementedError`` instead.
|
the local filesystem, this will raise ``NotImplementedError`` instead.
|
||||||
|
|
||||||
.. method:: save(name, content)
|
.. method:: save(name, content, max_length=None)
|
||||||
|
|
||||||
Saves a new file using the storage system, preferably with the name
|
Saves a new file using the storage system, preferably with the name
|
||||||
specified. If there already exists a file with this name ``name``, the
|
specified. If there already exists a file with this name ``name``, the
|
||||||
storage system may modify the filename as necessary to get a unique
|
storage system may modify the filename as necessary to get a unique
|
||||||
name. The actual name of the stored file will be returned.
|
name. The actual name of the stored file will be returned.
|
||||||
|
|
||||||
|
The ``max_length`` argument is passed along to
|
||||||
|
:meth:`get_available_name`.
|
||||||
|
|
||||||
The ``content`` argument must be an instance of
|
The ``content`` argument must be an instance of
|
||||||
:class:`django.core.files.File` or of a subclass of
|
:class:`django.core.files.File` or of a subclass of
|
||||||
:class:`~django.core.files.File`.
|
:class:`~django.core.files.File`.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.8
|
||||||
|
|
||||||
|
The ``max_length`` argument was added.
|
||||||
|
|
||||||
.. method:: size(name)
|
.. method:: size(name)
|
||||||
|
|
||||||
Returns the total size, in bytes, of the file referenced by ``name``.
|
Returns the total size, in bytes, of the file referenced by ``name``.
|
||||||
|
@ -317,7 +317,15 @@ Email
|
|||||||
File Storage
|
File Storage
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
* ...
|
* :meth:`Storage.get_available_name()
|
||||||
|
<django.core.files.storage.Storage.get_available_name>` and
|
||||||
|
:meth:`Storage.save() <django.core.files.storage.Storage.save>`
|
||||||
|
now take a ``max_length`` argument to implement storage-level maximum
|
||||||
|
filename length constraints. Filenames exceeding this argument will get
|
||||||
|
truncated. This prevents a database error when appending a unique suffix to a
|
||||||
|
long filename that already exists on the storage. See the :ref:`deprecation
|
||||||
|
note <storage-max-length-update>` about adding this argument to your custom
|
||||||
|
storage classes.
|
||||||
|
|
||||||
File Uploads
|
File Uploads
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
@ -1432,6 +1440,17 @@ loader that inherits ``BaseLoader``, you must inherit ``Loader`` instead.
|
|||||||
Private API ``django.test.utils.TestTemplateLoader`` is deprecated in favor of
|
Private API ``django.test.utils.TestTemplateLoader`` is deprecated in favor of
|
||||||
``django.template.loaders.locmem.Loader``.
|
``django.template.loaders.locmem.Loader``.
|
||||||
|
|
||||||
|
.. _storage-max-length-update:
|
||||||
|
|
||||||
|
Support for the ``max_length`` argument on custom ``Storage`` classes
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
``Storage`` subclasses should add ``max_length=None`` as a parameter to
|
||||||
|
:meth:`~django.core.files.storage.Storage.get_available_name` and/or
|
||||||
|
:meth:`~django.core.files.storage.Storage.save` if they override either method.
|
||||||
|
Support for storages that do not accept this argument will be removed in
|
||||||
|
Django 2.0.
|
||||||
|
|
||||||
``qn`` replaced by ``compiler``
|
``qn`` replaced by ``compiler``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -13,6 +13,19 @@ from django.db import models
|
|||||||
from django.core.files.storage import FileSystemStorage
|
from django.core.files.storage import FileSystemStorage
|
||||||
|
|
||||||
|
|
||||||
|
class OldStyleFSStorage(FileSystemStorage):
|
||||||
|
"""
|
||||||
|
Storage backend without support for the ``max_length`` argument in
|
||||||
|
``get_available_name()`` and ``save()``; for backward-compatibility and
|
||||||
|
deprecation testing.
|
||||||
|
"""
|
||||||
|
def get_available_name(self, name):
|
||||||
|
return name
|
||||||
|
|
||||||
|
def save(self, name, content):
|
||||||
|
return super(OldStyleFSStorage, self).save(name, content)
|
||||||
|
|
||||||
|
|
||||||
temp_storage_location = tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR'])
|
temp_storage_location = tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR'])
|
||||||
temp_storage = FileSystemStorage(location=temp_storage_location)
|
temp_storage = FileSystemStorage(location=temp_storage_location)
|
||||||
|
|
||||||
@ -31,3 +44,9 @@ class Storage(models.Model):
|
|||||||
random = models.FileField(storage=temp_storage, upload_to=random_upload_to)
|
random = models.FileField(storage=temp_storage, upload_to=random_upload_to)
|
||||||
default = models.FileField(storage=temp_storage, upload_to='tests', default='tests/default.txt')
|
default = models.FileField(storage=temp_storage, upload_to='tests', default='tests/default.txt')
|
||||||
empty = models.FileField(storage=temp_storage)
|
empty = models.FileField(storage=temp_storage)
|
||||||
|
limited_length = models.FileField(storage=temp_storage, upload_to='tests', max_length=20)
|
||||||
|
extended_length = models.FileField(storage=temp_storage, upload_to='tests', max_length=300)
|
||||||
|
old_style = models.FileField(
|
||||||
|
storage=OldStyleFSStorage(location=temp_storage_location),
|
||||||
|
upload_to='tests',
|
||||||
|
)
|
||||||
|
@ -8,6 +8,7 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
import warnings
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -16,7 +17,7 @@ except ImportError:
|
|||||||
import dummy_threading as threading
|
import dummy_threading as threading
|
||||||
|
|
||||||
from django.core.cache import cache
|
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.base import File, ContentFile
|
||||||
from django.core.files.storage import FileSystemStorage, get_storage_class
|
from django.core.files.storage import FileSystemStorage, get_storage_class
|
||||||
from django.core.files.uploadedfile import (InMemoryUploadedFile, SimpleUploadedFile,
|
from django.core.files.uploadedfile import (InMemoryUploadedFile, SimpleUploadedFile,
|
||||||
@ -407,7 +408,7 @@ class FileStorageTests(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class CustomStorage(FileSystemStorage):
|
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.
|
Append numbers to duplicate files rather than underscores, like Trac.
|
||||||
"""
|
"""
|
||||||
@ -433,7 +434,7 @@ class CustomStorageTests(FileStorageTests):
|
|||||||
self.storage.delete(second)
|
self.storage.delete(second)
|
||||||
|
|
||||||
|
|
||||||
class FileFieldStorageTests(unittest.TestCase):
|
class FileFieldStorageTests(SimpleTestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
shutil.rmtree(temp_storage_location)
|
shutil.rmtree(temp_storage_location)
|
||||||
|
|
||||||
@ -503,6 +504,69 @@ class FileFieldStorageTests(unittest.TestCase):
|
|||||||
for o in objs:
|
for o in objs:
|
||||||
o.delete()
|
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):
|
def test_filefield_default(self):
|
||||||
# Default values allow an object to access a single file.
|
# Default values allow an object to access a single file.
|
||||||
temp_storage.save('tests/default.txt', ContentFile('default content'))
|
temp_storage.save('tests/default.txt', ContentFile('default content'))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user