Switched back to setting a {{ children }} variable, but set it to a lazy recurser...
[philo.git] / contrib / shipherd / templatetags / shipherd.py
1 from django import template
2 from django.conf import settings
3 from django.utils.safestring import mark_safe
4 from philo.contrib.shipherd.models import Navigation
5 from philo.models import Node
6 from django.utils.translation import ugettext as _
7
8
9 register = template.Library()
10
11
12 class LazyNavigationRecurser(object):
13         def __init__(self, template_nodes, items, context, request):
14                 self.template_nodes = template_nodes
15                 self.items = items
16                 self.context = context
17                 self.request = request
18         
19         def __call__(self):
20                 items = self.items
21                 context = self.context
22                 request = self.request
23                 
24                 if not items:
25                         return ''
26                 
27                 if 'navloop' in context:
28                         parentloop = context['navloop']
29                 else:
30                         parentloop = {}
31                 context.push()
32                 
33                 depth = items[0].get_level()
34                 len_items = len(items)
35                 
36                 loop_dict = context['navloop'] = {
37                         'parentloop': parentloop,
38                         'depth': depth + 1,
39                         'depth0': depth
40                 }
41                 
42                 bits = []
43                 
44                 for i, item in enumerate(items):
45                         # First set context variables.
46                         loop_dict['counter0'] = i
47                         loop_dict['counter'] = i + 1
48                         loop_dict['revcounter'] = len_items - i
49                         loop_dict['revcounter0'] = len_items - i - 1
50                         loop_dict['first'] = (i == 0)
51                         loop_dict['last'] = (i == len_items - 1)
52                         
53                         # Set on loop_dict and context for backwards-compatibility.
54                         # Eventually only allow access through the loop_dict.
55                         loop_dict['active'] = context['active'] = item.is_active(request)
56                         loop_dict['active_descendants'] = context['active_descendants'] = item.has_active_descendants(request)
57                         
58                         # Set these directly in the context for easy access.
59                         context['item'] = item
60                         context['children'] = self.__class__(self.template_nodes, item.get_children(), context, request)
61                         
62                         # Then render the nodelist bit by bit.
63                         for node in self.template_nodes:
64                                 bits.append(node.render(context))
65                 context.pop()
66                 return ''.join(bits)
67
68
69 class RecurseNavigationNode(template.Node):
70         def __init__(self, template_nodes, instance_var, key):
71                 self.template_nodes = template_nodes
72                 self.instance_var = instance_var
73                 self.key = key
74         
75         def render(self, context):
76                 try:
77                         request = context['request']
78                 except KeyError:
79                         return ''
80                 
81                 instance = self.instance_var.resolve(context)
82                 
83                 try:
84                         items = instance.navigation[self.key]
85                 except:
86                         return settings.TEMPLATE_STRING_IF_INVALID
87                 
88                 return LazyNavigationRecurser(self.template_nodes, items, context, request)()
89
90
91 @register.tag
92 def recursenavigation(parser, token):
93         """
94         The recursenavigation templatetag takes two arguments:
95         - the node for which the navigation should be found
96         - the navigation's key.
97         
98         It will then recursively loop over each item in the navigation and render the template
99         chunk within the block. recursenavigation sets the following variables in the context:
100         
101                 ==============================  ================================================
102                 Variable                        Description
103                 ==============================  ================================================
104                 ``navloop.depth``               The current depth of the loop (1 is the top level)
105                 ``navloop.depth0``              The current depth of the loop (0 is the top level)
106                 ``navloop.counter``             The current iteration of the current level(1-indexed)
107                 ``navloop.counter0``            The current iteration of the current level(0-indexed)
108                 ``navloop.first``               True if this is the first time through the current level
109                 ``navloop.last``                True if this is the last time through the current level
110                 ``navloop.parentloop``          This is the loop one level "above" the current one
111                 ==============================  ================================================
112                 ``item``                        The current item in the loop (a NavigationItem instance)
113                 ``children``                    If accessed, performs the next level of recursion.
114                 ``navloop.active``              True if the item is active for this request
115                 ``navloop.active_descendants``  True if the item has active descendants for this request
116                 ==============================  ================================================
117         
118         Example:
119                 <ul>
120                         {% recursenavigation node main %}
121                                 <li{% if navloop.active %} class='active'{% endif %}>
122                                         {{ navloop.item.text }}
123                                         {% if item.get_children %}
124                                                 <ul>
125                                                         {{ children }}
126                                                 </ul>
127                                         {% endif %}
128                                 </li>
129                         {% endrecursenavigation %}
130                 </ul>
131         """
132         bits = token.contents.split()
133         if len(bits) != 3:
134                 raise template.TemplateSyntaxError(_('%s tag requires two arguments: a node and a navigation section name') % bits[0])
135         
136         instance_var = parser.compile_filter(bits[1])
137         key = bits[2]
138         
139         template_nodes = parser.parse(('recurse', 'endrecursenavigation',))
140         
141         token = parser.next_token()
142         if token.contents == 'recurse':
143                 template_nodes.append(RecurseNavigationMarker())
144                 template_nodes.extend(parser.parse(('endrecursenavigation')))
145                 parser.delete_first_token()
146         
147         return RecurseNavigationNode(template_nodes, instance_var, key)
148
149
150 @register.filter
151 def has_navigation(node, key=None):
152         try:
153                 nav = node.navigation
154                 if key is not None:
155                         if key in nav and bool(node.navigation[key]):
156                                 return True
157                         elif key not in node.navigation:
158                                 return False
159                 return bool(node.navigation)
160         except:
161                 return False
162
163
164 @register.filter
165 def navigation_host(node, key):
166         try:
167                 return Navigation.objects.filter(node__in=node.get_ancestors(include_self=True), key=key).order_by('-node__level')[0].node
168         except:
169                 if settings.TEMPLATE_DEBUG:
170                         raise
171                 return node