Skip to content

Instantly share code, notes, and snippets.

@andybak
Created July 15, 2011 06:58
Show Gist options
  • Save andybak/1084222 to your computer and use it in GitHub Desktop.
Save andybak/1084222 to your computer and use it in GitHub Desktop.
Nav mixin
from django.core.urlresolvers import reverse
from django.contrib.admin import site
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
from cms.utils import get_page_from_url
class NavigableMixin(object):
"""
Adds a bunch of methods for generating navigation for objects.
Using classmethods instead of regular methods as sometimes we need to generate the navigation
when we don't have a current instance (i.e. to render a page outside the hierarchy)
"""
# The following stubs are required for any class using this Mixin
# The first 4 should already be supplied by any MPTT registered model
# The last one just returns True/False depending on whether we have a hierarchical model or not
# The 4 MPTT methods should never get called if this returns False so shouldn't need to be implemented
def get_siblings(self, include_self):
raise NotImplementedError
def get_children(self):
raise NotImplementedError
def get_ancestors(self, ascending):
raise NotImplementedError
def is_root_node(self):
raise NotImplementedError
@classmethod
def is_hierarchical(cls):
raise NotImplemented
@classmethod
def _link(cls, navigable, current_navigable, extra_markup=''):
"""
Takes a navigable node and the current navigable node being rendered
and returns the correct element (span or a) with class="ancestor" or class="selected" for the node
"""
if current_navigable:
if navigable==current_navigable:
# Current
return '<span id="nav-%s" class="selected">%s%s</span>' % (navigable.name, extra_markup, navigable.title)
elif cls.is_hierarchical() and navigable in current_navigable.get_active_ancestors(include_root=False):
# Ancestor
return '<a href="%s" id="nav-%s" class="ancestor">%s%s</a>' % (navigable.get_absolute_url(), navigable.name, extra_markup, navigable.title)
# Anything Else
return '<a href="%s" id="nav-%s">%s%s</a>' % (navigable.get_absolute_url(), navigable.name, extra_markup, navigable.title)
@classmethod
def nav_tree_ul(cls, nav_array=None, current_navigable=None, extra_markup=''): # TODO fix for sites that don't pass in extra_markup
# Pass in a nested structure in the form [node, node, ...]
# Node is either {'node': navigable_instance}
# or {'node': navigable_instance, 'children': [...]}
# where children is another list of nodes
#
# Returns: A set of nested <ul> and <li> tags as a string (missing the top outer <ul></li> pair)
# <li>'s contain <a href's>
# except the current navigable which is just a span
# Any direct ancestor has the class 'selected'
def ul(inner):
return '<ul>%s</ul>\n' % inner
def li(inner):
return '<li>%s</li>\n' % inner
def li_ancestor(inner):
return '<li class="ancestor">%s</li>\n' % inner
html = []
for item in nav_array:
if item.get('children', None):
html_fragment = cls._link(item['node'], current_navigable)
html_fragment += cls.nav_tree_ul(item['children'], current_navigable)
html.append(li_ancestor(html_fragment))
else:
html_fragment = li(cls._link(item['node'], current_navigable))
html.append(html_fragment)
if cls.is_hierarchical() and nav_array and len(nav_array[0]['node'].get_ancestors())==1:
# Add home page to top level even though it's really the parent
html = [li(cls._link(nav_array[0]['node'].get_ancestors()[0], current_navigable))] + html
# Don't return empty <ul>'s
if html:
return ul(''.join(html))
else:
return ''
@classmethod
def get_nav_tree(cls, current, nodes):
"""
Gets the tree back from 'current' only opening up subtrees if they are on route to the current navigable but including all the items 'nodes' TODO better explanation!
"""
result = []
for node in nodes:
subresult = {}
subresult['node'] = node
if not(node.is_leaf_node()) and (node in current.get_active_ancestors(include_root=True)):
subresult['children'] = cls.get_nav_tree(current, node.get_active_children())
result.append(subresult)
return result
def get_all_active_siblings(self):
return self.get_siblings(include_self=True).filter(active=True, show_in_navigation=True)
def get_active_children(self):
return self.get_children().filter(active=True, show_in_navigation=True)
def get_active_ancestors(self, include_root=False):
ancestors = list(self.get_ancestors(ascending=False).filter(active=True, show_in_navigation=True))+[self]
if include_root:
return ancestors
else:
return ancestors[1:]
def get_navigation_array(self, starting_level=0, top_tier_only=False):
if type(self).is_hierarchical():
if self.is_root_node():
root = self
else:
try:
# Set the root to the parent of the requested starting_level
root = self.get_active_ancestors(include_root=True)[starting_level]
except IndexError:
# Ooops! We've asked for a starting_level deeper than the current navigable
return None
if not top_tier_only:
nav_array = self.get_nav_tree(self, root.get_active_children())
else:
nav_array = [{'node': x} for x in root.get_active_children()] # Where is this used?
else:
nav_array = [{'node': x} for x in type(self).objects.filter(active=True, show_in_navigation=True)]
return nav_array
# Main entry point Returns the full tree or a part of it for the current navigable
def get_navigation_ul(self, starting_level=0, top_tier_only=False):
nav_array = self.get_navigation_array(starting_level, top_tier_only)
return self.nav_tree_ul(nav_array, self)
# Helper methods
def get_subnavigation_ul(self): # Everything below the top level
return self.get_navigation_ul(starting_level=1)
def get_mainnavigation_ul(self): # Just the top level (and the root navigable)
return self.get_navigation_ul(top_tier_only=True)
def get_secondary_navigation_ul(self): # Just the second level
return self.get_navigation_ul(starting_level=1, top_tier_only=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment