Repository: jschneier/django-storages

Issue:
collectstatic -c fails since django 1.9 for s3 storage
it seems that since django 1.9 specificaly this PR  https://github.com/django/django/commit/87d78241a2fc85e5715fb51c554fe06e91deee58 it breaks the "collectstatic -c"  since it tries to validate that the root directory exists (which is empty in out case) because the bucket.new_key fails if the key name is empty

In the case of django-storages, maybe we can check if the bucket exists or just return true if its empty? (I think I would go for the true if empty)

Here is the Traceback:

```
Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/app/.heroku/python/lib/python3.4/site-packages/django/core/management/__init__.py", line 353, in execute_from_command_line
    utility.execute()
  File "/app/.heroku/python/lib/python3.4/site-packages/django/core/management/__init__.py", line 345, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/app/.heroku/python/lib/python3.4/site-packages/django/core/management/base.py", line 348, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/app/.heroku/python/lib/python3.4/site-packages/django/core/management/base.py", line 399, in execute
    output = self.handle(*args, **options)
  File "/app/.heroku/python/lib/python3.4/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 176, in handle
    collected = self.collect()
  File "/app/.heroku/python/lib/python3.4/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 89, in collect
    self.clear_dir('')
  File "/app/.heroku/python/lib/python3.4/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 210, in clear_dir
    if not self.storage.exists(path):
  File "/app/.heroku/python/lib/python3.4/site-packages/storages/backends/s3boto.py", line 433, in exists
    k = self.bucket.new_key(self._encode_name(name))
  File "/app/.heroku/python/lib/python3.4/site-packages/boto/s3/bucket.py", line 622, in new_key
    raise ValueError('Empty key names are not allowed')
ValueError: Empty key names are not allowed
```



Discussion:
Thanks for the report. I'm torn between the 2 possible fixes. From a strictly correctness standpoint I think that checking for bucket existence makes more sense. The only time that doesn't matter in this case is when you have set the setting `AWS_AUTO_CREATE_BUCKET`.

Side note: looks like we should be using `get_key` for checking existence.


Resolving patch:
diff --git a/storages/backends/s3boto.py b/storages/backends/s3boto.py
--- a/storages/backends/s3boto.py
+++ b/storages/backends/s3boto.py
@@ -428,6 +428,13 @@ def delete(self, name):
         self.bucket.delete_key(self._encode_name(name))
 
     def exists(self, name):
+        if not name:  # root element aka the bucket
+            try:
+                self.bucket
+                return True
+            except ImproperlyConfigured:
+                return False
+
         name = self._normalize_name(self._clean_name(name))
         if self.entries:
             return name in self.entries


Test patch:
diff --git a/tests/test_s3boto.py b/tests/test_s3boto.py
--- a/tests/test_s3boto.py
+++ b/tests/test_s3boto.py
@@ -10,6 +10,7 @@
 from django.core.files.base import ContentFile
 import django
 
+from boto.exception import S3ResponseError
 from boto.s3.key import Key
 from boto.utils import parse_ts, ISO8601
 
@@ -196,6 +197,13 @@ def test_storage_open_write(self):
         )
         file._multipart.complete_upload.assert_called_once()
 
+    def test_storage_exists_bucket(self):
+        self.storage._connection.get_bucket.side_effect = S3ResponseError(404, 'No bucket')
+        self.assertFalse(self.storage.exists(''))
+
+        self.storage._connection.get_bucket.side_effect = None
+        self.assertTrue(self.storage.exists(''))
+
     def test_storage_exists(self):
         key = self.storage.bucket.new_key.return_value
         key.exists.return_value = True