Corrections to has_navigation and navigation_host to use the new NavigationMapper...
[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 :ttag:`recursenavigation` templatetag takes two arguments:
105         
106         * the :class:`.Node` for which the :class:`.Navigation` should be found
107         * the :class:`.Navigation`'s :attr:`~.Navigation.key`.
108         
109         It will then recursively loop over each :class:`.NavigationItem` in the :class:`.Navigation` and render the template
110         chunk within the block. :ttag:`recursenavigation` sets the following variables in the context:
111         
112                 ==============================  ================================================
113                 Variable                        Description
114                 ==============================  ================================================
115                 ``navloop.depth``               The current depth of the loop (1 is the top level)
116                 ``navloop.depth0``              The current depth of the loop (0 is the top level)
117                 ``navloop.counter``             The current iteration of the current level(1-indexed)
118                 ``navloop.counter0``            The current iteration of the current level(0-indexed)
119                 ``navloop.first``               True if this is the first time through the current level
120                 ``navloop.last``                True if this is the last time through the current level
121                 ``navloop.parentloop``          This is the loop one level "above" the current one
122                 
123                 ``item``                        The current item in the loop (a :class:`.NavigationItem` instance)
124                 ``children``                    If accessed, performs the next level of recursion.
125                 ``navloop.active``              True if the item is active for this request
126                 ``navloop.active_descendants``  True if the item has active descendants for this request
127                 ==============================  ================================================
128         
129         Example::
130         
131                 <ul>
132                     {% recursenavigation node "main" %}
133                         <li{% if navloop.active %} class='active'{% endif %}>
134                             {{ item.text }}
135                             {% if item.get_children %}
136                                 <ul>
137                                     {{ children }}
138                                 </ul>
139                             {% endif %}
140                         </li>
141                     {% endrecursenavigation %}
142                 </ul>
143         
144         .. note:: {% recursenavigation %} requires that the current :class:`HttpRequest` be present in the context as ``request``. The simplest way to do this is with the `request context processor`_. If this is installed with just the default template context processors, the entry in your settings file will look like this::
145
146                 TEMPLATE_CONTEXT_PROCESSORS = (
147                         # Defaults
148                         "django.contrib.auth.context_processors.auth",
149                         "django.core.context_processors.debug",
150                         "django.core.context_processors.i18n",
151                         "django.core.context_processors.media",
152                         "django.core.context_processors.static",
153                         "django.contrib.messages.context_processors.messages"
154                         ...
155                         "django.core.context_processors.request"
156                 )
157         
158         .. _request context processor: https://docs.djangoproject.com/en/dev/ref/templates/api/#django-core-context-processors-request
159         """
160         bits = token.contents.split()
161         if len(bits) != 3:
162                 raise template.TemplateSyntaxError(_('%s tag requires two arguments: a node and a navigation section name') % bits[0])
163         
164         instance_var = parser.compile_filter(bits[1])
165         key_var = parser.compile_filter(bits[2])
166         
167         template_nodes = parser.parse(('endrecursenavigation',))
168         token = parser.delete_first_token()
169         return RecurseNavigationNode(template_nodes, instance_var, key_var)
170
171
172 @register.filter
173 def has_navigation(node, key=None):
174         """Returns ``True`` if the node has a :class:`.Navigation` with the given key and ``False`` otherwise. If ``key`` is ``None``, returns whether the node has any :class:`.Navigation`\ s at all."""
175         try:
176                 return bool(node.navigation[key])
177         except:
178                 return False
179
180
181 @register.filter
182 def navigation_host(node, key):
183         """Returns the :class:`.Node` which hosts the :class:`.Navigation` which ``node`` has inherited for ``key``. Returns ``node`` if any exceptions are encountered."""
184         try:
185                 return node.navigation[key].node
186         except:
187                 return node