Anonymous avatar Anonymous committed 71ec014

added blobstore backend

Comments (0)

Files changed (4)

     datastore_path = options.get('datastore_path',
                                  dev_appserver_main.DEFAULT_ARGS['datastore_path'].replace(
                                  'dev_appserver', 'django_%s' % appid))
+    blobstore_path = options.get('blobstore_path',
+                                 dev_appserver_main.DEFAULT_ARGS['blobstore_path'].replace(
+                                 'dev_appserver', 'django_%s' % appid))
     history_path = options.get('history_path',
                                dev_appserver_main.DEFAULT_ARGS['history_path'].replace(
                                'dev_appserver', 'django_%s' % appid))
-    return datastore_path, history_path
+    return datastore_path, blobstore_path, history_path
 
 def get_test_datastore_paths(inmemory=True):
     """Returns a tuple with the path to the test datastore and history file.
     """
     if inmemory:
         return None, None
-    datastore_path, history_path = get_datastore_paths()
+    datastore_path, blobstore_path, history_path = get_datastore_paths()
     datastore_path = datastore_path.replace('.datastore', '.testdatastore')
+    blobstore_path = blobstore_path.replace('.blobstore', '.testblobstore')
     history_path = history_path.replace('.datastore', '.testdatastore')
-    return datastore_path, history_path
+    return datastore_path, blobstore, history_path
 
-def destroy_datastore(datastore_path, history_path):
+def destroy_datastore(*args):
     """Destroys the appengine datastore at the specified paths."""
-    for path in (datastore_path, history_path):
+    for path in args:
         if not path:
             continue
         try:
         if not have_appserver:
             from google.appengine.tools import dev_appserver_main
             args = dev_appserver_main.DEFAULT_ARGS.copy()
-            args['datastore_path'], args['history_path'] = self._get_paths()
+            args['datastore_path'], args['blobstore_path'], args['history_path'] = self._get_paths()
             from google.appengine.tools import dev_appserver
             dev_appserver.SetupStubs(appid, **args)
         # If we're supposed to set up the remote_api, do that now.

management/commands/runserver.py

     p = connection._get_paths()
     if '--datastore_path' not in args:
         args.extend(['--datastore_path', p[0]])
+    if '--blobstore_path' not in args:
+        args.extend(['--blobstore_path', p[1]])
     if '--history_path' not in args:
-        args.extend(['--history_path', p[1]])
+        args.extend(['--history_path', p[2]])
 
     # Reset logging level to INFO as dev_appserver will spew tons of debug logs
     logging.getLogger().setLevel(logging.INFO)
 else:
     EMAIL_BACKEND = 'djangoappengine.mail.EmailBackend'
 
+DEFAULT_FILE_STORAGE = 'djangoappengine.storage.BlobstoreStorage'
+DEFAULT_FILE_UPLOAD_BACKEND = 'djangoappengine.storage.prepare_upload'
+DEFAULT_FILE_SERVING_BACKEND = 'djangoappengine.storage.serve_file'
 FILE_UPLOAD_MAX_MEMORY_SIZE = 1024 * 1024
 FILE_UPLOAD_HANDLERS = (
+    'djangoappengine.storage.BlobstoreFileUploadHandler',
     'django.core.files.uploadhandler.MemoryFileUploadHandler',
 )
 
+import os
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+from django.conf import settings
+from django.core.files.base import File
+from django.core.files.storage import Storage
+from django.core.files.uploadedfile import UploadedFile
+from django.core.files.uploadhandler import FileUploadHandler, \
+    StopFutureHandlers
+from django.core.exceptions import ImproperlyConfigured
+from django.http import HttpResponse
+from django.utils.encoding import smart_str, force_unicode
+
+from google.appengine.ext.blobstore import BlobInfo, BlobKey, delete, \
+    create_upload_url, BLOB_KEY_HEADER
+
+def prepare_upload(url):
+    return create_upload_url(url)
+
+def serve_file(request, file, save_as, content_type):
+    if hasattr(file, 'file') and hasattr(file.file, 'blobstore_info'):
+        blobkey = file.file.blobstore_info.key()
+    elif hasattr(file, 'blobstore_info'):
+        blobkey = file.blobstore_info.key()
+    else:
+        raise ValueError("The provided file can't be served via the "
+                         "Google App Engine Blobstore.")
+    response = HttpResponse(content_type=content_type)
+    response[BLOB_KEY_HEADER] = str(blobkey)
+    if save_as:
+        response['Content-Disposition'] = smart_str(u'attachment; filename=%s' % save_as)
+    if file.size is not None:
+        response['Content-Length'] = file.size
+    return response
+
+class BlobstoreStorage(Storage):
+    """Google App Engine Blobstore storage backend"""
+
+    def _open(self, name, mode='rb'):
+        return BlobstoreFile(name, mode, self)
+
+    def _save(self, name, content):
+        name = name.replace('\\', '/')
+        if hasattr(content, 'file') and hasattr(content.file, 'blobstore_info'):
+            data = content.file.blobstore_info
+        elif hasattr(content, 'blobstore_info'):
+            data = content.blobstore_info
+        elif hasattr(content, 'chunks'):
+            data = ''.join(chunk for chunk in content.chunks())
+        else:
+            data = content.read()
+
+        if isinstance(data, (BlobInfo, BlobKey)):
+            # We change the file name to the BlobKey's str() value
+            if isinstance(data, BlobInfo):
+                data = data.key()
+            return '%s/%s' % (data, name.lstrip('/'))
+        else:
+            raise ValueError("The App Engine Blobstore only supports "
+                             "BlobInfo values. Data can't be uploaded "
+                             "directly. You have to use the file upload "
+                             "handler.")
+
+    def delete(self, name):
+        delete(self._get_key(name))
+
+    def exists(self, name):
+        return self._get_blobinfo(name) is not None
+
+    def size(self, name):
+        return self._get_blobinfo(name).size
+
+    def url(self, name):
+        raise NotImplementedError()
+
+    def get_valid_name(self, name):
+        return force_unicode(name).strip().replace('\\', '/')
+
+    def get_available_name(self, name):
+        return name.replace('\\', '/')
+
+    def _get_key(self, name):
+        return BlobKey(name.split('/', 1)[0])
+
+    def _get_blobinfo(self, name):
+        return BlobInfo.get(self._get_key(name))
+
+class BlobstoreFile(File):
+    def __init__(self, name, mode, storage):
+        self.name = name
+        self._storage = storage
+        self._mode = mode
+        self.blobstore_info = storage._get_blobinfo(name)
+
+    @property
+    def size(self):
+        return self.blobstore_info.size
+
+    def read(self, *args, **kwargs):
+        raise NotImplementedError()
+
+    def write(self, content):
+        raise NotImplementedError()
+
+    def close(self):
+        pass
+
+class BlobstoreFileUploadHandler(FileUploadHandler):
+    """
+    File upload handler for the Google App Engine Blobstore
+    """
+
+    def new_file(self, *args, **kwargs):
+        super(BlobstoreFileUploadHandler, self).new_file(*args, **kwargs)
+        access_type = self.content_type_extra.get('access-type')
+        blobkey = self.content_type_extra.get('blob-key')
+        self.active = access_type == 'X-AppEngine-BlobKey'
+        if self.active:
+            self.blobkey = BlobKey(blobkey)
+            raise StopFutureHandlers()
+
+    def receive_data_chunk(self, raw_data, start):
+        """
+        Add the data to the StringIO file.
+        """
+        if not self.active:
+            return raw_data
+
+    def file_complete(self, file_size):
+        """
+        Return a file object if we're activated.
+        """
+        if not self.active:
+            return
+
+        return BlobstoreUploadedFile(
+            blobinfo=BlobInfo(self.blobkey),
+            charset=self.charset)
+
+class BlobstoreUploadedFile(UploadedFile):
+    """
+    A file uploaded into memory (i.e. stream-to-memory).
+    """
+    def __init__(self, blobinfo, charset):
+        super(BlobstoreUploadedFile, self).__init__(
+            blobinfo, blobinfo.filename, blobinfo.content_type, blobinfo.size,
+            charset)
+        self.blobstore_info = blobinfo
+
+    def open(self, mode=None):
+        pass
+
+    def close(self):
+        pass
+
+    def chunks(self, chunk_size=None):
+        self.file.seek(0)
+        yield self.read()
+
+    def multiple_chunks(self, chunk_size=None):
+        # Since it's in memory, we'll never have multiple chunks.
+        return False
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.