Compare commits

...
11 Commits
Author SHA1 Message Date
Tris Forster 9903bb0088 Updated cache to not use get_storage_class 2024-08-26 11:55:38 +10:00
Tris Forster f134df3384 Version bump 2024-08-26 11:49:10 +10:00
Tris Forster 92d64a57d2 get_storage_class removed in Django 5 2024-08-26 11:37:29 +10:00
tris 43db0b0983 Update 'README.rst' 2023-02-19 21:37:40 +11:00
Tris Forster 227ce5fccb Fixed passing Storage instance 2022-12-21 08:22:14 +11:00
Tris Forster 432118ac58 Version bump 2022-12-21 08:08:36 +11:00
Tris Forster 49646dfafa Dont cache if local 2022-12-21 08:03:40 +11:00
Tris 4d87edd995 Better defaults for user storage 2022-10-19 09:17:14 +11:00
Tris 4284b4a05f Added missing path() method 2022-10-19 09:02:34 +11:00
Tris 845762400f Add all storages classes 2022-09-10 15:25:13 +10:00
Tris 0ad2812ed6 Version bump 2022-09-10 14:49:14 +10:00
10 changed files with 92 additions and 20 deletions
+5
View File
@@ -23,3 +23,8 @@ Usage
photo = models.FileField(
storage=BYOStorage(),
upload_to=generate_filename)
Notes
=====
Boto3 client sometimes doesn't respect the `region_name` so it may be neccesary to set `endpoint_url: https://https://s3-<region>.amazonaws.com`
+2 -2
View File
@@ -1,2 +1,2 @@
__version__ = 0.1
default_app_config = 'byostorage.apps.BYOStorageConfig'
__version__ = '0.2.4'
default_app_config = 'byostorage.apps.BYOStorageConfig'
+14 -6
View File
@@ -1,5 +1,6 @@
from django.core.files.storage import Storage, get_storage_class
from django.core.files.storage import Storage, DefaultStorage
from django.utils.deconstruct import deconstructible
from django.utils.module_loading import import_string
from django.conf import settings
from hashlib import sha1
import os.path
@@ -15,11 +16,14 @@ class CachedStorage(Storage):
def __init__(self, remote=None, cachedir=None, expires=600):
if not remote:
remote = getattr(settings, 'CACHED_STORAGE_REMOTE', None)
remote = getattr(settings, 'CACHED_STORAGE_REMOTE', DefaultStorage)
if not cachedir:
cachedir = getattr(settings, 'CACHED_STORAGE_DIR', 'cache')
self.remote = get_storage_class(remote)()
if isinstance(remote, Storage):
self.remote = remote
else:
self.remote = import_string(remote)()
self.cachedir = cachedir
self.expires = expires
@@ -52,16 +56,20 @@ class CachedStorage(Storage):
def _open(self, name, mode='rb'):
assert 'r' in mode, "Can only open for reading"
p = self._cached(name)
p = self.path(name)
return open(p, mode)
def path(self, name):
return self._cached(name)
try:
return self.remote.path(name)
except NotImplementedError:
return self._cached(name)
def save(self, name, content, max_length=None):
p = self._filepath(name)
if os.path.exists(p):
os.unlink(p)
# TODO: cache content to p
return self.remote.save(name, content, max_length)
def delete(self, name):
@@ -0,0 +1,18 @@
# Generated by Django 3.2.7 on 2022-09-09 23:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('byostorage', '0004_auto_20210917_0000'),
]
operations = [
migrations.AlterField(
model_name='userstorage',
name='storage',
field=models.CharField(choices=[('django.core.files.storage.FileSystemStorage', 'django.core.files.storage.FileSystemStorage')], help_text='Storage class for this instance', max_length=255),
),
]
@@ -0,0 +1,19 @@
# Generated by Django 3.2.7 on 2022-09-10 00:10
import byostorage.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('byostorage', '0005_alter_userstorage_storage'),
]
operations = [
migrations.AlterField(
model_name='userstorage',
name='settings_data',
field=models.TextField(default='{}', help_text='JSON dict with key/value settings', validators=[byostorage.models.validate_json]),
),
]
+13 -3
View File
@@ -2,8 +2,9 @@ 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, get_storage_class
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
@@ -18,7 +19,15 @@ STORAGE_CLASSES.extend([
try:
import storages.backends
STORAGE_CLASSES.append('storages.backends.s3boto3.S3Boto3Storage')
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
@@ -53,10 +62,11 @@ class UserStorage(models.Model):
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 get_storage_class(self.storage)(**self.settings)
return import_string(self.storage)(**self.settings)
@property
def settings(self):
+8 -4
View File
@@ -1,6 +1,7 @@
from django.core.files.storage import Storage, default_storage, get_storage_class
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
@@ -28,7 +29,7 @@ class MultiStorage(Storage):
config = self.config[name]
except KeyError:
return default_storage
klass = get_storage_class(config.pop('storage', None))
klass = import_string(config.pop('storage', 'django.core.files.storage.FileSystemStorage'))
self._cache[name] = klass(**config)
return self._cache[name]
@@ -45,18 +46,21 @@ class MultiStorage(Storage):
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, name, args, kwargs, result)
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, name, args, kwargs, storage, result)
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)
+2 -1
View File
@@ -16,6 +16,7 @@ class HTTPStorageTestCase(TestCase):
self.assertFalse(s.exists('//gitea.tfconsulting.com.au/foo.txt'))
def test_save(self):
return # already caught as suspicious activity
s = HTTPStorage()
with self.assertRaisesMessage(NotImplementedError, "Unable to save to web locations"):
s.save('//gitea.tfconsulting.com.au/foo', 'Some content')
@@ -25,4 +26,4 @@ class HTTPStorageTestCase(TestCase):
with s.open('//gitea.tfconsulting.com.au', 'rb') as f:
data = f.read()
self.assertTrue(len(data) > 4000)
self.assertTrue(len(data) > 4000)
+8
View File
@@ -2,6 +2,7 @@ from django.test import TestCase
from byostorage.multi import MultiStorage
from django.core.files.storage import FileSystemStorage
import os.path
class MultiStorageTestCase(TestCase):
@@ -34,3 +35,10 @@ class MultiStorageTestCase(TestCase):
})
self.assertEqual(ms.url('https://google.com'), 'https://google.com')
def test_local_methods(self):
ms = MultiStorage({
'local': {'storage': 'django.core.files.storage.FileSystemStorage', 'location': 'local_store'}
})
self.assertEqual(ms.path('local:foo/bar.txt'), os.path.realpath('local_store/foo/bar.txt'))
+3 -4
View File
@@ -11,9 +11,8 @@ classifiers =
Development Status :: 1 - Development
Environment :: Web Environment
Framework :: Django
Framework :: Django :: 2.2
Framework :: Django :: 3.0
Framework :: Django :: 3.1
Framework :: Django :: 4.2
Framework :: Django :: 5.0
Intended Audience :: Developers
License :: OSI Approved :: BSD License
Operating System :: OS Independent
@@ -29,7 +28,7 @@ classifiers =
[options]
python_requires = >=3.5
install_requires =
Django >= 2.2
Django >= 4.2
packages =
byostorage
byostorage.migrations