Skip to content

Commit

Permalink
Fixes #28184 - Add ability to pass a callable as the storage param to…
Browse files Browse the repository at this point in the history
… FileField, and add test

Signed-off-by: miigotu <miigotu@gmail.com>

Add a check that the callable FileField storage is an instance of Storage once instantiated, to assure necessary methods are present
  • Loading branch information
miigotu committed Mar 2, 2018
1 parent a2e97ab commit 446e6db
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 4 deletions.
12 changes: 11 additions & 1 deletion django/db/models/fields/files.py
Expand Up @@ -3,9 +3,10 @@

from django import forms
from django.core import checks
from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import File
from django.core.files.images import ImageFile
from django.core.files.storage import default_storage
from django.core.files.storage import Storage, default_storage
from django.db.models import signals
from django.db.models.fields import Field
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -224,6 +225,15 @@ def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **k
self._primary_key_set_explicitly = 'primary_key' in kwargs

self.storage = storage or default_storage

if callable(self.storage):
self.storage = self.storage()
if not isinstance(self.storage, Storage):
raise ImproperlyConfigured(
"%s.storage must be a subclass/instance of %s.%s"
% (self.__class__.__name__, Storage.__module__, Storage.__name__)
)

self.upload_to = upload_to

kwargs.setdefault('max_length', 100)
Expand Down
15 changes: 13 additions & 2 deletions tests/file_storage/models.py
Expand Up @@ -23,10 +23,17 @@ def get_valid_name(self, name):


class Storage(models.Model):
def custom_upload_to(self, filename):

@staticmethod
def callable_temp_storage():
return FileSystemStorage(location=temp_storage_location)

@staticmethod
def custom_upload_to(filename):
return 'foo'

def random_upload_to(self, filename):
@staticmethod
def random_upload_to(filename):
# This returns a different result each time,
# to make sure it only gets called once.
return '%s/%s' % (random.randint(100, 999), filename)
Expand All @@ -38,6 +45,10 @@ def random_upload_to(self, filename):
storage=CustomValidNameStorage(location=temp_storage_location),
upload_to=random_upload_to,
)
callable_storage = models.FileField(
storage=callable_temp_storage,
upload_to=custom_upload_to,
)
default = models.FileField(storage=temp_storage, upload_to='tests', default='tests/default.txt')
empty = models.FileField(storage=temp_storage)
limited_length = models.FileField(storage=temp_storage, upload_to='tests', max_length=20)
Expand Down
31 changes: 30 additions & 1 deletion tests/file_storage/tests.py
Expand Up @@ -10,12 +10,15 @@
from urllib.request import urlopen

from django.core.cache import cache
from django.core.exceptions import SuspiciousFileOperation
from django.core.exceptions import (
ImproperlyConfigured, SuspiciousFileOperation,
)
from django.core.files.base import ContentFile, File
from django.core.files.storage import FileSystemStorage, get_storage_class
from django.core.files.uploadedfile import (
InMemoryUploadedFile, SimpleUploadedFile, TemporaryUploadedFile,
)
from django.db.models import FileField
from django.db.models.fields.files import FileDescriptor
from django.test import (
LiveServerTestCase, SimpleTestCase, TestCase, override_settings,
Expand Down Expand Up @@ -801,6 +804,32 @@ def test_stringio(self):
with temp_storage.open('tests/stringio') as f:
self.assertEqual(f.read(), b'content')

def test_callable_base_class_error_raises(self):
class NotStorage:
pass
msg = "FileField.storage must be a subclass/instance of django.core.files.storage.Storage"
with self.assertRaisesMessage(ImproperlyConfigured, msg):
FileField(storage=NotStorage)

def test_callable_function_storage_file_field(self):
storage = FileSystemStorage(location=temp_storage_location)

def get_storage():
return storage

obj = FileField(storage=get_storage)
self.assertEqual(obj.storage, storage)
self.assertEqual(obj.storage.location, storage.location)

def test_callable_class_storage_file_field(self):
class GetStorage(FileSystemStorage):
def __init__(self):
super(FileSystemStorage, self).__init__(location=temp_storage_location)

obj = FileField(storage=GetStorage)
self.assertIsInstance(obj.storage, Storage)
self.assertEqual(obj.storage.location, temp_storage_location)


# Tests for a race condition on file saving (#4948).
# This is written in such a way that it'll always pass on platforms
Expand Down

0 comments on commit 446e6db

Please sign in to comment.