Commits

David Larlet committed 07f2aa0

S3Storage: first draft for the latest patch (5361-r8189.diff)

Comments (0)

Files changed (3)

 import os
 from mimetypes import guess_type
 
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
-from django.core.files.storage import Storage
-from django.core.files.remote import RemoteFile
+from django.core.files.storage import Storage, StorageFile
 from django.utils.functional import curry
 
 ACCESS_KEY_NAME = 'AWS_ACCESS_KEY_ID'
     def _get_connection(self):
         return AWSAuthConnection(*self._get_access_keys())
 
-    def _put_file(self, name, raw_contents):
+    def _put_file(self, name, content):
         content_type = guess_type(name)[0] or "application/x-octet-stream"
         self.headers.update({'x-amz-acl':  self.acl, 'Content-Type': content_type})
-        response = self.connection.put(self.bucket, name, raw_contents, self.headers)
+        response = self.connection.put(self.bucket, name, content, self.headers)
 
-    def path(self, name):
-        return self.generator.make_bare_url(self.bucket, name)
+    def _open(self, name, mode='rb'):
+        response = self.connection.get(self.bucket, name)
+        writer = curry(self._put_file, name)
+        #print response.object.data
+        remote_file = S3StorageFile(response.object.data, mode, writer)
+        remote_file.size = self.size(name)
+        return remote_file
+
+    def _save(self, name, content):
+        self._put_file(self.url(name), content.open())
     
-    def size(self, name):
-        response = self.connection._make_request('HEAD', self.bucket, name)
-        return int(response.getheader('Content-Length'))
-    
-    url = path
-    
+    def delete(self, name):
+        self.connection.delete(self.bucket, name)
+
     def exists(self, name):
         response = self.connection._make_request('HEAD', self.bucket, name)
         return response.status == 200
 
-    def _open(self, name, mode='rb'):
-        response = self.connection.get(self.bucket, name)
-        writer = curry(self._put_file, name)
-        return RemoteFile(response.object.data, mode, writer)
-
-    def save(self, name, raw_contents):
-        name = self.get_available_filename(name)
-        self._put_file(name, raw_contents)
-        return name
+    def size(self, name):
+        response = self.connection._make_request('HEAD', self.bucket, name)
+        content_length = response.getheader('Content-Length')
+        return content_length and int(content_length) or 0
     
-    def delete(self, name):
-        self.connection.delete(self.bucket, name)
+    def url(self, name):
+        return self.generator.make_bare_url(self.bucket, name)
 
     ## UNCOMMENT BELOW IF NECESSARY
-    #def get_available_filename(self, name):
+    #def get_available_name(self, name):
     #    """ Overwrite existing file with the same name. """
     #    return name
+
+
+class S3StorageFile(StorageFile):
+    def __init__(self, data, mode, writer):
+        self._mode = mode
+        self._write_to_storage = writer
+        self._is_dirty = False
+        self.file = StringIO(data)
+
+    def read(self, num_bytes=None):
+        return self.file.getvalue()
+
+    def write(self, content):
+        if 'w' not in self._mode:
+            raise AttributeError("File was opened for read-only access.")
+        self.file = StringIO(content)
+        self._is_dirty = True
+
+    def close(self):
+        if self._is_dirty:
+            self._write_to_storage(self.file.getvalue())
+        self.file.close()

storages_tests/models.py

-from django.db import models
-from django.core.files.storage import default_storage
 
-s3_storage = default_storage
-
-# Write out a file to be used as default content
-s3_storage.save('tests/default.txt',  'default content')
-
-
-class Storage(models.Model):
-    def custom_upload_to(self, filename):
-        return 'foo'
-
-    normal = models.FileField(storage=s3_storage, upload_to='tests')
-    custom = models.FileField(storage=s3_storage, upload_to=custom_upload_to)
-    default = models.FileField(storage=s3_storage, upload_to='tests', default='tests/default.txt')

storages_tests/tests.py

 
 Initialization::
 
-    >>> from django.core.files.remote import RemoteFile
-    >>> from django.core.files.storage import default_storage
-    >>> from django.core.cache import cache
-    >>> from models import Storage
+    >>> from django.core.files.storage import default_storage as s3_storage
 
-An object without a file has limited functionality::
+Standard file access options are available, and work as expected::
 
-    >>> obj1 = Storage()
-    >>> obj1.normal
-    <FieldFile: None>
-    >>> obj1.normal.size
-    Traceback (most recent call last):
-    ...
-    ValueError: The 'normal' attribute has no file associated with it.
-
-Saving a file enables full functionality::
-
-    >>> obj1.normal.save('django_test.txt', 'content')
-    >>> obj1.normal
-    <FieldFile: tests/django_test.txt>
-    >>> obj1.normal.size
-    7
-    >>> obj1.normal.read()
-    'content'
-
-Save another file with the same name::
-
-    >>> obj2 = Storage()
-    >>> obj2.normal.save('django_test.txt', 'more content')
-    >>> obj2.normal
-    <FieldFile: tests/django_test_.txt>
-    >>> obj2.normal.size
-    12
-
-Push the objects into the cache to make sure they pickle properly::
-
-    >>> cache.set('obj1', obj1)
-    >>> cache.set('obj2', obj2)
-    >>> cache.get('obj2').normal
-    <FieldFile: tests/django_test_.txt>
-
-Deleting an object deletes the file it uses, if there are no other objects
-still using that file::
-
-    >>> obj2.delete()
-    >>> obj2.normal.save('django_test.txt', 'more content')
-    >>> obj2.normal
-    <FieldFile: tests/django_test_.txt>
-
-Default values allow an object to access a single file::
-
-    >>> obj3 = Storage.objects.create()
-    >>> obj3.default
-    <FieldFile: tests/default.txt>
-    >>> obj3.default.read()
-    'default content'
-
-But it shouldn't be deleted, even if there are no more objects using it::
-
-    >>> obj3.delete()
-    >>> obj3 = Storage()
-    >>> obj3.default.read()
-    'default content'
-
-Clean up the temporary files::
-
-    >>> obj1.normal.delete()
-    >>> obj2.normal.delete()
-    >>> obj3.default.delete()
-"""
+    >>> s3_storage.exists('storage_test')
+    False
+    >>> file = s3_storage.open('storage_test', 'w')
+    >>> file.write('storage contents')
+    >>> file.close()
+    
+    >>> s3_storage.exists('storage_test')
+    True
+    >>> file = s3_storage.open('storage_test', 'r')
+    >>> file.read()
+    'storage contents'
+    >>> file.close()
+    
+    >>> s3_storage.delete('storage_test')
+    >>> s3_storage.exists('storage_test')
+    False
+"""