From b3ffcf5da7ff2ec40b449f205e622d7d76acbb24 Mon Sep 17 00:00:00 2001 From: Josh Schneier Date: Wed, 26 Jul 2017 23:33:37 +0200 Subject: [PATCH] Fix for file-like objects without names --- storages/backends/s3boto3.py | 12 ++++++++++++ tests/test_s3boto3.py | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/storages/backends/s3boto3.py b/storages/backends/s3boto3.py index 97cd6294d..1547a47de 100644 --- a/storages/backends/s3boto3.py +++ b/storages/backends/s3boto3.py @@ -430,6 +430,18 @@ def _save(self, name, content): if self.preload_metadata: self._entries[encoded_name] = obj + # If both `name` and `content.name` are empty or None, your request + # can be rejected with `XAmzContentSHA256Mismatch` error, because in + # `django.core.files.storage.Storage.save` method your file-like object + # will be wrapped in `django.core.files.File` if no `chunks` method + # provided. `File.__bool__` method is Django-specific and depends on + # file name, for this reason`botocore.handlers.calculate_md5` can fail + # even if wrapped file-like object exists. To avoid Django-specific + # logic, pass internal file-like object if `content` is `File` + # class instance. + if isinstance(content, File): + content = content.file + self._save_content(obj, content, parameters=parameters) # Note: In boto3, after a put, last_modified is automatically reloaded # the next time it is accessed; no need to specifically reload it. diff --git a/tests/test_s3boto3.py b/tests/test_s3boto3.py index 7b239900f..adfed69b5 100644 --- a/tests/test_s3boto3.py +++ b/tests/test_s3boto3.py @@ -80,7 +80,7 @@ def test_storage_save(self): obj = self.storage.bucket.Object.return_value obj.upload_fileobj.assert_called_with( - content, + content.file, ExtraArgs={ 'ContentType': 'text/plain', 'ACL': self.storage.default_acl, @@ -96,7 +96,7 @@ def test_storage_save_gzipped(self): self.storage.save(name, content) obj = self.storage.bucket.Object.return_value obj.upload_fileobj.assert_called_with( - content, + content.file, ExtraArgs={ 'ContentType': 'application/octet-stream', 'ContentEncoding': 'gzip',