3 from django.template import TextNode, VariableNode, Context
4 from django.template.loader_tags import BlockNode, ExtendsNode, BlockContext, ConstantIncludeNode
5 from django.utils.datastructures import SortedDict
7 from philo.templatetags.containers import ContainerNode
10 LOADED_TEMPLATE_ATTR = '_philo_loaded_template'
11 BLANK_CONTEXT = Context()
14 def get_extended(self):
15 return self.get_parent(BLANK_CONTEXT)
18 def get_included(self):
22 # We ignore the IncludeNode because it will never work in a blank context.
23 setattr(ExtendsNode, LOADED_TEMPLATE_ATTR, property(get_extended))
24 setattr(ConstantIncludeNode, LOADED_TEMPLATE_ATTR, property(get_included))
27 def get_containers(template):
28 # Build a tree of the templates we're using, placing the root template first.
29 levels = build_extension_tree(template.nodelist)
32 contentreference_specs = SortedDict()
35 for level in reversed(levels):
37 contentlet_specs.extend(itertools.ifilter(lambda x: x not in contentlet_specs, level.contentlet_specs))
38 contentreference_specs.update(level.contentreference_specs)
39 for name, block in level.blocks.items():
41 blocks.setdefault(name, []).append(block)
43 blocks[name] = [block]
45 for block_list in blocks.values():
46 for block in block_list:
48 contentlet_specs.extend(itertools.ifilter(lambda x: x not in contentlet_specs, block.contentlet_specs))
49 contentreference_specs.update(block.contentreference_specs)
51 return contentlet_specs, contentreference_specs
54 class LazyContainerFinder(object):
55 def __init__(self, nodes, extends=False):
57 self.initialized = False
58 self.contentlet_specs = []
59 self.contentreference_specs = SortedDict()
61 self.block_super = False
62 self.extends = extends
64 def process(self, nodelist):
67 if isinstance(node, BlockNode):
68 self.blocks[node.name] = block = LazyContainerFinder(node.nodelist)
70 self.blocks.update(block.blocks)
73 if isinstance(node, ContainerNode):
74 if not node.references:
75 self.contentlet_specs.append(node.name)
77 if node.name not in self.contentreference_specs.keys():
78 self.contentreference_specs[node.name] = node.references
81 if isinstance(node, VariableNode):
82 if node.filter_expression.var.lookups == (u'block', u'super'):
83 self.block_super = True
85 if hasattr(node, 'child_nodelists'):
86 for nodelist_name in node.child_nodelists:
87 if hasattr(node, nodelist_name):
88 nodelist = getattr(node, nodelist_name)
89 self.process(nodelist)
91 # LOADED_TEMPLATE_ATTR contains the name of an attribute philo uses to declare a
92 # node as rendering an additional template. Philo monkeypatches the attribute onto
93 # the relevant default nodes and declares it on any native nodes.
94 if hasattr(node, LOADED_TEMPLATE_ATTR):
95 loaded_template = getattr(node, LOADED_TEMPLATE_ATTR)
97 nodelist = loaded_template.nodelist
98 self.process(nodelist)
100 def initialize(self):
101 if not self.initialized:
102 self.process(self.nodes)
103 self.initialized = True
106 def build_extension_tree(nodelist):
109 for node in nodelist:
110 if not isinstance(node, TextNode):
111 if isinstance(node, ExtendsNode):
117 nodelists.append(LazyContainerFinder(extends.nodelist, extends=True))
118 loaded_template = getattr(extends, LOADED_TEMPLATE_ATTR)
119 nodelists.extend(build_extension_tree(loaded_template.nodelist))
122 nodelists.append(LazyContainerFinder(nodelist))