Removed outdated RecurseNavigationMarker code and added filtered interpretation of...
[philo.git] / contrib / shipherd / templatetags / shipherd.py
index 317fffb..b05ff0f 100644 (file)
@@ -1,47 +1,81 @@
-from django import template
+from django import template, VERSION as django_version
 from django.conf import settings
 from django.utils.safestring import mark_safe
 from philo.contrib.shipherd.models import Navigation
 from philo.models import Node
-from mptt.templatetags.mptt_tags import RecurseTreeNode, cache_tree_children
+from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext as _
 
 
 register = template.Library()
 
 
-class RecurseNavigationNode(RecurseTreeNode):
-       def __init__(self, template_nodes, instance_var, key):
+class LazyNavigationRecurser(object):
+       def __init__(self, template_nodes, items, context, request):
                self.template_nodes = template_nodes
-               self.instance_var = instance_var
-               self.key = key
+               self.items = items
+               self.context = context
+               self.request = request
        
-       def _render_node(self, context, item, request):
-               bits = []
+       def __call__(self):
+               items = self.items
+               context = self.context
+               request = self.request
+               
+               if not items:
+                       return ''
+               
+               if 'navloop' in context:
+                       parentloop = context['navloop']
+               else:
+                       parentloop = {}
                context.push()
                
-               # loosely based on django.template.defaulttags.ForNode.render
-               children = item.get_children()
-               parentloop = context['navloop']
-               loop_dict = context['navloop'] = {'parentloop':parentloop}
-               len_items = len(children)
-               for i, child in enumerate(children):
-                       context['item'] = child
+               depth = items[0].get_level()
+               len_items = len(items)
+               
+               loop_dict = context['navloop'] = {
+                       'parentloop': parentloop,
+                       'depth': depth + 1,
+                       'depth0': depth
+               }
+               
+               bits = []
+               
+               for i, item in enumerate(items):
+                       # First set context variables.
                        loop_dict['counter0'] = i
                        loop_dict['counter'] = i + 1
                        loop_dict['revcounter'] = len_items - i
                        loop_dict['revcounter0'] = len_items - i - 1
                        loop_dict['first'] = (i == 0)
                        loop_dict['last'] = (i == len_items - 1)
-                       bits.append(self._render_node(context, child, request))
-               context['navloop'] = context['navloop']['parentloop']
-               context['item'] = item
-               context['children'] = mark_safe(u''.join(bits))
-               context['active'] = item.is_active(request)
-               context['active_descendants'] = item.has_active_descendants(request)
-               rendered = self.template_nodes.render(context)
+                       
+                       # Set on loop_dict and context for backwards-compatibility.
+                       # Eventually only allow access through the loop_dict.
+                       loop_dict['active'] = context['active'] = item.is_active(request)
+                       loop_dict['active_descendants'] = context['active_descendants'] = item.has_active_descendants(request)
+                       
+                       # Set these directly in the context for easy access.
+                       context['item'] = item
+                       context['children'] = self.__class__(self.template_nodes, item.get_children(), context, request)
+                       
+                       # Django 1.2.X compatibility - a lazy recurser will not be called if accessed as a template variable.
+                       if django_version < (1,3):
+                               context['children'] = context['children']()
+                       
+                       # Then render the nodelist bit by bit.
+                       for node in self.template_nodes:
+                               bits.append(node.render(context))
                context.pop()
-               return rendered
+               return mark_safe(''.join(bits))
+
+
+class RecurseNavigationNode(template.Node):
+       def __init__(self, template_nodes, instance_var, key_var):
+               self.template_nodes = template_nodes
+               self.instance_var = instance_var
+               self.key_var = key_var
        
        def render(self, context):
                try:
@@ -50,46 +84,57 @@ class RecurseNavigationNode(RecurseTreeNode):
                        return ''
                
                instance = self.instance_var.resolve(context)
+               key = self.key_var.resolve(context)
+               
+               # Fall back on old behavior if the key doesn't seem to be a variable.
+               if not key:
+                       token = self.key_var.token
+                       if token[0] not in ["'", '"'] and '.' not in token:
+                               key = token
+                       else:
+                               return settings.TEMPLATE_STRING_IF_INVALID
                
                try:
-                       items = instance.navigation[self.key]
+                       items = instance.navigation[key]
                except:
                        return settings.TEMPLATE_STRING_IF_INVALID
                
-               bits = []
-               
-               # loosely based on django.template.defaulttags.ForNode.render
-               # This is a repetition of the stuff that happens above. We should eliminate that somehow.
-               loop_dict = context['navloop'] = {'parentloop':{}}
-               len_items = len(items)
-               for i, item in enumerate(items):
-                       loop_dict['counter0'] = i
-                       loop_dict['counter'] = i + 1
-                       loop_dict['revcounter'] = len_items - i
-                       loop_dict['revcounter0'] = len_items - i - 1
-                       loop_dict['first'] = (i == 0)
-                       loop_dict['last'] = (i == len_items - 1)
-                       bits.append(self._render_node(context, item, request))
-               
-               return ''.join(bits)
+               return LazyNavigationRecurser(self.template_nodes, items, context, request)()
 
 
 @register.tag
 def recursenavigation(parser, token):
        """
-       Based on django-mptt's recursetree templatetag. In addition to {{ item }} and {{ children }},
-       sets {{ active }}, {{ active_descendants }}, {{ navloop.counter }}, {{ navloop.counter0 }},
-       {{ navloop.revcounter }}, {{ navloop.revcounter0 }}, {{ navloop.first }}, {{ navloop.last }},
-       and {{ navloop.parentloop }} in the context.
+       The recursenavigation templatetag takes two arguments:
+       - the node for which the navigation should be found
+       - the navigation's key.
+       
+       It will then recursively loop over each item in the navigation and render the template
+       chunk within the block. recursenavigation sets the following variables in the context:
        
-       Note that the tag takes one variable, which is a Node instance.
+               ==============================  ================================================
+               Variable                        Description
+               ==============================  ================================================
+               ``navloop.depth``               The current depth of the loop (1 is the top level)
+               ``navloop.depth0``              The current depth of the loop (0 is the top level)
+               ``navloop.counter``             The current iteration of the current level(1-indexed)
+               ``navloop.counter0``            The current iteration of the current level(0-indexed)
+               ``navloop.first``               True if this is the first time through the current level
+               ``navloop.last``                True if this is the last time through the current level
+               ``navloop.parentloop``          This is the loop one level "above" the current one
+               ==============================  ================================================
+               ``item``                        The current item in the loop (a NavigationItem instance)
+               ``children``                    If accessed, performs the next level of recursion.
+               ``navloop.active``              True if the item is active for this request
+               ``navloop.active_descendants``  True if the item has active descendants for this request
+               ==============================  ================================================
        
-       Usage:
+       Example:
                <ul>
                        {% recursenavigation node main %}
-                               <li{% if active %} class='active'{% endif %}>
-                                       {{ navigation.text }}
-                                       {% if navigation.get_children %}
+                               <li{% if navloop.active %} class='active'{% endif %}>
+                                       {{ navloop.item.text }}
+                                       {% if item.get_children %}
                                                <ul>
                                                        {{ children }}
                                                </ul>
@@ -103,12 +148,11 @@ def recursenavigation(parser, token):
                raise template.TemplateSyntaxError(_('%s tag requires two arguments: a node and a navigation section name') % bits[0])
        
        instance_var = parser.compile_filter(bits[1])
-       key = bits[2]
+       key_var = parser.compile_filter(bits[2])
        
        template_nodes = parser.parse(('endrecursenavigation',))
-       parser.delete_first_token()
-       
-       return RecurseNavigationNode(template_nodes, instance_var, key)
+       token = parser.delete_first_token()
+       return RecurseNavigationNode(template_nodes, instance_var, key_var)
 
 
 @register.filter
@@ -130,6 +174,4 @@ def navigation_host(node, key):
        try:
                return Navigation.objects.filter(node__in=node.get_ancestors(include_self=True), key=key).order_by('-node__level')[0].node
        except:
-               if settings.TEMPLATE_DEBUG:
-                       raise
                return node
\ No newline at end of file