diff --git a/docs/backends/gcloud.rst b/docs/backends/gcloud.rst index e8d8b3dae..4f26ca429 100644 --- a/docs/backends/gcloud.rst +++ b/docs/backends/gcloud.rst @@ -142,6 +142,11 @@ rolled over into a temporary file on disk. Default is 0: Do not roll over. Sets Cache-Control HTTP header for the file, more about HTTP caching can be found `here `_ +``GS_CUSTOM_ENDPOINT`` (optional: default is ``None``) + +Sets a `custom endpoint `_, +that will be used instead of ``https://storage.googleapis.com`` when generating URLs for files. + ``GS_LOCATION`` (optional: default is ``''``) Subdirectory in which the files will be stored. diff --git a/storages/backends/gcloud.py b/storages/backends/gcloud.py index b9aa55274..4c7de4a6c 100644 --- a/storages/backends/gcloud.py +++ b/storages/backends/gcloud.py @@ -8,6 +8,7 @@ from django.utils import timezone from django.utils.deconstruct import deconstructible from django.utils.encoding import force_bytes, smart_str +from django.utils.six.moves.urllib.parse import quote from storages.utils import ( check_location, clean_name, get_available_overwrite_name, safe_join, @@ -15,6 +16,7 @@ ) try: + from google.cloud.storage._signing import generate_signed_url from google.cloud.storage.client import Client from google.cloud.storage.blob import Blob from google.cloud.exceptions import NotFound @@ -86,6 +88,7 @@ class GoogleCloudStorage(Storage): project_id = setting('GS_PROJECT_ID') credentials = setting('GS_CREDENTIALS') bucket_name = setting('GS_BUCKET_NAME') + custom_endpoint = setting('GS_CUSTOM_ENDPOINT', None) location = setting('GS_LOCATION', '') auto_create_bucket = setting('GS_AUTO_CREATE_BUCKET', False) auto_create_acl = setting('GS_AUTO_CREATE_ACL', 'projectPrivate') @@ -262,9 +265,22 @@ def url(self, name): name = self._normalize_name(clean_name(name)) blob = self.bucket.blob(self._encode_name(name)) - if self.default_acl == 'publicRead': + if not self.custom_endpoint and self.default_acl == 'publicRead': return blob.public_url - return blob.generate_signed_url(self.expiration) + elif self.default_acl == 'publicRead': + return '{storage_base_url}/{quoted_name}'.format( + storage_base_url=self.custom_endpoint, + quoted_name=quote(name.encode('utf-8')), + ) + elif not self.custom_endpoint: + return blob.generate_signed_url(self.expiration) + else: + return generate_signed_url( + self.credentials, + resource=quote(name.encode('utf-8')), + api_access_endpoint=self.custom_endpoint, + expiration=self.expiration, + ) def get_available_name(self, name, max_length=None): name = clean_name(name) diff --git a/tests/test_gcloud.py b/tests/test_gcloud.py index 1d3a8f723..a5fcd953c 100644 --- a/tests/test_gcloud.py +++ b/tests/test_gcloud.py @@ -358,6 +358,18 @@ def test_url_not_public_file_with_custom_expires(self): self.assertEqual(url, 'http://signed_url') blob.generate_signed_url.assert_called_with(timedelta(seconds=3600)) + def test_custom_endpoint(self): + self.storage.custom_endpoint = "https://example.com" + + self.storage.default_acl = 'publicRead' + url = "{}/{}".format(self.storage.custom_endpoint, self.filename) + self.assertEqual(self.storage.url(self.filename), url) + + signed_url = 'https://signed_url' + self.storage.default_acl = 'projectPrivate' + gcloud.generate_signed_url = mock.MagicMock(return_value=signed_url) + self.assertEqual(self.storage.url(self.filename), signed_url) + def test_get_available_name(self): self.storage.file_overwrite = True self.assertEqual(self.storage.get_available_name(