2024-08-26 11:37:29 +10:00

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)