diff --git a/compressor/cache.py b/compressor/cache.py index d04a9246..61523571 100644 --- a/compressor/cache.py +++ b/compressor/cache.py @@ -11,7 +11,7 @@ from django.utils.functional import SimpleLazyObject from compressor.conf import settings -from compressor.storage import default_storage +from compressor.storage import default_offline_manifest_storage from compressor.utils import get_mod_func _cachekey_func = None @@ -66,20 +66,15 @@ def get_offline_cachekey(source): return get_cachekey("offline.%s" % get_offline_hexdigest(source)) -def get_offline_manifest_filename(): - output_dir = settings.COMPRESS_OUTPUT_DIR.strip('/') - return os.path.join(output_dir, settings.COMPRESS_OFFLINE_MANIFEST) - - _offline_manifest = None def get_offline_manifest(): global _offline_manifest if _offline_manifest is None: - filename = get_offline_manifest_filename() - if default_storage.exists(filename): - with default_storage.open(filename) as fp: + filename = settings.COMPRESS_OFFLINE_MANIFEST + if default_offline_manifest_storage.exists(filename): + with default_offline_manifest_storage.open(filename) as fp: _offline_manifest = json.loads(fp.read().decode('utf8')) else: _offline_manifest = {} @@ -92,9 +87,8 @@ def flush_offline_manifest(): def write_offline_manifest(manifest): - filename = get_offline_manifest_filename() content = json.dumps(manifest, indent=2).encode('utf8') - default_storage.save(filename, ContentFile(content)) + default_offline_manifest_storage.save(settings.COMPRESS_OFFLINE_MANIFEST, ContentFile(content)) flush_offline_manifest() diff --git a/compressor/conf.py b/compressor/conf.py index 9a2d158a..7ebdbaee 100644 --- a/compressor/conf.py +++ b/compressor/conf.py @@ -75,6 +75,7 @@ class CompressorConf(AppConf): OFFLINE_CONTEXT = {} # The name of the manifest file (e.g. filename.ext) OFFLINE_MANIFEST = 'manifest.json' + OFFLINE_MANIFEST_STORAGE = 'compressor.storage.OfflineManifestFileStorage' # The Context to be used when TemplateFilter is used TEMPLATE_FILTER_CONTEXT = {} # Placeholder to be used instead of settings.COMPRESS_URL during offline compression. diff --git a/compressor/storage.py b/compressor/storage.py index 5d12eb8c..1bf58ddf 100644 --- a/compressor/storage.py +++ b/compressor/storage.py @@ -2,6 +2,7 @@ import os from datetime import datetime import time +from urllib.parse import urljoin from django.core.files.storage import FileSystemStorage, get_storage_class from django.utils.functional import LazyObject, SimpleLazyObject @@ -110,3 +111,20 @@ def _setup(self): default_storage = DefaultStorage() + + +class OfflineManifestFileStorage(CompressorFileStorage): + def __init__(self, location=None, base_url=None, *args, **kwargs): + if location is None: + location = os.path.join(settings.COMPRESS_ROOT, settings.COMPRESS_OUTPUT_DIR) + if base_url is None: + base_url = urljoin(settings.COMPRESS_URL, settings.COMPRESS_OUTPUT_DIR) + super().__init__(location, base_url, *args, **kwargs) + + +class DefaultOfflineManifestStorage(LazyObject): + def _setup(self): + self._wrapped = get_storage_class(settings.COMPRESS_OFFLINE_MANIFEST_STORAGE)() + + +default_offline_manifest_storage = DefaultOfflineManifestStorage() diff --git a/compressor/tests/test_offline.py b/compressor/tests/test_offline.py index 69363775..b7ff0fac 100644 --- a/compressor/tests/test_offline.py +++ b/compressor/tests/test_offline.py @@ -15,7 +15,7 @@ from compressor.cache import flush_offline_manifest, get_offline_manifest from compressor.exceptions import OfflineGenerationError from compressor.management.commands.compress import Command as CompressCommand -from compressor.storage import default_storage +from compressor.storage import default_offline_manifest_storage from compressor.utils import get_mod_func @@ -154,9 +154,9 @@ def setUp(self): def tearDown(self): self.override_settings.__exit__(None, None, None) - manifest_path = os.path.join('CACHE', 'manifest.json') - if default_storage.exists(manifest_path): - default_storage.delete(manifest_path) + manifest_filename = 'manifest.json' + if default_offline_manifest_storage.exists(manifest_filename): + default_offline_manifest_storage.delete(manifest_filename) def _prepare_contexts(self, engine): contexts = settings.COMPRESS_OFFLINE_CONTEXT @@ -311,9 +311,9 @@ def test_rendering_without_manifest_raises_exception_jinja2(self): def _test_deleting_manifest_does_not_affect_rendering(self, engine): count, result = CompressCommand().handle_inner(engines=[engine], verbosity=0) get_offline_manifest() - manifest_path = os.path.join('CACHE', 'manifest.json') - if default_storage.exists(manifest_path): - default_storage.delete(manifest_path) + manifest_filename = 'manifest.json' + if default_offline_manifest_storage.exists(manifest_filename): + default_offline_manifest_storage.delete(manifest_filename) self.assertEqual(1, count) self.assertEqual([self._render_script(self.expected_hash)], result) rendered_template = self._render_template(engine) diff --git a/compressor/tests/test_storages.py b/compressor/tests/test_storages.py index 9168510f..112c250a 100644 --- a/compressor/tests/test_storages.py +++ b/compressor/tests/test_storages.py @@ -67,3 +67,9 @@ def test_duplicate_save_overwrites_same_file(self): filename2 = self.default_storage.save('test.txt', ContentFile('yeah yeah')) self.assertEqual(filename1, filename2) self.assertNotIn("_", filename2) + + def test_offline_manifest_storage(self): + storage.default_offline_manifest_storage.save('test.txt', ContentFile('yeah yeah')) + self.assertTrue(os.path.exists(os.path.join(settings.COMPRESS_ROOT, 'CACHE', 'test.txt'))) + # Check that the file is stored at the same default location as before the new manifest storage. + self.assertTrue(self.default_storage.exists(os.path.join('CACHE', 'test.txt'))) diff --git a/docs/changelog.txt b/docs/changelog.txt index 0f1a4477..420906fd 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -1,6 +1,16 @@ Changelog ========= +Unreleased +---------- + +- New setting ``COMPRESS_OFFLINE_MANIFEST_STORAGE`` to customize the offline manifest's file storage (#1112) + + With this change the function ``compressor.cache.get_offline_manifest_filename()`` has been removed. + You can now use the new file storage ``compressor.storage.default_offline_manifest_storage`` to access the + location of the manifest. + + v4.0 (2022-03-23) ----------------- diff --git a/docs/settings.txt b/docs/settings.txt index 7b65b668..c2ab0482 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -541,3 +541,26 @@ Offline settings The name of the file to be used for saving the names of the files compressed offline. + +.. attribute:: COMPRESS_OFFLINE_MANIFEST_STORAGE + + :Default: ``compressor.storage.OfflineManifestFileStorage`` + + The dotted path to a Django Storage backend to be used to save the + offline manifest. + + By default, the file configured with + :attr:`~django.conf.settings.COMPRESS_OFFLINE_MANIFEST` will be stored + into :attr:`~django.conf.settings.COMPRESS_OUTPUT_DIR`. + + An example to output the manifest into the project's root directory:: + + # project/settings.py: + COMPRESS_STORAGE = 'project.module.PrivateOfflineManifestFileStorage' + + # project/module.py: + from compressor.storage import OfflineManifestFileStorage + from django.conf import settings + class PrivateOfflineManifestFileStorage(OfflineManifestFileStorage): + def __init__(self, *args, **kwargs): + super().__init__(settings.BASE_DIR, None, *args, **kwargs)