Make offline compression independent of STATIC_URL (#809), fixes #778

This commit is contained in:
Rostyslav Bryzgunov
2016-11-25 17:01:07 -05:00
committed by Johannes Linke
parent 9b448d7477
commit f7886c06e6
7 changed files with 119 additions and 60 deletions

View File

@@ -50,7 +50,10 @@ def get_mtime_cachekey(filename):
def get_offline_hexdigest(render_template_string):
return get_hexdigest(render_template_string)
return get_hexdigest(
# Make the hexdigest determination independent of STATIC_URL
render_template_string.replace(settings.STATIC_URL, '')
)
def get_offline_cachekey(source):

View File

@@ -70,6 +70,9 @@ class CompressorConf(AppConf):
OFFLINE_MANIFEST = 'manifest.json'
# The Context to be used when TemplateFilter is used
TEMPLATE_FILTER_CONTEXT = {}
# Placeholder to be used instead of settings.COMPRESS_URL during offline compression.
# Affects manifest file contents only.
URL_PLACEHOLDER = '/__compressor_url_placeholder__/'
# Returns the Jinja2 environment to use in offline compression.
def JINJA2_GET_ENVIRONMENT():

View File

@@ -49,7 +49,7 @@ class Command(BaseCommand):
"(which defaults to STATIC_ROOT). Be aware that using this "
"can lead to infinite recursion if a link points to a parent "
"directory of itself.", dest='follow_links')
parser.add_argument('--engine', default="[]", action="append",
parser.add_argument('--engine', default=[], action="append",
help="Specifies the templating engine. jinja2 and django are "
"supported. It may be a specified more than once for "
"multiple engines. If not specified, django engine is used.",
@@ -250,6 +250,9 @@ class Command(BaseCommand):
except Exception as e:
raise CommandError("An error occurred during rendering %s: "
"%s" % (template.template_name, smart_text(e)))
result = result.replace(
settings.COMPRESS_URL, settings.COMPRESS_URL_PLACEHOLDER
)
offline_manifest[key] = result
context.pop()
results.append(result)

View File

@@ -64,7 +64,9 @@ class CompressorMixin(object):
key = get_offline_hexdigest(self.get_original_content(context))
offline_manifest = get_offline_manifest()
if key in offline_manifest:
return offline_manifest[key]
return offline_manifest[key].replace(
settings.COMPRESS_URL_PLACEHOLDER, settings.COMPRESS_URL
)
else:
raise OfflineGenerationError('You have offline compression '
'enabled but key "%s" is missing from offline manifest. '

View File

@@ -46,6 +46,10 @@ def offline_context_generator():
yield {'content': 'OK %d!' % i}
def static_url_context_generator():
yield {'STATIC_URL': settings.STATIC_URL}
class OfflineTestCaseMixin(object):
template_name = 'test_compressor_offline.html'
verbosity = 0
@@ -124,7 +128,6 @@ class OfflineTestCaseMixin(object):
contexts = settings.COMPRESS_OFFLINE_CONTEXT
if not isinstance(contexts, (list, tuple)):
contexts = [contexts]
if engine == 'django':
return [Context(c) for c in contexts]
if engine == 'jinja2':
@@ -140,6 +143,27 @@ class OfflineTestCaseMixin(object):
self.template_jinja2.render(c) for c in contexts) + '\n'
return None
def _render_script(self, hash):
return (
'<script type="text/javascript" src="{}CACHE/js/{}.js">'
'</script>'.format(
settings.COMPRESS_URL_PLACEHOLDER, hash
)
)
def _render_link(self, hash):
return (
'<link rel="stylesheet" href="{}CACHE/css/{}.css" '
'type="text/css" />'.format(
settings.COMPRESS_URL_PLACEHOLDER, hash
)
)
def _render_result(self, result, separator='\n'):
return (separator.join(result) + '\n').replace(
settings.COMPRESS_URL_PLACEHOLDER, settings.COMPRESS_URL
)
def _test_offline(self, engine):
hashes = self.expected_hash
if not isinstance(hashes, (list, tuple)):
@@ -147,11 +171,9 @@ class OfflineTestCaseMixin(object):
count, result = CompressCommand().compress(
log=self.log, verbosity=self.verbosity, engine=engine)
self.assertEqual(len(hashes), count)
self.assertEqual([
'<script type="text/javascript" src="/static/CACHE/js/'
'%s.js"></script>' % h for h in hashes], result)
self.assertEqual([self._render_script(h) for h in hashes], result)
rendered_template = self._render_template(engine)
self.assertEqual(rendered_template, '\n'.join(result) + '\n')
self.assertEqual(rendered_template, self._render_result(result))
def test_offline_django(self):
if 'django' not in self.engines:
@@ -240,11 +262,9 @@ class OfflineCompressBasicTestCase(OfflineTestCaseMixin, TestCase):
if default_storage.exists(manifest_path):
default_storage.delete(manifest_path)
self.assertEqual(1, count)
self.assertEqual([
'<script type="text/javascript" src="/static/CACHE/js/'
'%s.js"></script>' % (self.expected_hash, )], result)
self.assertEqual([self._render_script(self.expected_hash)], result)
rendered_template = self._render_template(engine)
self.assertEqual(rendered_template, ''.join(result) + '\n')
self.assertEqual(rendered_template, self._render_result(result))
def test_deleting_manifest_does_not_affect_rendering(self):
for engine in self.engines:
@@ -285,13 +305,11 @@ class OfflineCompressSkipDuplicatesTestCase(OfflineTestCaseMixin, TestCase):
# Only one block compressed, the second identical one was skipped.
self.assertEqual(1, count)
# Only 1 <script> block in returned result as well.
self.assertEqual([
'<script type="text/javascript" src="/static/CACHE/js/'
'f5e179b8eca4.js"></script>',
], result)
self.assertEqual([self._render_script('f5e179b8eca4')], result)
rendered_template = self._render_template(engine)
# But rendering the template returns both (identical) scripts.
self.assertEqual(rendered_template, ''.join(result * 2) + '\n')
self.assertEqual(
rendered_template, self._render_result(result * 2, ''))
class OfflineCompressBlockSuperTestCase(OfflineTestCaseMixin, TestCase):
@@ -336,13 +354,11 @@ class OfflineCompressBlockSuperTestCaseWithExtraContent(
log=self.log, verbosity=self.verbosity, engine=engine)
self.assertEqual(2, count)
self.assertEqual([
'<script type="text/javascript" src="/static/CACHE/js/'
'ced14aec5856.js"></script>',
'<script type="text/javascript" src="/static/CACHE/js/'
'7c02d201f69d.js"></script>',
self._render_script('ced14aec5856'),
self._render_script('7c02d201f69d')
], result)
rendered_template = self._render_template(engine)
self.assertEqual(rendered_template, ''.join(result) + '\n')
self.assertEqual(rendered_template, self._render_result(result, ''))
class OfflineCompressConditionTestCase(OfflineTestCaseMixin, TestCase):
@@ -444,23 +460,54 @@ class OfflineCompressTestCaseWithContextGeneratorSuper(
engines = ('django',)
class OfflineCompressStaticUrlIndependenceTestCase(
OfflineCompressTestCaseWithContextGenerator):
"""
Test that the offline manifest is independent of STATIC_URL.
I.e. users can use the manifest with any other STATIC_URL in the future.
We use COMPRESS_OFFLINE_CONTEXT generator to make sure that
STATIC_URL is not cached when rendering the template.
"""
templates_dir = 'test_static_url_independence'
expected_hash = '44ed960a3d1d'
additional_test_settings = {
'STATIC_URL': '/custom/static/url/',
'COMPRESS_OFFLINE_CONTEXT': (
'compressor.tests.test_offline.static_url_context_generator'
)
}
def _test_offline(self, engine):
count, result = CompressCommand().compress(
log=self.log, verbosity=self.verbosity, engine=engine
)
self.assertEqual(1, count)
self.assertEqual([self._render_script(self.expected_hash)], result)
self.assertEqual(
self._render_template(engine), self._render_result(result))
# Changing STATIC_URL setting doesn't break things despite that
# offline compression was made with different STATIC_URL.
with self.settings(STATIC_URL='/another/static/url/'):
self.assertEqual(
self._render_template(engine), self._render_result(result))
class OfflineCompressTestCaseWithContextVariableInheritance(
OfflineTestCaseMixin, TestCase):
templates_dir = 'test_with_context_variable_inheritance'
expected_hash = 'ea3267f3e9dd'
additional_test_settings = {
'COMPRESS_OFFLINE_CONTEXT': {
'parent_template': 'base.html',
}
}
def _test_offline(self, engine):
count, result = CompressCommand().compress(
log=self.log, verbosity=self.verbosity, engine=engine)
self.assertEqual(1, count)
self.assertEqual(['<script type="text/javascript" src="/static/CACHE/js/'
'ea3267f3e9dd.js"></script>'], result)
rendered_template = self._render_template(engine)
self.assertEqual(rendered_template, '\n' + result[0] + '\n')
def _render_result(self, result, separator='\n'):
return '\n' + super(
OfflineCompressTestCaseWithContextVariableInheritance, self
)._render_result(result, separator)
class OfflineCompressTestCaseWithContextVariableInheritanceSuper(
@@ -537,19 +584,11 @@ class OfflineCompressTestCaseErrors(OfflineTestCaseMixin, TestCase):
# 'compress' nodes are processed correctly.
self.assertEqual(4, count)
self.assertEqual(engine, 'jinja2')
self.assertIn(
'<link rel="stylesheet" href="/static/CACHE/css/'
'78bd7a762e2d.css" type="text/css" />', result)
self.assertIn(
'<link rel="stylesheet" href="/static/CACHE/css/'
'e31030430724.css" type="text/css" />', result)
self.assertIn(self._render_link('78bd7a762e2d'), result)
self.assertIn(self._render_link('e31030430724'), result)
self.assertIn(
'<script type="text/javascript" src="/static/CACHE/js/'
'3872c9ae3f42.js"></script>', result)
self.assertIn(
'<script type="text/javascript" src="/static/CACHE/js/'
'cd8870829421.js"></script>', result)
self.assertIn(self._render_script('3872c9ae3f42'), result)
self.assertIn(self._render_script('cd8870829421'), result)
class OfflineCompressTestCaseWithError(OfflineTestCaseMixin, TestCase):
@@ -627,11 +666,11 @@ class OfflineCompressBlockSuperBaseCompressed(OfflineTestCaseMixin, TestCase):
log=self.log, verbosity=self.verbosity, engine=engine)
self.assertEqual(len(self.expected_hash), count)
for expected_hash, template in zip(self.expected_hash, self.templates):
expected = ('<script type="text/javascript" src="/static/CACHE/js/'
'%s.js"></script>' % (expected_hash, ))
expected = self._render_script(expected_hash)
self.assertIn(expected, result)
rendered_template = self._render_template(template, engine)
self.assertEqual(rendered_template, expected + '\n')
self.assertEqual(
rendered_template, self._render_result([expected]))
class OfflineCompressInlineNonAsciiTestCase(OfflineTestCaseMixin, TestCase):
@@ -666,23 +705,23 @@ class OfflineCompressComplexTestCase(OfflineTestCaseMixin, TestCase):
log=self.log, verbosity=self.verbosity, engine=engine)
self.assertEqual(3, count)
self.assertEqual([
'<script type="text/javascript" src="/static/CACHE/js/'
'0e8807bebcee.js"></script>',
'<script type="text/javascript" src="/static/CACHE/js/'
'eed1d222933e.js"></script>',
'<script type="text/javascript" src="/static/CACHE/js/'
'00b4baffe335.js"></script>',
self._render_script('0e8807bebcee'),
self._render_script('eed1d222933e'),
self._render_script('00b4baffe335')
], result)
rendered_template = self._render_template(engine)
result = (result[0], result[2])
self.assertEqual(rendered_template, ''.join(result) + '\n')
self.assertEqual(
rendered_template, self._render_result([result[0], result[2]], ''))
@skipIf(django.VERSION < (1, 9), "Needs Django >= 1.9, recursive templates were fixed in Django 1.9")
@skipIf(
django.VERSION < (1, 9),
"Needs Django >= 1.9, recursive templates were fixed in Django 1.9"
)
class OfflineCompressExtendsRecursionTestCase(OfflineTestCaseMixin, TestCase):
"""
Test that templates extending templates with the same name
(e.g. admin/index.html) don't cause an infinite test_extends_recursion
Test that templates extending templates with the same name
(e.g. admin/index.html) don't cause an infinite test_extends_recursion
"""
templates_dir = 'test_extends_recursion'
engines = ('django',)
@@ -710,10 +749,9 @@ class TestCompressCommand(OfflineTestCaseMixin, TestCase):
raise SkipTest("Not utilized for this test case")
def _build_expected_manifest(self, expected):
return dict([
(k, ('<script type="text/javascript" src="/static/CACHE/js/'
'%s.js"></script>' % (v, ))) for k, v in expected.items()
])
return {
k: self._render_script(v) for k, v in expected.items()
}
def test_multiple_engines(self):
opts = {

View File

@@ -0,0 +1,5 @@
{% load compress %}{% spaceless %}
{% compress js %}
<script>STATIC_URL='{{ STATIC_URL }}';</script>
{% endcompress %}{% endspaceless %}

View File

@@ -0,0 +1,5 @@
{% spaceless %}
{% compress js %}
<script>STATIC_URL='{{ STATIC_URL }}';</script>
{% endcompress %}{% endspaceless %}