committed by
Johannes Linke
parent
9b448d7477
commit
f7886c06e6
@@ -50,7 +50,10 @@ def get_mtime_cachekey(filename):
|
|||||||
|
|
||||||
|
|
||||||
def get_offline_hexdigest(render_template_string):
|
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):
|
def get_offline_cachekey(source):
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ class CompressorConf(AppConf):
|
|||||||
OFFLINE_MANIFEST = 'manifest.json'
|
OFFLINE_MANIFEST = 'manifest.json'
|
||||||
# The Context to be used when TemplateFilter is used
|
# The Context to be used when TemplateFilter is used
|
||||||
TEMPLATE_FILTER_CONTEXT = {}
|
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.
|
# Returns the Jinja2 environment to use in offline compression.
|
||||||
def JINJA2_GET_ENVIRONMENT():
|
def JINJA2_GET_ENVIRONMENT():
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class Command(BaseCommand):
|
|||||||
"(which defaults to STATIC_ROOT). Be aware that using this "
|
"(which defaults to STATIC_ROOT). Be aware that using this "
|
||||||
"can lead to infinite recursion if a link points to a parent "
|
"can lead to infinite recursion if a link points to a parent "
|
||||||
"directory of itself.", dest='follow_links')
|
"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 "
|
help="Specifies the templating engine. jinja2 and django are "
|
||||||
"supported. It may be a specified more than once for "
|
"supported. It may be a specified more than once for "
|
||||||
"multiple engines. If not specified, django engine is used.",
|
"multiple engines. If not specified, django engine is used.",
|
||||||
@@ -250,6 +250,9 @@ class Command(BaseCommand):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise CommandError("An error occurred during rendering %s: "
|
raise CommandError("An error occurred during rendering %s: "
|
||||||
"%s" % (template.template_name, smart_text(e)))
|
"%s" % (template.template_name, smart_text(e)))
|
||||||
|
result = result.replace(
|
||||||
|
settings.COMPRESS_URL, settings.COMPRESS_URL_PLACEHOLDER
|
||||||
|
)
|
||||||
offline_manifest[key] = result
|
offline_manifest[key] = result
|
||||||
context.pop()
|
context.pop()
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ class CompressorMixin(object):
|
|||||||
key = get_offline_hexdigest(self.get_original_content(context))
|
key = get_offline_hexdigest(self.get_original_content(context))
|
||||||
offline_manifest = get_offline_manifest()
|
offline_manifest = get_offline_manifest()
|
||||||
if key in offline_manifest:
|
if key in offline_manifest:
|
||||||
return offline_manifest[key]
|
return offline_manifest[key].replace(
|
||||||
|
settings.COMPRESS_URL_PLACEHOLDER, settings.COMPRESS_URL
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise OfflineGenerationError('You have offline compression '
|
raise OfflineGenerationError('You have offline compression '
|
||||||
'enabled but key "%s" is missing from offline manifest. '
|
'enabled but key "%s" is missing from offline manifest. '
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ def offline_context_generator():
|
|||||||
yield {'content': 'OK %d!' % i}
|
yield {'content': 'OK %d!' % i}
|
||||||
|
|
||||||
|
|
||||||
|
def static_url_context_generator():
|
||||||
|
yield {'STATIC_URL': settings.STATIC_URL}
|
||||||
|
|
||||||
|
|
||||||
class OfflineTestCaseMixin(object):
|
class OfflineTestCaseMixin(object):
|
||||||
template_name = 'test_compressor_offline.html'
|
template_name = 'test_compressor_offline.html'
|
||||||
verbosity = 0
|
verbosity = 0
|
||||||
@@ -124,7 +128,6 @@ class OfflineTestCaseMixin(object):
|
|||||||
contexts = settings.COMPRESS_OFFLINE_CONTEXT
|
contexts = settings.COMPRESS_OFFLINE_CONTEXT
|
||||||
if not isinstance(contexts, (list, tuple)):
|
if not isinstance(contexts, (list, tuple)):
|
||||||
contexts = [contexts]
|
contexts = [contexts]
|
||||||
|
|
||||||
if engine == 'django':
|
if engine == 'django':
|
||||||
return [Context(c) for c in contexts]
|
return [Context(c) for c in contexts]
|
||||||
if engine == 'jinja2':
|
if engine == 'jinja2':
|
||||||
@@ -140,6 +143,27 @@ class OfflineTestCaseMixin(object):
|
|||||||
self.template_jinja2.render(c) for c in contexts) + '\n'
|
self.template_jinja2.render(c) for c in contexts) + '\n'
|
||||||
return None
|
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):
|
def _test_offline(self, engine):
|
||||||
hashes = self.expected_hash
|
hashes = self.expected_hash
|
||||||
if not isinstance(hashes, (list, tuple)):
|
if not isinstance(hashes, (list, tuple)):
|
||||||
@@ -147,11 +171,9 @@ class OfflineTestCaseMixin(object):
|
|||||||
count, result = CompressCommand().compress(
|
count, result = CompressCommand().compress(
|
||||||
log=self.log, verbosity=self.verbosity, engine=engine)
|
log=self.log, verbosity=self.verbosity, engine=engine)
|
||||||
self.assertEqual(len(hashes), count)
|
self.assertEqual(len(hashes), count)
|
||||||
self.assertEqual([
|
self.assertEqual([self._render_script(h) for h in hashes], result)
|
||||||
'<script type="text/javascript" src="/static/CACHE/js/'
|
|
||||||
'%s.js"></script>' % h for h in hashes], result)
|
|
||||||
rendered_template = self._render_template(engine)
|
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):
|
def test_offline_django(self):
|
||||||
if 'django' not in self.engines:
|
if 'django' not in self.engines:
|
||||||
@@ -240,11 +262,9 @@ class OfflineCompressBasicTestCase(OfflineTestCaseMixin, TestCase):
|
|||||||
if default_storage.exists(manifest_path):
|
if default_storage.exists(manifest_path):
|
||||||
default_storage.delete(manifest_path)
|
default_storage.delete(manifest_path)
|
||||||
self.assertEqual(1, count)
|
self.assertEqual(1, count)
|
||||||
self.assertEqual([
|
self.assertEqual([self._render_script(self.expected_hash)], result)
|
||||||
'<script type="text/javascript" src="/static/CACHE/js/'
|
|
||||||
'%s.js"></script>' % (self.expected_hash, )], result)
|
|
||||||
rendered_template = self._render_template(engine)
|
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):
|
def test_deleting_manifest_does_not_affect_rendering(self):
|
||||||
for engine in self.engines:
|
for engine in self.engines:
|
||||||
@@ -285,13 +305,11 @@ class OfflineCompressSkipDuplicatesTestCase(OfflineTestCaseMixin, TestCase):
|
|||||||
# Only one block compressed, the second identical one was skipped.
|
# Only one block compressed, the second identical one was skipped.
|
||||||
self.assertEqual(1, count)
|
self.assertEqual(1, count)
|
||||||
# Only 1 <script> block in returned result as well.
|
# Only 1 <script> block in returned result as well.
|
||||||
self.assertEqual([
|
self.assertEqual([self._render_script('f5e179b8eca4')], result)
|
||||||
'<script type="text/javascript" src="/static/CACHE/js/'
|
|
||||||
'f5e179b8eca4.js"></script>',
|
|
||||||
], result)
|
|
||||||
rendered_template = self._render_template(engine)
|
rendered_template = self._render_template(engine)
|
||||||
# But rendering the template returns both (identical) scripts.
|
# 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):
|
class OfflineCompressBlockSuperTestCase(OfflineTestCaseMixin, TestCase):
|
||||||
@@ -336,13 +354,11 @@ class OfflineCompressBlockSuperTestCaseWithExtraContent(
|
|||||||
log=self.log, verbosity=self.verbosity, engine=engine)
|
log=self.log, verbosity=self.verbosity, engine=engine)
|
||||||
self.assertEqual(2, count)
|
self.assertEqual(2, count)
|
||||||
self.assertEqual([
|
self.assertEqual([
|
||||||
'<script type="text/javascript" src="/static/CACHE/js/'
|
self._render_script('ced14aec5856'),
|
||||||
'ced14aec5856.js"></script>',
|
self._render_script('7c02d201f69d')
|
||||||
'<script type="text/javascript" src="/static/CACHE/js/'
|
|
||||||
'7c02d201f69d.js"></script>',
|
|
||||||
], result)
|
], result)
|
||||||
rendered_template = self._render_template(engine)
|
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):
|
class OfflineCompressConditionTestCase(OfflineTestCaseMixin, TestCase):
|
||||||
@@ -444,23 +460,54 @@ class OfflineCompressTestCaseWithContextGeneratorSuper(
|
|||||||
engines = ('django',)
|
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(
|
class OfflineCompressTestCaseWithContextVariableInheritance(
|
||||||
OfflineTestCaseMixin, TestCase):
|
OfflineTestCaseMixin, TestCase):
|
||||||
templates_dir = 'test_with_context_variable_inheritance'
|
templates_dir = 'test_with_context_variable_inheritance'
|
||||||
|
expected_hash = 'ea3267f3e9dd'
|
||||||
additional_test_settings = {
|
additional_test_settings = {
|
||||||
'COMPRESS_OFFLINE_CONTEXT': {
|
'COMPRESS_OFFLINE_CONTEXT': {
|
||||||
'parent_template': 'base.html',
|
'parent_template': 'base.html',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _test_offline(self, engine):
|
def _render_result(self, result, separator='\n'):
|
||||||
count, result = CompressCommand().compress(
|
return '\n' + super(
|
||||||
log=self.log, verbosity=self.verbosity, engine=engine)
|
OfflineCompressTestCaseWithContextVariableInheritance, self
|
||||||
self.assertEqual(1, count)
|
)._render_result(result, separator)
|
||||||
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')
|
|
||||||
|
|
||||||
|
|
||||||
class OfflineCompressTestCaseWithContextVariableInheritanceSuper(
|
class OfflineCompressTestCaseWithContextVariableInheritanceSuper(
|
||||||
@@ -537,19 +584,11 @@ class OfflineCompressTestCaseErrors(OfflineTestCaseMixin, TestCase):
|
|||||||
# 'compress' nodes are processed correctly.
|
# 'compress' nodes are processed correctly.
|
||||||
self.assertEqual(4, count)
|
self.assertEqual(4, count)
|
||||||
self.assertEqual(engine, 'jinja2')
|
self.assertEqual(engine, 'jinja2')
|
||||||
self.assertIn(
|
self.assertIn(self._render_link('78bd7a762e2d'), result)
|
||||||
'<link rel="stylesheet" href="/static/CACHE/css/'
|
self.assertIn(self._render_link('e31030430724'), result)
|
||||||
'78bd7a762e2d.css" type="text/css" />', result)
|
|
||||||
self.assertIn(
|
|
||||||
'<link rel="stylesheet" href="/static/CACHE/css/'
|
|
||||||
'e31030430724.css" type="text/css" />', result)
|
|
||||||
|
|
||||||
self.assertIn(
|
self.assertIn(self._render_script('3872c9ae3f42'), result)
|
||||||
'<script type="text/javascript" src="/static/CACHE/js/'
|
self.assertIn(self._render_script('cd8870829421'), result)
|
||||||
'3872c9ae3f42.js"></script>', result)
|
|
||||||
self.assertIn(
|
|
||||||
'<script type="text/javascript" src="/static/CACHE/js/'
|
|
||||||
'cd8870829421.js"></script>', result)
|
|
||||||
|
|
||||||
|
|
||||||
class OfflineCompressTestCaseWithError(OfflineTestCaseMixin, TestCase):
|
class OfflineCompressTestCaseWithError(OfflineTestCaseMixin, TestCase):
|
||||||
@@ -627,11 +666,11 @@ class OfflineCompressBlockSuperBaseCompressed(OfflineTestCaseMixin, TestCase):
|
|||||||
log=self.log, verbosity=self.verbosity, engine=engine)
|
log=self.log, verbosity=self.verbosity, engine=engine)
|
||||||
self.assertEqual(len(self.expected_hash), count)
|
self.assertEqual(len(self.expected_hash), count)
|
||||||
for expected_hash, template in zip(self.expected_hash, self.templates):
|
for expected_hash, template in zip(self.expected_hash, self.templates):
|
||||||
expected = ('<script type="text/javascript" src="/static/CACHE/js/'
|
expected = self._render_script(expected_hash)
|
||||||
'%s.js"></script>' % (expected_hash, ))
|
|
||||||
self.assertIn(expected, result)
|
self.assertIn(expected, result)
|
||||||
rendered_template = self._render_template(template, engine)
|
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):
|
class OfflineCompressInlineNonAsciiTestCase(OfflineTestCaseMixin, TestCase):
|
||||||
@@ -666,19 +705,19 @@ class OfflineCompressComplexTestCase(OfflineTestCaseMixin, TestCase):
|
|||||||
log=self.log, verbosity=self.verbosity, engine=engine)
|
log=self.log, verbosity=self.verbosity, engine=engine)
|
||||||
self.assertEqual(3, count)
|
self.assertEqual(3, count)
|
||||||
self.assertEqual([
|
self.assertEqual([
|
||||||
'<script type="text/javascript" src="/static/CACHE/js/'
|
self._render_script('0e8807bebcee'),
|
||||||
'0e8807bebcee.js"></script>',
|
self._render_script('eed1d222933e'),
|
||||||
'<script type="text/javascript" src="/static/CACHE/js/'
|
self._render_script('00b4baffe335')
|
||||||
'eed1d222933e.js"></script>',
|
|
||||||
'<script type="text/javascript" src="/static/CACHE/js/'
|
|
||||||
'00b4baffe335.js"></script>',
|
|
||||||
], result)
|
], result)
|
||||||
rendered_template = self._render_template(engine)
|
rendered_template = self._render_template(engine)
|
||||||
result = (result[0], result[2])
|
self.assertEqual(
|
||||||
self.assertEqual(rendered_template, ''.join(result) + '\n')
|
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):
|
class OfflineCompressExtendsRecursionTestCase(OfflineTestCaseMixin, TestCase):
|
||||||
"""
|
"""
|
||||||
Test that templates extending templates with the same name
|
Test that templates extending templates with the same name
|
||||||
@@ -710,10 +749,9 @@ class TestCompressCommand(OfflineTestCaseMixin, TestCase):
|
|||||||
raise SkipTest("Not utilized for this test case")
|
raise SkipTest("Not utilized for this test case")
|
||||||
|
|
||||||
def _build_expected_manifest(self, expected):
|
def _build_expected_manifest(self, expected):
|
||||||
return dict([
|
return {
|
||||||
(k, ('<script type="text/javascript" src="/static/CACHE/js/'
|
k: self._render_script(v) for k, v in expected.items()
|
||||||
'%s.js"></script>' % (v, ))) for k, v in expected.items()
|
}
|
||||||
])
|
|
||||||
|
|
||||||
def test_multiple_engines(self):
|
def test_multiple_engines(self):
|
||||||
opts = {
|
opts = {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{% load compress %}{% spaceless %}
|
||||||
|
|
||||||
|
{% compress js %}
|
||||||
|
<script>STATIC_URL='{{ STATIC_URL }}';</script>
|
||||||
|
{% endcompress %}{% endspaceless %}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{% spaceless %}
|
||||||
|
|
||||||
|
{% compress js %}
|
||||||
|
<script>STATIC_URL='{{ STATIC_URL }}';</script>
|
||||||
|
{% endcompress %}{% endspaceless %}
|
||||||
Reference in New Issue
Block a user