145 lines
4.2 KiB
Python
145 lines
4.2 KiB
Python
from django.conf import settings
|
|
from django.db import models
|
|
from django.db.models.fields.files import FieldFile
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.core.files.storage import Storage
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils.module_loading import import_string
|
|
import json
|
|
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
STORAGE_CLASSES = getattr(settings, 'STORAGE_CLASSES', [])
|
|
|
|
STORAGE_CLASSES.extend([
|
|
'django.core.files.storage.FileSystemStorage',
|
|
])
|
|
|
|
try:
|
|
import storages.backends
|
|
|
|
STORAGE_CLASSES.extend([
|
|
'storages.backends.s3boto3.S3Boto3Storage',
|
|
'storages.backends.azure_storage.AzureStorage',
|
|
'storages.backends.dropbox.DropBoxStorage',
|
|
'storages.backends.ftp.FTPStorage',
|
|
'storages.backends.gcloud.GoogleCloudStorage',
|
|
'storages.backends.sftpstorage.SFTPStorage',
|
|
])
|
|
except ImportError:
|
|
pass
|
|
|
|
STORAGE_CLASS_OPTIONS = [ (x, x) for x in STORAGE_CLASSES ]
|
|
|
|
def validate_json(value):
|
|
try:
|
|
json.loads(value)
|
|
except json.JSONDecodeError as e:
|
|
raise ValidationError(e.msg)
|
|
|
|
def resolve_instance(obj, relation):
|
|
for p in relation.split('__'):
|
|
obj = getattr(obj, p)
|
|
|
|
if callable(obj):
|
|
obj = obj()
|
|
|
|
return obj
|
|
|
|
class UserStorage(models.Model):
|
|
""" A user defined storage
|
|
"""
|
|
owner = models.ForeignKey('auth.User',
|
|
on_delete=models.CASCADE,
|
|
null=True, blank=True)
|
|
name = models.SlugField(max_length=20,
|
|
primary_key=True,
|
|
help_text="Storage tag")
|
|
storage = models.CharField(max_length=255,
|
|
choices=STORAGE_CLASS_OPTIONS,
|
|
help_text="Storage class for this instance")
|
|
settings_data = models.TextField(
|
|
validators=[validate_json],
|
|
default='{}',
|
|
help_text="JSON dict with key/value settings")
|
|
|
|
def instance(self):
|
|
return import_string(self.storage)(**self.settings)
|
|
|
|
@property
|
|
def settings(self):
|
|
if not self.settings_data:
|
|
return {}
|
|
try:
|
|
return json.loads(self.settings_data)
|
|
except Exception as e:
|
|
raise ValueError("Error in settings for storage '{0}' [{1}]".format(self.name, e))
|
|
|
|
def clean(self):
|
|
try:
|
|
self.test_storage()
|
|
except Exception as e:
|
|
raise ValidationError(str(e))
|
|
|
|
def test_storage(self):
|
|
# just do something that requires connection
|
|
try:
|
|
self.instance().listdir('')
|
|
except FileNotFoundError:
|
|
# FileSystemStorage doesn't create the base_dir until write
|
|
pass
|
|
return True
|
|
|
|
def save(self, *args, **kwargs):
|
|
# ensure the settings JSON isvalid
|
|
if self.settings_data:
|
|
validate_json(self.settings_data)
|
|
|
|
super(UserStorage, self).save(*args, **kwargs)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class BYOFieldFile(FieldFile):
|
|
|
|
def __init__(self, instance, field, name):
|
|
logger.debug("BYOFieldFile(%r, %r, %r)", instance, field, name)
|
|
super().__init__(instance, field, name)
|
|
try:
|
|
self.storage = field.get_storage(instance)
|
|
logger.debug("Using BYO storage: %r", self.storage)
|
|
except ObjectDoesNotExist:
|
|
logger.debug("Unable to select BYO storage")
|
|
self.storage = None # trigger error if try to save etc
|
|
|
|
class BYOStorageField(models.FileField):
|
|
attr_class = BYOFieldFile
|
|
|
|
def __init__(self, storage_instance, *args, **kwargs):
|
|
self.storage_instance = storage_instance
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def deconstruct(self):
|
|
name, path, args, kwargs = super().deconstruct()
|
|
args = [self.storage_instance] + args
|
|
return name, path, args, kwargs
|
|
|
|
def get_storage(self, instance):
|
|
|
|
if callable(self.storage_instance):
|
|
storage = self.storage_instance(instance)
|
|
else:
|
|
storage = resolve_instance(instance, self.storage_instance)
|
|
|
|
if not isinstance(storage, Storage):
|
|
raise RuntimeError("Not a storage instance")
|
|
|
|
return storage
|
|
|
|
#def formfield(self, **kwargs):
|
|
# return BYOStorageFormField(**kwargs)
|
|
|
|
|