diff --git a/AUTHORS b/AUTHORS index a0baffc83..843e55efb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -34,10 +34,9 @@ By order of apparition, thanks: * Max Malysh (Dropbox large file support) * Scott White (Google Cloud updates) * Alex Watt (Google Cloud Storage patch) + * Janusz Skonieczny (Google Cloud Storage limited permission workaround) * Jumpei Yoshimura (S3 docs) * Jon Dufresne - - Extra thanks to Marty for adding this in Django, you can buy his very interesting book (Pro Django). diff --git a/storages/backends/gcloud.py b/storages/backends/gcloud.py index 3ea9d6b5b..8e49705e6 100644 --- a/storages/backends/gcloud.py +++ b/storages/backends/gcloud.py @@ -7,6 +7,7 @@ from django.utils import timezone from django.utils.deconstruct import deconstructible from django.utils.encoding import force_bytes, smart_str +from google.cloud.storage import Bucket from storages.utils import check_location, clean_name, safe_join, setting @@ -84,6 +85,7 @@ class GoogleCloudStorage(Storage): bucket_name = setting('GS_BUCKET_NAME') location = setting('GS_LOCATION', '') auto_create_bucket = setting('GS_AUTO_CREATE_BUCKET', False) + always_get_bucket = setting('GS_ALWAYS_GET_BUCKET', True) auto_create_acl = setting('GS_AUTO_CREATE_ACL', 'projectPrivate') default_acl = setting('GS_DEFAULT_ACL') @@ -125,6 +127,13 @@ def _get_or_create_bucket(self, name): """ Retrieves a bucket if it exists, otherwise creates it. """ + if not self.always_get_bucket: + # If permissions are limited. Call to get_bucket (below) results in: + # 403 Caller does not have storage.buckets.get access to bucket. + # Even if bucket actually exists + # So we don't get_bucket, we just create it's proxy here + return Bucket(client=self.client, name=name) + try: return self.client.get_bucket(name) except NotFound: diff --git a/tests/test_gcloud.py b/tests/test_gcloud.py index a3e85fb6d..4ffac3aaf 100644 --- a/tests/test_gcloud.py +++ b/tests/test_gcloud.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - try: from unittest import mock except ImportError: # Python 3.2 and below @@ -19,6 +18,7 @@ class GCloudTestCase(TestCase): + def setUp(self): self.bucket_name = 'test_bucket' self.filename = 'test_file.txt' @@ -169,6 +169,36 @@ def test_exists_bucket_auto_create(self): self.assertTrue(self.storage.exists('')) self.storage._client.create_bucket.assert_called_with(self.bucket_name) + def test_bucket_auto_create_true(self): + self.storage.auto_create_bucket = True + self.storage._client = mock.MagicMock() + self.storage._client.get_bucket.side_effect = NotFound('dang') + + self.assertIsNotNone(self.storage.bucket) + self.storage._client.create_bucket.assert_called_with(self.bucket_name) + + def test_bucket_always_get_bucket_false(self): + """ + If auto_create_bucket is False getting a bucket property should not bother + with checking for bucket existence. + + If permissions are limited. Call to get_bucket (below) results in: + 403 Caller does not have storage.buckets.get access to bucket. + Even if bucket actually exists + + Let it fail later on usage of non-existent buckets. + + This prevents preemptive fails when client user does not have privileges to create buckets. + """ + self.storage.always_get_bucket = False + self.storage._client = mock.MagicMock() + + bucket = self.storage.bucket + self.assertFalse(self.storage._client.get_bucket.called) + self.assertIsNotNone(bucket) + self.assertEqual(bucket.name, self.bucket_name) + self.assertEqual(bucket.client, self.storage._client) + def test_listdir(self): file_names = ["some/path/1.txt", "2.txt", "other/path/3.txt", "4.txt"] subdir = "" diff --git a/tox.ini b/tox.ini index 63118553d..a9a568306 100644 --- a/tox.ini +++ b/tox.ini @@ -26,6 +26,7 @@ deps = boto3 dropbox google-cloud-storage + cryptography<2.0 ; python_version < '3.4' paramiko [testenv:flake8] @@ -35,3 +36,6 @@ deps = commands = flake8 isort --recursive --check-only --diff storages/ tests/ + +[pytest] +DJANGO_SETTINGS_MODULE = tests.settings