Compare commits
No commits in common. "be8954b50563c9fc87176ec34347074e361289bb" and "c67d636d2457faa57644cd812ca1b5a916e23766" have entirely different histories.
be8954b505
...
c67d636d24
@ -4,4 +4,3 @@ from django.apps import AppConfig
|
|||||||
class BYOStorageConfig(AppConfig):
|
class BYOStorageConfig(AppConfig):
|
||||||
name = 'byostorage'
|
name = 'byostorage'
|
||||||
verbose_name = 'Bring your own storage'
|
verbose_name = 'Bring your own storage'
|
||||||
default_auto_field = 'django.db.models.AutoField'
|
|
||||||
|
|||||||
@ -5,15 +5,11 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||||||
|
|
||||||
MEDIA_ROOT = "media"
|
MEDIA_ROOT = "media"
|
||||||
|
|
||||||
DEBUG = True
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'byostorage'
|
'byostorage'
|
||||||
]
|
]
|
||||||
|
|
||||||
SECRET_KEY = 'shh!'
|
SECRET_KEY = 'shh!'
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
# Generated by Django 3.2.7 on 2021-09-17 00:00
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('byostorage', '0003_auto_20210323_1047'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='userstorage',
|
|
||||||
name='id',
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='userstorage',
|
|
||||||
name='name',
|
|
||||||
field=models.SlugField(help_text='Storage tag', max_length=20, primary_key=True, serialize=False),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,15 +1,9 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.fields.files import FieldFile
|
from django.core.files.storage import get_storage_class, FileSystemStorage
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.core.files.storage import Storage, get_storage_class
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import logging
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
STORAGE_CLASSES = getattr(settings, 'STORAGE_CLASSES', [])
|
STORAGE_CLASSES = getattr(settings, 'STORAGE_CLASSES', [])
|
||||||
|
|
||||||
STORAGE_CLASSES.extend([
|
STORAGE_CLASSES.extend([
|
||||||
@ -17,7 +11,7 @@ STORAGE_CLASSES.extend([
|
|||||||
])
|
])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import storages.backends
|
import storages
|
||||||
STORAGE_CLASSES.append('storages.backends.s3boto3.S3Boto3Storage')
|
STORAGE_CLASSES.append('storages.backends.s3boto3.S3Boto3Storage')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
@ -30,15 +24,6 @@ def validate_json(value):
|
|||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
raise ValidationError(e.msg)
|
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):
|
class UserStorage(models.Model):
|
||||||
""" A user defined storage
|
""" A user defined storage
|
||||||
"""
|
"""
|
||||||
@ -46,7 +31,7 @@ class UserStorage(models.Model):
|
|||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
null=True, blank=True)
|
null=True, blank=True)
|
||||||
name = models.SlugField(max_length=20,
|
name = models.SlugField(max_length=20,
|
||||||
primary_key=True,
|
unique=True,
|
||||||
help_text="Storage tag")
|
help_text="Storage tag")
|
||||||
storage = models.CharField(max_length=255,
|
storage = models.CharField(max_length=255,
|
||||||
choices=STORAGE_CLASS_OPTIONS,
|
choices=STORAGE_CLASS_OPTIONS,
|
||||||
@ -91,44 +76,3 @@ class UserStorage(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,17 +2,46 @@ from django.test import TestCase
|
|||||||
from django.core.files.storage import FileSystemStorage
|
from django.core.files.storage import FileSystemStorage
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from byostorage.models import UserStorage
|
from .multi import MultiStorage
|
||||||
from byostorage.user import BYOStorage
|
from .models import UserStorage
|
||||||
|
from .user import BYOStorage
|
||||||
#from example.models import LinkedStorageTestModel
|
from .http import HTTPStorage
|
||||||
|
|
||||||
import tempfile
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
#import logging
|
#import logging
|
||||||
#logging.basicConfig(level=logging.DEBUG)
|
#logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
class MultiStorageTestCase(TestCase):
|
||||||
|
|
||||||
|
def test_storage_selection(self):
|
||||||
|
ms = MultiStorage({'one': {'location': 'storage/one'}, 'two': {}})
|
||||||
|
self.assertEqual(str(ms), "<MultiStorage: 2 storages>")
|
||||||
|
|
||||||
|
# use the default if nothing set
|
||||||
|
default = ms.get_storage('foo')
|
||||||
|
self.assertIsInstance(default, FileSystemStorage)
|
||||||
|
self.assertEqual(default.base_location, 'media')
|
||||||
|
|
||||||
|
# get first instance
|
||||||
|
one = ms.get_storage('one')
|
||||||
|
self.assertEqual(one.base_location, 'storage/one')
|
||||||
|
|
||||||
|
def test_storage_proxies(self):
|
||||||
|
ms = MultiStorage({
|
||||||
|
'one': {'location': 'storage/one', 'base_url': 'http://one'},
|
||||||
|
'two': {'location': 'storage/two', 'base_url': 'http://two'}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
self.assertEqual(ms.url("one:foo.txt"), 'http://one/foo.txt')
|
||||||
|
self.assertEqual(ms.url("two:foo.txt"), 'http://two/foo.txt')
|
||||||
|
|
||||||
|
def test_with_http(self):
|
||||||
|
ms = MultiStorage({
|
||||||
|
'https': {'storage': 'byostorage.http.HTTPStorage', 'protocol': 'https'}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual(ms.url('https://google.com'), 'https://google.com')
|
||||||
|
|
||||||
class UserStorageTestCase(TestCase):
|
class UserStorageTestCase(TestCase):
|
||||||
|
|
||||||
def test_construction(self):
|
def test_construction(self):
|
||||||
@ -34,10 +63,10 @@ class UserStorageTestCase(TestCase):
|
|||||||
instance = UserStorage.objects.create(name='one', storage='storages.backends.s3boto3.S3Boto3Storage',
|
instance = UserStorage.objects.create(name='one', storage='storages.backends.s3boto3.S3Boto3Storage',
|
||||||
settings_data='''
|
settings_data='''
|
||||||
{
|
{
|
||||||
"access_key": "minioadmin",
|
"access_key": "polyphonic_test_key",
|
||||||
"secret_key": "minioadmin",
|
"secret_key": "polyphonic_secret",
|
||||||
"endpoint_url": "http://localhost:9000",
|
"endpoint_url": "http://localhost:9000",
|
||||||
"bucket_name": "byostorage_test"
|
"bucket_name": "personal"
|
||||||
}
|
}
|
||||||
''')
|
''')
|
||||||
|
|
||||||
@ -47,10 +76,10 @@ class UserStorageTestCase(TestCase):
|
|||||||
UserStorage.objects.create(name='one', storage='storages.backends.s3boto3.S3Boto3Storage',
|
UserStorage.objects.create(name='one', storage='storages.backends.s3boto3.S3Boto3Storage',
|
||||||
settings_data='''
|
settings_data='''
|
||||||
{
|
{
|
||||||
"access_key": "minioadmin",
|
"access_key": "polyphonic_test_key",
|
||||||
"secret_key": "the_wrong_secret",
|
"secret_key": "the_wrong_secret",
|
||||||
"endpoint_url": "http://localhost:9000",
|
"endpoint_url": "http://localhost:9000",
|
||||||
"bucket_name": "byostorage_test"
|
"bucket_name": "missing"
|
||||||
}
|
}
|
||||||
''')
|
''')
|
||||||
self.fail("Should have raised an exception")
|
self.fail("Should have raised an exception")
|
||||||
@ -84,29 +113,28 @@ class BYOStorageTestCase(TestCase):
|
|||||||
one = storage.get_storage('one')
|
one = storage.get_storage('one')
|
||||||
self.assertEqual(one.base_location, 'other/one')
|
self.assertEqual(one.base_location, 'other/one')
|
||||||
|
|
||||||
"""
|
|
||||||
class BYOStorageFieldTestCase(TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
class HTTPStorageTestCase(TestCase):
|
||||||
self.test_dir = tempfile.TemporaryDirectory()
|
|
||||||
self.one = UserStorage.objects.create(name='one', storage='django.core.files.storage.FileSystemStorage',
|
|
||||||
settings_data = '{"location": "%s/one"}' % self.test_dir.name)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def test_url(self):
|
||||||
self.test_dir.cleanup()
|
s = HTTPStorage()
|
||||||
|
|
||||||
def test_linked_storage(self):
|
self.assertEqual(s.url('//google.com'), 'https://google.com')
|
||||||
|
|
||||||
from django.core.files.uploadedfile import UploadedFile
|
def test_exists(self):
|
||||||
|
s = HTTPStorage()
|
||||||
|
|
||||||
with open(__file__, 'rb') as f:
|
self.assertTrue(s.exists('//gitea.tfconsulting.com.au/tris'))
|
||||||
result = LinkedStorageTestModel.objects.create(name="first", my_storage_id=self.one.pk, my_file=UploadedFile(f))
|
self.assertFalse(s.exists('//gitea.tfconsulting.com.au/foo.txt'))
|
||||||
|
|
||||||
expected = os.path.basename(__file__)
|
def test_save(self):
|
||||||
|
s = HTTPStorage()
|
||||||
|
with self.assertRaisesMessage(NotImplementedError, "Unable to save to web locations"):
|
||||||
|
s.save('//gitea.tfconsulting.com.au/foo', 'Some content')
|
||||||
|
|
||||||
self.assertEqual(result.my_file.name, expected)
|
def test_open(self):
|
||||||
self.assertGreater(result.my_file.size, 0)
|
s = HTTPStorage()
|
||||||
|
with s.open('//gitea.tfconsulting.com.au', 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
self.assertEqual(os.listdir(os.path.join(self.test_dir.name, 'one')), [expected])
|
self.assertTrue(len(data) > 4000)
|
||||||
|
|
||||||
"""
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
from byostorage.http import HTTPStorage
|
|
||||||
|
|
||||||
class HTTPStorageTestCase(TestCase):
|
|
||||||
|
|
||||||
def test_url(self):
|
|
||||||
s = HTTPStorage()
|
|
||||||
|
|
||||||
self.assertEqual(s.url('//google.com'), 'https://google.com')
|
|
||||||
|
|
||||||
def test_exists(self):
|
|
||||||
s = HTTPStorage()
|
|
||||||
|
|
||||||
self.assertTrue(s.exists('//gitea.tfconsulting.com.au/tris'))
|
|
||||||
self.assertFalse(s.exists('//gitea.tfconsulting.com.au/foo.txt'))
|
|
||||||
|
|
||||||
def test_save(self):
|
|
||||||
s = HTTPStorage()
|
|
||||||
with self.assertRaisesMessage(NotImplementedError, "Unable to save to web locations"):
|
|
||||||
s.save('//gitea.tfconsulting.com.au/foo', 'Some content')
|
|
||||||
|
|
||||||
def test_open(self):
|
|
||||||
s = HTTPStorage()
|
|
||||||
with s.open('//gitea.tfconsulting.com.au', 'rb') as f:
|
|
||||||
data = f.read()
|
|
||||||
|
|
||||||
self.assertTrue(len(data) > 4000)
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
from byostorage.multi import MultiStorage
|
|
||||||
from django.core.files.storage import FileSystemStorage
|
|
||||||
|
|
||||||
|
|
||||||
class MultiStorageTestCase(TestCase):
|
|
||||||
|
|
||||||
def test_storage_selection(self):
|
|
||||||
ms = MultiStorage({'one': {'location': 'storage/one'}, 'two': {}})
|
|
||||||
self.assertEqual(str(ms), "<MultiStorage: 2 storages>")
|
|
||||||
|
|
||||||
# use the default if nothing set
|
|
||||||
default = ms.get_storage('foo')
|
|
||||||
self.assertIsInstance(default, FileSystemStorage)
|
|
||||||
self.assertEqual(default.base_location, 'media')
|
|
||||||
|
|
||||||
# get first instance
|
|
||||||
one = ms.get_storage('one')
|
|
||||||
self.assertEqual(one.base_location, 'storage/one')
|
|
||||||
|
|
||||||
def test_storage_proxies(self):
|
|
||||||
ms = MultiStorage({
|
|
||||||
'one': {'location': 'storage/one', 'base_url': 'http://one'},
|
|
||||||
'two': {'location': 'storage/two', 'base_url': 'http://two'}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
self.assertEqual(ms.url("one:foo.txt"), 'http://one/foo.txt')
|
|
||||||
self.assertEqual(ms.url("two:foo.txt"), 'http://two/foo.txt')
|
|
||||||
|
|
||||||
def test_with_http(self):
|
|
||||||
ms = MultiStorage({
|
|
||||||
'https': {'storage': 'byostorage.http.HTTPStorage', 'protocol': 'https'}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertEqual(ms.url('https://google.com'), 'https://google.com')
|
|
||||||
@ -6,6 +6,7 @@ from .multi import MultiStorage
|
|||||||
class BYOStorage(MultiStorage):
|
class BYOStorage(MultiStorage):
|
||||||
''' Database driven Bring-Your-Own-Storage
|
''' Database driven Bring-Your-Own-Storage
|
||||||
|
|
||||||
|
Multiple storages can
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, config=None):
|
def __init__(self, config=None):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user