Merge remote-tracking branch 'lapilofu/hotfix/manifest' into release
[philo.git] / philo / utils / templates.py
1 import itertools
2
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
6
7 from philo.templatetags.containers import ContainerNode
8
9
10 LOADED_TEMPLATE_ATTR = '_philo_loaded_template'
11 BLANK_CONTEXT = Context()
12
13
14 def get_extended(self):
15         return self.get_parent(BLANK_CONTEXT)
16
17
18 def get_included(self):
19         return self.template
20
21
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))
25
26
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)
30                 
31                 contentlet_specs = []
32                 contentreference_specs = SortedDict()
33                 blocks = {}
34                 
35                 for level in reversed(levels):
36                         level.initialize()
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():
40                                 if block.block_super:
41                                         blocks.setdefault(name, []).append(block)
42                                 else:
43                                         blocks[name] = [block]
44                 
45                 for block_list in blocks.values():
46                         for block in block_list:
47                                 block.initialize()
48                                 contentlet_specs.extend(itertools.ifilter(lambda x: x not in contentlet_specs, block.contentlet_specs))
49                                 contentreference_specs.update(block.contentreference_specs)
50                 
51                 return contentlet_specs, contentreference_specs
52
53
54 class LazyContainerFinder(object):
55         def __init__(self, nodes, extends=False):
56                 self.nodes = nodes
57                 self.initialized = False
58                 self.contentlet_specs = []
59                 self.contentreference_specs = SortedDict()
60                 self.blocks = {}
61                 self.block_super = False
62                 self.extends = extends
63         
64         def process(self, nodelist):
65                 for node in nodelist:
66                         if self.extends:
67                                 if isinstance(node, BlockNode):
68                                         self.blocks[node.name] = block = LazyContainerFinder(node.nodelist)
69                                         block.initialize()
70                                         self.blocks.update(block.blocks)
71                                 continue
72                         
73                         if isinstance(node, ContainerNode):
74                                 if not node.references:
75                                         self.contentlet_specs.append(node.name)
76                                 else:
77                                         if node.name not in self.contentreference_specs.keys():
78                                                 self.contentreference_specs[node.name] = node.references
79                                 continue
80                         
81                         if isinstance(node, VariableNode):
82                                 if node.filter_expression.var.lookups == (u'block', u'super'):
83                                         self.block_super = True
84                         
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)
90                         
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)
96                                 if loaded_template:
97                                         nodelist = loaded_template.nodelist
98                                         self.process(nodelist)
99         
100         def initialize(self):
101                 if not self.initialized:
102                         self.process(self.nodes)
103                         self.initialized = True
104
105
106 def build_extension_tree(nodelist):
107         nodelists = []
108         extends = None
109         for node in nodelist:
110                 if not isinstance(node, TextNode):
111                         if isinstance(node, ExtendsNode):
112                                 extends = node
113                         break
114         
115         if extends:
116                 if extends.nodelist:
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))
120         else:
121                 # Base case: root.
122                 nodelists.append(LazyContainerFinder(nodelist))
123         return nodelists