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