Use COMPRESS_OFFLINE_CONTEXT in offline templates discovery
Test case for template inheritance with variable from context and super Test case for template inheritance with variable from context
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict, defaultdict
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
@@ -154,6 +154,18 @@ class Command(BaseCommand):
|
|||||||
if verbosity > 1:
|
if verbosity > 1:
|
||||||
log.write("Found templates:\n\t" + "\n\t".join(templates) + "\n")
|
log.write("Found templates:\n\t" + "\n\t".join(templates) + "\n")
|
||||||
|
|
||||||
|
contexts = settings.COMPRESS_OFFLINE_CONTEXT
|
||||||
|
if isinstance(contexts, six.string_types):
|
||||||
|
try:
|
||||||
|
module, function = get_mod_func(contexts)
|
||||||
|
contexts = getattr(import_module(module), function)()
|
||||||
|
except (AttributeError, ImportError, TypeError) as e:
|
||||||
|
raise ImportError("Couldn't import offline context function %s: %s" %
|
||||||
|
(settings.COMPRESS_OFFLINE_CONTEXT, e))
|
||||||
|
elif not isinstance(contexts, (list, tuple)):
|
||||||
|
contexts = [contexts]
|
||||||
|
contexts = list(contexts) # evaluate generator
|
||||||
|
|
||||||
parser = self.__get_parser(engine)
|
parser = self.__get_parser(engine)
|
||||||
compressor_nodes = OrderedDict()
|
compressor_nodes = OrderedDict()
|
||||||
for template_name in templates:
|
for template_name in templates:
|
||||||
@@ -176,16 +188,22 @@ class Command(BaseCommand):
|
|||||||
log.write("UnicodeDecodeError while trying to read "
|
log.write("UnicodeDecodeError while trying to read "
|
||||||
"template %s\n" % template_name)
|
"template %s\n" % template_name)
|
||||||
continue
|
continue
|
||||||
try:
|
|
||||||
nodes = list(parser.walk_nodes(template))
|
for context_dict in contexts:
|
||||||
except (TemplateDoesNotExist, TemplateSyntaxError) as e:
|
context = parser.get_init_context(context_dict)
|
||||||
# Could be an error in some base template
|
context = Context(context)
|
||||||
if verbosity > 0:
|
try:
|
||||||
log.write("Error parsing template %s: %s\n" % (template_name, e))
|
nodes = list(parser.walk_nodes(template, context=context))
|
||||||
continue
|
except (TemplateDoesNotExist, TemplateSyntaxError) as e:
|
||||||
if nodes:
|
# Could be an error in some base template
|
||||||
template.template_name = template_name
|
if verbosity > 0:
|
||||||
compressor_nodes.setdefault(template, []).extend(nodes)
|
log.write("Error parsing template %s: %s\n" % (template_name, e))
|
||||||
|
continue
|
||||||
|
if nodes:
|
||||||
|
template.template_name = template_name
|
||||||
|
template_nodes = compressor_nodes.setdefault(template, OrderedDict())
|
||||||
|
for node in nodes:
|
||||||
|
template_nodes.setdefault(node, []).append(context)
|
||||||
|
|
||||||
if not compressor_nodes:
|
if not compressor_nodes:
|
||||||
raise OfflineGenerationError(
|
raise OfflineGenerationError(
|
||||||
@@ -198,36 +216,23 @@ class Command(BaseCommand):
|
|||||||
"\n\t".join((t.template_name
|
"\n\t".join((t.template_name
|
||||||
for t in compressor_nodes.keys())) + "\n")
|
for t in compressor_nodes.keys())) + "\n")
|
||||||
|
|
||||||
contexts = settings.COMPRESS_OFFLINE_CONTEXT
|
|
||||||
if isinstance(contexts, six.string_types):
|
|
||||||
try:
|
|
||||||
module, function = get_mod_func(contexts)
|
|
||||||
contexts = getattr(import_module(module), function)()
|
|
||||||
except (AttributeError, ImportError, TypeError) as e:
|
|
||||||
raise ImportError("Couldn't import offline context function %s: %s" %
|
|
||||||
(settings.COMPRESS_OFFLINE_CONTEXT, e))
|
|
||||||
elif not isinstance(contexts, (list, tuple)):
|
|
||||||
contexts = [contexts]
|
|
||||||
|
|
||||||
log.write("Compressing... ")
|
log.write("Compressing... ")
|
||||||
block_count = context_count = 0
|
block_count = 0
|
||||||
|
compressed_contexts = []
|
||||||
results = []
|
results = []
|
||||||
offline_manifest = OrderedDict()
|
offline_manifest = OrderedDict()
|
||||||
|
for template, nodes in compressor_nodes.items():
|
||||||
|
template._log = log
|
||||||
|
template._log_verbosity = verbosity
|
||||||
|
|
||||||
for context_dict in contexts:
|
for node, contexts in nodes.items():
|
||||||
context_count += 1
|
for context in contexts:
|
||||||
init_context = parser.get_init_context(context_dict)
|
if context not in compressed_contexts:
|
||||||
|
compressed_contexts.append(context)
|
||||||
for template, nodes in compressor_nodes.items():
|
|
||||||
context = Context(init_context)
|
|
||||||
template._log = log
|
|
||||||
template._log_verbosity = verbosity
|
|
||||||
|
|
||||||
if not parser.process_template(template, context):
|
|
||||||
continue
|
|
||||||
|
|
||||||
for node in nodes:
|
|
||||||
context.push()
|
context.push()
|
||||||
|
if not parser.process_template(template, context):
|
||||||
|
continue
|
||||||
|
|
||||||
parser.process_node(template, context, node)
|
parser.process_node(template, context, node)
|
||||||
rendered = parser.render_nodelist(template, context, node)
|
rendered = parser.render_nodelist(template, context, node)
|
||||||
key = get_offline_hexdigest(rendered)
|
key = get_offline_hexdigest(rendered)
|
||||||
@@ -247,6 +252,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
write_offline_manifest(offline_manifest)
|
write_offline_manifest(offline_manifest)
|
||||||
|
|
||||||
|
context_count = len(compressed_contexts)
|
||||||
log.write("done\nCompressed %d block(s) from %d template(s) for %d context(s).\n" %
|
log.write("done\nCompressed %d block(s) from %d template(s) for %d context(s).\n" %
|
||||||
(block_count, len(compressor_nodes), context_count))
|
(block_count, len(compressor_nodes), context_count))
|
||||||
return block_count, results
|
return block_count, results
|
||||||
|
|||||||
@@ -116,16 +116,12 @@ class DjangoParser(object):
|
|||||||
def render_node(self, template, context, node):
|
def render_node(self, template, context, node):
|
||||||
return node.render(context, forced=True)
|
return node.render(context, forced=True)
|
||||||
|
|
||||||
def get_nodelist(self, node, original):
|
def get_nodelist(self, node, original, context):
|
||||||
if isinstance(node, ExtendsNode):
|
if isinstance(node, ExtendsNode):
|
||||||
try:
|
try:
|
||||||
context = Context()
|
if context is None:
|
||||||
|
context = Context()
|
||||||
context.template = original
|
context.template = original
|
||||||
# TODO: We are passing an empty context when finding base
|
|
||||||
# templates. This does not work when extending using
|
|
||||||
# variables ({% extends template_var %}).
|
|
||||||
# A refactor might be needed to support that use-case with
|
|
||||||
# multiple offline contexts.
|
|
||||||
return handle_extendsnode(node, context)
|
return handle_extendsnode(node, context)
|
||||||
except template.TemplateSyntaxError as e:
|
except template.TemplateSyntaxError as e:
|
||||||
raise TemplateSyntaxError(str(e))
|
raise TemplateSyntaxError(str(e))
|
||||||
@@ -141,13 +137,13 @@ class DjangoParser(object):
|
|||||||
nodelist = getattr(node, 'nodelist', [])
|
nodelist = getattr(node, 'nodelist', [])
|
||||||
return nodelist
|
return nodelist
|
||||||
|
|
||||||
def walk_nodes(self, node, original=None):
|
def walk_nodes(self, node, original=None, context=None):
|
||||||
if original is None:
|
if original is None:
|
||||||
original = node
|
original = node
|
||||||
for node in self.get_nodelist(node, original):
|
for node in self.get_nodelist(node, original, context):
|
||||||
if isinstance(node, CompressorNode) \
|
if isinstance(node, CompressorNode) \
|
||||||
and node.is_offline_compression_enabled(forced=True):
|
and node.is_offline_compression_enabled(forced=True):
|
||||||
yield node
|
yield node
|
||||||
else:
|
else:
|
||||||
for node in self.walk_nodes(node, original):
|
for node in self.walk_nodes(node, original, context):
|
||||||
yield node
|
yield node
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class Jinja2Parser(object):
|
|||||||
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
def walk_nodes(self, node, block_name=None):
|
def walk_nodes(self, node, block_name=None, context=None):
|
||||||
for node in self.get_nodelist(node):
|
for node in self.get_nodelist(node):
|
||||||
if (isinstance(node, CallBlock) and
|
if (isinstance(node, CallBlock) and
|
||||||
isinstance(node.call, Call) and
|
isinstance(node.call, Call) and
|
||||||
|
|||||||
@@ -120,10 +120,14 @@ class OfflineTestCaseMixin(object):
|
|||||||
default_storage.delete(manifest_path)
|
default_storage.delete(manifest_path)
|
||||||
|
|
||||||
def _prepare_contexts(self, engine):
|
def _prepare_contexts(self, engine):
|
||||||
|
contexts = settings.COMPRESS_OFFLINE_CONTEXT
|
||||||
|
if not isinstance(contexts, (list, tuple)):
|
||||||
|
contexts = [contexts]
|
||||||
|
|
||||||
if engine == 'django':
|
if engine == 'django':
|
||||||
return [Context(settings.COMPRESS_OFFLINE_CONTEXT)]
|
return [Context(c) for c in contexts]
|
||||||
if engine == 'jinja2':
|
if engine == 'jinja2':
|
||||||
return [settings.COMPRESS_OFFLINE_CONTEXT]
|
return contexts
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _render_template(self, engine):
|
def _render_template(self, engine):
|
||||||
@@ -433,6 +437,40 @@ class OfflineCompressTestCaseWithContextGeneratorSuper(
|
|||||||
engines = ('django',)
|
engines = ('django',)
|
||||||
|
|
||||||
|
|
||||||
|
class OfflineCompressTestCaseWithContextVariableInheritance(
|
||||||
|
OfflineTestCaseMixin, TestCase):
|
||||||
|
templates_dir = 'test_with_context_variable_inheritance'
|
||||||
|
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')
|
||||||
|
|
||||||
|
|
||||||
|
class OfflineCompressTestCaseWithContextVariableInheritanceSuper(
|
||||||
|
OfflineTestCaseMixin, TestCase):
|
||||||
|
templates_dir = 'test_with_context_variable_inheritance_super'
|
||||||
|
additional_test_settings = {
|
||||||
|
'COMPRESS_OFFLINE_CONTEXT': [{
|
||||||
|
'parent_template': 'base1.html',
|
||||||
|
}, {
|
||||||
|
'parent_template': 'base2.html',
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
expected_hash = ['7d1416cab12e', 'a31eb23d0157']
|
||||||
|
# Block.super not supported for Jinja2 yet.
|
||||||
|
engines = ('django',)
|
||||||
|
|
||||||
|
|
||||||
class OfflineCompressTestCaseWithContextGeneratorImportError(
|
class OfflineCompressTestCaseWithContextGeneratorImportError(
|
||||||
OfflineTestCaseMixin, TestCase):
|
OfflineTestCaseMixin, TestCase):
|
||||||
templates_dir = 'test_with_context'
|
templates_dir = 'test_with_context'
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{% load compress %}
|
||||||
|
{% spaceless %}
|
||||||
|
{% compress js %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
alert("test using template extension passed in variable parent=base.html");
|
||||||
|
</script>
|
||||||
|
{% endcompress %}
|
||||||
|
{% endspaceless %}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{% extends parent_template %}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{% spaceless %}
|
||||||
|
{% block js %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
alert("test using template extension passed in variable parent=base1.html");
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
{% endspaceless %}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{% spaceless %}
|
||||||
|
{% block js %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
alert("test using template extension passed in variable parent=base2.html");
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
{% endspaceless %}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{% extends parent_template %}
|
||||||
|
{% load compress %}
|
||||||
|
|
||||||
|
{% block js %}{% spaceless %}
|
||||||
|
{% compress js %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endcompress %}
|
||||||
|
{% endspaceless %}{% endblock %}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
{% spaceless %}
|
||||||
|
{% compress js %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
alert("test using template extension passed in variable parent=base.html");
|
||||||
|
</script>
|
||||||
|
{% endcompress %}
|
||||||
|
{% endspaceless %}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{% extends parent_template %}
|
||||||
Reference in New Issue
Block a user