6e36126243c8b5697be4810c9c06d1604b8fb3c4
[philo.git] / 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                         # Django 1.2.X compatibility - a lazy recurser will not be called if accessed as a template variable.
64                         if django_version < (1,3):
65                                 context['children'] = context['children']()
66                         
67                         # Then render the nodelist bit by bit.
68                         for node in self.template_nodes:
69                                 bits.append(node.render(context))
70                 context.pop()
71                 return mark_safe(''.join(bits))
72
73
74 class RecurseNavigationNode(template.Node):
75         def __init__(self, template_nodes, instance_var, key):
76                 self.template_nodes = template_nodes
77                 self.instance_var = instance_var
78                 self.key = key
79         
80         def render(self, context):
81                 try:
82                         request = context['request']
83                 except KeyError:
84                         return ''
85                 
86                 instance = self.instance_var.resolve(context)
87                 
88                 try:
89                         items = instance.navigation[self.key]
90                 except:
91                         return settings.TEMPLATE_STRING_IF_INVALID
92                 
93                 return LazyNavigationRecurser(self.template_nodes, items, context, request)()
94
95
96 @register.tag
97 def recursenavigation(parser, token):
98         """
99         The recursenavigation templatetag takes two arguments:
100         - the node for which the navigation should be found
101         - the navigation's key.
102         
103         It will then recursively loop over each item in the navigation and render the template
104         chunk within the block. recursenavigation sets the following variables in the context:
105         
106                 ==============================  ================================================
107                 Variable                        Description
108                 ==============================  ================================================
109                 ``navloop.depth``               The current depth of the loop (1 is the top level)
110                 ``navloop.depth0``              The current depth of the loop (0 is the top level)
111                 ``navloop.counter``             The current iteration of the current level(1-indexed)
112                 ``navloop.counter0``            The current iteration of the current level(0-indexed)
113                 ``navloop.first``               True if this is the first time through the current level
114                 ``navloop.last``                True if this is the last time through the current level
115                 ``navloop.parentloop``          This is the loop one level "above" the current one
116                 ==============================  ================================================
117                 ``item``                        The current item in the loop (a NavigationItem instance)
118                 ``children``                    If accessed, performs the next level of recursion.
119                 ``navloop.active``              True if the item is active for this request
120                 ``navloop.active_descendants``  True if the item has active descendants for this request
121                 ==============================  ================================================
122         
123         Example:
124                 <ul>
125                         {% recursenavigation node main %}
126                                 <li{% if navloop.active %} class='active'{% endif %}>
127                                         {{ navloop.item.text }}
128                                         {% if item.get_children %}
129                                                 <ul>
130                                                         {{ children }}
131                                                 </ul>
132                                         {% endif %}
133                                 </li>
134                         {% endrecursenavigation %}
135                 </ul>
136         """
137         bits = token.contents.split()
138         if len(bits) != 3:
139                 raise template.TemplateSyntaxError(_('%s tag requires two arguments: a node and a navigation section name') % bits[0])
140         
141         instance_var = parser.compile_filter(bits[1])
142         key = bits[2]
143         
144         template_nodes = parser.parse(('recurse', 'endrecursenavigation',))
145         
146         token = parser.next_token()
147         if token.contents == 'recurse':
148                 template_nodes.append(RecurseNavigationMarker())
149                 template_nodes.extend(parser.parse(('endrecursenavigation')))
150                 parser.delete_first_token()
151         
152         return RecurseNavigationNode(template_nodes, instance_var, key)
153
154
155 @register.filter
156 def has_navigation(node, key=None):
157         try:
158                 nav = node.navigation
159                 if key is not None:
160                         if key in nav and bool(node.navigation[key]):
161                                 return True
162                         elif key not in node.navigation:
163                                 return False
164                 return bool(node.navigation)
165         except:
166                 return False
167
168
169 @register.filter
170 def navigation_host(node, key):
171         try:
172                 return Navigation.objects.filter(node__in=node.get_ancestors(include_self=True), key=key).order_by('-node__level')[0].node
173         except:
174                 return node