Repository: jschneier/django-storages

Issue:
No way to upload files with headers using Google Cloud Storage
I'm using Google Cloud Storage and currently have to either manually or script the setting of my Content-Disposition headers. The amazon module supports this but GCS does not?
I believe it should be implemented in a similar way, 
```python
GS_HEADERS = {
    'Expires': 'Thu, 15 Apr 2010 20:00:00 GMT',
    'Cache-Control': 'max-age=86400',
    'Content-Disposition': 'attachment',
}
```


Discussion:
@ReconCubed  do you want something different than what was fixed in #411? 
Do you want to set all headers not just Cache-control?
@sww314 Yes, I'd like to be able to modify the content-disposition mainly.
There's no way to set headers using Azure Storage either.
This is now possible on Azure as of v1.11 (see #898). Just add the following to your Django configuration:
```py
AZURE_OBJECT_PARAMETERS = {
    'content_disposition': 'attachment'
}
```

Resolving patch:
diff --git a/storages/backends/gcloud.py b/storages/backends/gcloud.py
--- a/storages/backends/gcloud.py
+++ b/storages/backends/gcloud.py
@@ -1,4 +1,5 @@
 import mimetypes
+import warnings
 from datetime import timedelta
 from tempfile import SpooledTemporaryFile
 
@@ -23,6 +24,9 @@
                                "See https://github.com/GoogleCloudPlatform/gcloud-python")
 
 
+CONTENT_TYPE = 'content_type'
+
+
 class GoogleCloudFile(File):
     def __init__(self, name, mode, storage):
         self.name = name
@@ -106,6 +110,7 @@ def get_default_settings(self):
             "expiration": setting('GS_EXPIRATION', timedelta(seconds=86400)),
             "file_overwrite": setting('GS_FILE_OVERWRITE', True),
             "cache_control": setting('GS_CACHE_CONTROL'),
+            "object_parameters": setting('GS_OBJECT_PARAMETERS', {}),
             # The max amount of memory a returned file can take up before being
             # rolled over into a temporary file on disk. Default is 0: Do not
             # roll over.
@@ -154,12 +159,34 @@ def _save(self, name, content):
 
         content.name = cleaned_name
         file = GoogleCloudFile(name, 'rw', self)
-        file.blob.cache_control = self.cache_control
-        file.blob.upload_from_file(
-            content, rewind=True, size=content.size,
-            content_type=file.mime_type, predefined_acl=self.default_acl)
+
+        upload_params = {}
+        blob_params = self.get_object_parameters(name, content)
+        if 'cache_control' not in blob_params and self.cache_control:
+            warnings.warn(
+                'The GS_CACHE_CONTROL setting is deprecated. Use GS_OBJECT_PARAMETERS to set any '
+                'writable blob property or override GoogleCloudStorage.get_object_parameters to '
+                'vary the parameters per object.', DeprecationWarning
+            )
+            blob_params['cache_control'] = self.cache_control
+
+        upload_params['predefined_acl'] = blob_params.pop('acl', self.default_acl)
+        if CONTENT_TYPE not in blob_params:
+            upload_params[CONTENT_TYPE] = file.mime_type
+
+        for prop, val in blob_params.items():
+            setattr(file.blob, prop, val)
+
+        file.blob.upload_from_file(content, rewind=True, size=content.size, **upload_params)
         return cleaned_name
 
+    def get_object_parameters(self, name, content):
+        """Override this to return a dictionary of overwritable blob-property to value.
+
+        Returns GS_OBJECT_PARAMETRS by default. See the docs for all possible options.
+        """
+        return self.object_parameters.copy()
+
     def delete(self, name):
         name = self._normalize_name(clean_name(name))
         try:


Test patch: