Skip to content

Commit

Permalink
Implement separate storage for the offline manifest
Browse files Browse the repository at this point in the history
* Implement separate storage for the offline manifest, fixes #1112

* correctly use urljoin() for URLs; added example to docs

* Updated changelog
  • Loading branch information
th3hamm0r committed May 27, 2022
1 parent 219b00b commit d70f1d6
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 18 deletions.
16 changes: 5 additions & 11 deletions compressor/cache.py
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {}
Expand All @@ -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()


Expand Down
1 change: 1 addition & 0 deletions compressor/conf.py
Expand Up @@ -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.
Expand Down
18 changes: 18 additions & 0 deletions compressor/storage.py
Expand Up @@ -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
Expand Down Expand Up @@ -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()
14 changes: 7 additions & 7 deletions compressor/tests/test_offline.py
Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions compressor/tests/test_storages.py
Expand Up @@ -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')))
10 changes: 10 additions & 0 deletions 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)
-----------------

Expand Down
23 changes: 23 additions & 0 deletions docs/settings.txt
Expand Up @@ -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)

0 comments on commit d70f1d6

Please sign in to comment.