Created
July 15, 2011 06:58
-
-
Save andybak/1084222 to your computer and use it in GitHub Desktop.
Nav mixin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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