From b1e84e30d5037a8f215943f1e1027ff6df77bc4c Mon Sep 17 00:00:00 2001 From: Brian Helba Date: Sun, 31 Aug 2025 17:37:26 -0400 Subject: [PATCH] Allow non-GET `Params` to be passed to `S3Storage.url` Previously, if any extra `Params` were passed to `S3Storage.url` beyond what Boto3's `get_object` API supported, the `generate_presigned_url` would fail. This means that a call like: `S3Storage.url(..., http_method='PUT`, parameters={'Tagging': ...})` would fail, as `get_object` doesn't support `Tagging`, but `put_object` does. --- storages/backends/s3.py | 7 +++++-- tests/test_s3.py | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/storages/backends/s3.py b/storages/backends/s3.py index fa2d39f16..f417f55b5 100644 --- a/storages/backends/s3.py +++ b/storages/backends/s3.py @@ -665,7 +665,7 @@ def get_modified_time(self, name): else: return make_naive(entry.last_modified) - def url(self, name, parameters=None, expire=None, http_method=None): + def url(self, name, parameters=None, expire=None, http_method='get'): # Preserve the trailing slash after normalizing the path. name = self._normalize_name(clean_name(name)) params = parameters.copy() if parameters else {} @@ -694,8 +694,11 @@ def url(self, name, parameters=None, expire=None, http_method=None): connection = ( self.connection if self.querystring_auth else self.unsigned_connection ) + # Vary on the the `ClientMethod`, instead of just the `HttpMethod`, to allow + # non-GET `Params` to be passed (e.g. setting `Tagging` for `put_object`). + client_method = f"{http_method.lower()}_object" url = connection.meta.client.generate_presigned_url( - "get_object", Params=params, ExpiresIn=expire, HttpMethod=http_method + client_method, Params=params, ExpiresIn=expire ) return url diff --git a/tests/test_s3.py b/tests/test_s3.py index e324baf05..0ebb8aaff 100644 --- a/tests/test_s3.py +++ b/tests/test_s3.py @@ -708,10 +708,9 @@ def test_storage_url(self): custom_method = "HEAD" self.assertEqual(self.storage.url(name, http_method=custom_method), url) self.storage.connection.meta.client.generate_presigned_url.assert_called_with( - "get_object", + "head_object", Params={"Bucket": self.storage.bucket.name, "Key": name}, ExpiresIn=self.storage.querystring_expire, - HttpMethod=custom_method, ) def test_url_unsigned(self):