summaryrefslogtreecommitdiff
path: root/storages/backends/database.py
diff options
context:
space:
mode:
Diffstat (limited to 'storages/backends/database.py')
-rw-r--r--storages/backends/database.py132
1 files changed, 132 insertions, 0 deletions
diff --git a/storages/backends/database.py b/storages/backends/database.py
new file mode 100644
index 0000000..3dd1259
--- /dev/null
+++ b/storages/backends/database.py
@@ -0,0 +1,132 @@
+# DatabaseStorage for django.
+# 2009 (c) GameKeeper Gambling Ltd, Ivanov E.
+import StringIO
+import urlparse
+
+from django.conf import settings
+from django.core.files import File
+from django.core.files.storage import Storage
+from django.core.exceptions import ImproperlyConfigured
+
+try:
+ import pyodbc
+except ImportError:
+ raise ImproperlyConfigured("Could not load pyodbc dependency.\
+ \nSee http://code.google.com/p/pyodbc/")
+
+REQUIRED_FIELDS = ('db_table', 'fname_column', 'blob_column', 'size_column', 'base_url')
+
+class DatabaseStorage(Storage):
+ """
+ Class DatabaseStorage provides storing files in the database.
+ """
+
+ def __init__(self, option=settings.DB_FILES):
+ """Constructor.
+
+ Constructs object using dictionary either specified in contucotr or
+in settings.DB_FILES.
+
+ @param option dictionary with 'db_table', 'fname_column',
+'blob_column', 'size_column', 'base_url' keys.
+
+ option['db_table']
+ Table to work with.
+ option['fname_column']
+ Column in the 'db_table' containing filenames (filenames can
+contain pathes). Values should be the same as where FileField keeps
+filenames.
+ It is used to map filename to blob_column. In sql it's simply
+used in where clause.
+ option['blob_column']
+ Blob column (for example 'image' type), created manually in the
+'db_table', used to store image.
+ option['size_column']
+ Column to store file size. Used for optimization of size()
+method (another way is to open file and get size)
+ option['base_url']
+ Url prefix used with filenames. Should be mapped to the view,
+that returns an image as result.
+ """
+
+ if not option or not all([field in option for field in REQUIRED_FIELDS]):
+ raise ValueError("You didn't specify required options")
+
+ self.db_table = option['db_table']
+ self.fname_column = option['fname_column']
+ self.blob_column = option['blob_column']
+ self.size_column = option['size_column']
+ self.base_url = option['base_url']
+
+ #get database settings
+ self.DATABASE_ODBC_DRIVER = settings.DATABASE_ODBC_DRIVER
+ self.DATABASE_NAME = settings.DATABASE_NAME
+ self.DATABASE_USER = settings.DATABASE_USER
+ self.DATABASE_PASSWORD = settings.DATABASE_PASSWORD
+ self.DATABASE_HOST = settings.DATABASE_HOST
+
+ self.connection = pyodbc.connect('DRIVER=%s;SERVER=%s;DATABASE=%s;UID=%s;PWD=%s'%(self.DATABASE_ODBC_DRIVER,self.DATABASE_HOST,self.DATABASE_NAME,
+ self.DATABASE_USER, self.DATABASE_PASSWORD) )
+ self.cursor = self.connection.cursor()
+
+ def _open(self, name, mode='rb'):
+ """Open a file from database.
+
+ @param name filename or relative path to file based on base_url. path should contain only "/", but not "\". Apache sends pathes with "/".
+ If there is no such file in the db, returs None
+ """
+
+ assert mode == 'rb', "You've tried to open binary file without specifying binary mode! You specified: %s"%mode
+
+ row = self.cursor.execute("SELECT %s from %s where %s = '%s'"%(self.blob_column,self.db_table,self.fname_column,name) ).fetchone()
+ if row is None:
+ return None
+ inMemFile = StringIO.StringIO(row[0])
+ inMemFile.name = name
+ inMemFile.mode = mode
+
+ retFile = File(inMemFile)
+ return retFile
+
+ def _save(self, name, content):
+ """Save 'content' as file named 'name'.
+
+ @note '\' in path will be converted to '/'.
+ """
+
+ name = name.replace('\\', '/')
+ binary = pyodbc.Binary(content.read())
+ size = len(binary)
+
+ #todo: check result and do something (exception?) if failed.
+ if self.exists(name):
+ self.cursor.execute("UPDATE %s SET %s = ?, %s = ? WHERE %s = '%s'"%(self.db_table,self.blob_column,self.size_column,self.fname_column,name),
+ (binary, size) )
+ else:
+ self.cursor.execute("INSERT INTO %s VALUES(?, ?, ?)"%(self.db_table), (name, binary, size) )
+ self.connection.commit()
+ return name
+
+ def exists(self, name):
+ row = self.cursor.execute("SELECT %s from %s where %s = '%s'"%(self.fname_column,self.db_table,self.fname_column,name)).fetchone()
+ return row is not None
+
+ def get_available_name(self, name):
+ return name
+
+ def delete(self, name):
+ if self.exists(name):
+ self.cursor.execute("DELETE FROM %s WHERE %s = '%s'"%(self.db_table,self.fname_column,name))
+ self.connection.commit()
+
+ def url(self, name):
+ if self.base_url is None:
+ raise ValueError("This file is not accessible via a URL.")
+ return urlparse.urljoin(self.base_url, name).replace('\\', '/')
+
+ def size(self, name):
+ row = self.cursor.execute("SELECT %s from %s where %s = '%s'"%(self.size_column,self.db_table,self.fname_column,name)).fetchone()
+ if row is None:
+ return 0
+ else:
+ return int(row[0])