Commits

David Larlet committed 3c2abbd

CouchDB: Initial addition of the storage. Thanks Christian Klein.

Comments (0)

Files changed (2)

     * Ariel Núñez (packaging)
     * Wim Leers (SymlinkOrCopy + patches)
     * Michael Elsdörfer (Overwrite + PEP8 compatibility)
+    * Christian Klein (CouchDB)
 
 Extra thanks to Marty for adding this in Django, 
 you can buy his very interesting book (Pro Django).

backends/couchdb.py

+"""
+This is a Custom Storage System for Django with CouchDB backend.
+Created by Christian Klein.
+(c) Copyright 2009 HUDORA GmbH. All Rights Reserved.
+"""
+import os
+from cStringIO import StringIO
+from urlparse import urljoin
+from urllib import quote_plus
+
+from django.conf import settings
+from django.core.files import File
+from django.core.files.storage import Storage
+
+try:
+    import couchdb.client
+except ImportError:
+    raise "You must install couchdb dependency. See http://code.google.com/p/couchdb-python/"
+
+DEFAULT_SERVER= getattr(settings, 'COUCHDB_DEFAULT_SERVER', 'http://couchdb.local:5984')
+STORAGE_OPTIONS= getattr(settings, 'COUCHDB_STORAGE_OPTIONS', {})
+
+
+class CouchDBStorage(Storage):
+    """
+    CouchDBStorage - a Django Storage class for CouchDB.
+
+    The CouchDBStorage can be configured in settings.py, e.g.::
+    
+        COUCHDB_STORAGE_OPTIONS = {
+            'server': "http://example.org", 
+            'database': 'database_name'
+        }
+
+    Alternatively, the configuration can be passed as a dictionary.
+    """
+    def __init__(self, **kwargs):
+        kwargs.update(STORAGE_OPTIONS)
+        self.base_url = kwargs.get('server', DEFAULT_SERVER)
+        server = couchdb.client.Server(self.base_url)
+        self.db = server[kwargs.get('database')]
+
+    def _put_file(self, name, content):
+        self.db[name] = {'size': len(content)}
+        self.db.put_attachment(self.db[name], content, filename='content')
+        return name
+
+    def get_document(self, name):
+        return self.db.get(name)
+
+    def _open(self, name, mode='rb'):
+        couchdb_file = CouchDBFile(name, self, mode=mode)
+        return couchdb_file
+
+    def _save(self, name, content):
+        content.open()
+        if hasattr(content, 'chunks'):
+            content_str = ''.join(chunk for chunk in content.chunks())
+        else:
+            content_str = content.read()
+        name = name.replace('/', '-')
+        return self._put_file(name, content_str)
+
+    def exists(self, name):
+        return name in self.db
+
+    def size(self, name):
+        doc = self.get_document(name)
+        if doc:
+            return doc['size']
+        return 0
+
+    def url(self, name):
+        return urljoin(self.base_url, 
+                       os.path.join(quote_plus(self.db.name), 
+                       quote_plus(name), 
+                       'content'))
+
+    def delete(self, name):
+        try:
+            del self.db[name]
+        except couchdb.client.ResourceNotFound:
+            raise IOError("File not found: %s" % name)
+
+    #def listdir(self, name):
+    # _all_docs?
+    #    pass
+
+
+class CouchDBFile(File):
+    """
+    CouchDBFile - a Django File-like class for CouchDB documents.
+    """
+
+    def __init__(self, name, storage, mode):
+        self._name = name
+        self._storage = storage
+        self._mode = mode
+        self._is_dirty = False
+
+        try:
+            self._doc = self._storage.get_document(name)
+
+            tmp, ext = os.path.split(name)
+            if ext:
+                filename = "content." + ext
+            else:
+                filename = "content"
+            attachment = self._storage.db.get_attachment(self._doc, filename=filename)
+            self.file = StringIO(attachment)
+        except couchdb.client.ResourceNotFound:
+            if 'r' in self._mode:
+                raise ValueError("The file cannot be reopened.")
+            else:
+                self.file = StringIO()
+                self._is_dirty = True
+
+    @property
+    def size(self):
+        return self._doc['size']
+
+    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._storage._put_file(self._name, self.file.getvalue())
+        self.file.close()
+
+