from django.core.files.storage import Storage, default_storage, storages from django.conf import settings from django.utils.deconstruct import deconstructible from django.utils.module_loading import import_string ''' TODO: Create a signal to remove instances from cache if modified ''' import logging logger = logging.getLogger(__name__) @deconstructible class MultiStorage(Storage): ''' Django storage class that proxies multiple storage classes. Uses a name prefix to determine store e.g. 'images:foo/bar.jpg' ''' DELIM = ":" def __init__(self, config=None): self.config = config or getattr(settings, 'BRING_YOUR_OWN_STORAGE', {}) self._cache = {} def get_storage(self, name): if name not in self._cache: try: config = self.config[name] except KeyError: return default_storage klass = import_string(config.pop('storage', 'django.core.files.storage.FileSystemStorage')) self._cache[name] = klass(**config) return self._cache[name] def split(self, name): try: storage, p = name.split(self.DELIM, 1) return storage, p except ValueError: raise ValueError(f"Bad storage path [{name}]") def join(self, storage, name): return self.DELIM.join((storage, name)) def _proxy(self, method, name, *args, **kwargs): storage, p = self.split(name) result = getattr(self.get_storage(storage), method)(p, *args, **kwargs) logger.debug("Proxy: %s(%r, *%r, **%r) => %r", method, p, args, kwargs, result) return result def _proxy_name(self, method, name, *args, **kwargs): storage, p = self.split(name) result = getattr(self.get_storage(storage), method)(p, *args, **kwargs) logger.debug("Proxy: %s(%r, *%r, **%r) => %s:%s", method, p, args, kwargs, storage, result) return self.join(storage, result) def _open(self, name, mode='rb'): return self._proxy('open', name, mode) def path(self, name): return self._proxy('path', name) def save(self, name, content, max_length=None): return self._proxy_name('save', name, content, max_length) def exists(self, name): return self._proxy('exists', name) def delete(self, name): return self._proxy('delete', name) def url(self, name): return self._proxy('url', name) def get_accessed_time(self, name): return self._proxy('get_accessed_time', name) def get_alternative_name(self, file_root, file_ext): return self._proxy_name('get_alternative_name', file_root, file_ext) def get_available_name(self, name, max_length=None): return self._proxy_name('get_available_name', name, max_length) def get_modified_time(self, name): return self._proxy('get_modified_time', name) def __str__(self): return "".format(len(self.config))