diff --git a/navbuilder/templates/navbuilder/inclusion_tags/breadcrumbs.html b/navbuilder/templates/navbuilder/inclusion_tags/breadcrumbs.html
new file mode 100644
index 0000000..dc709e1
--- /dev/null
+++ b/navbuilder/templates/navbuilder/inclusion_tags/breadcrumbs.html
@@ -0,0 +1,5 @@
+{% load navbuilder_tags %}
+{% for crumb in navbuilder_breadcrumbs %}
+{{ crumb.link.title|default:crumb.title }}
+{% if not forloop.last %} > {% endif %}
+{% endfor %}
diff --git a/navbuilder/templatetags/navbuilder_tags.py b/navbuilder/templatetags/navbuilder_tags.py
index 7c1e203..82e2dbb 100644
--- a/navbuilder/templatetags/navbuilder_tags.py
+++ b/navbuilder/templatetags/navbuilder_tags.py
@@ -1,6 +1,7 @@
from django import template
+from django.contrib.contenttypes.models import ContentType
-from navbuilder.models import Menu
+from navbuilder.models import Menu, MenuItem
register = template.Library()
@@ -22,3 +23,48 @@ def render_menu(context, slug):
def render_menuitem(context, obj):
context["object"] = obj
return context
+
+
+@register.inclusion_tag(
+ "navbuilder/inclusion_tags/breadcrumbs.html", takes_context=True
+)
+def navbuilder_breadcrumbs(context, slug):
+ """
+ Render the breadcrumbs, based on the current object. Prefer using the
+ structure of the menu designated by slug, but use any menu available.
+ Typical use case for this would be if the main menu has an about/terms
+ page, but it's mirrored in the footer menu in a much flatter layout. We
+ prefer the main menu structure. This also allows us to construct
+ breadcrumbs for items that don't show up in page menus at all.
+ """
+ context["navbuilder_breadcrumbs"] = []
+ if "object" not in context:
+ return context
+
+ def get_menuitems(item):
+ if item.parent:
+ struct = get_menuitems(item.parent)
+ struct.append(item)
+ return struct
+ return [item]
+
+ content_type = ContentType.objects.get_for_model(context["object"])
+ crumb_sets = []
+ for item in MenuItem.objects.filter(
+ link_content_type__pk=content_type.id,
+ link_object_id = context["object"].id):
+
+ crumb_sets.append(get_menuitems(item))
+
+ for crumb_set in crumb_sets:
+ menu = crumb_set[0].menu
+ if menu and menu.slug == slug:
+ context["navbuilder_breadcrumbs"] = crumb_set
+
+ if not context["navbuilder_breadcrumbs"]:
+ if crumb_sets:
+ context["navbuilder_breadcrumbs"] = crumb_sets[0]
+ else:
+ context["navbuilder_breadcrumbs"] = []
+
+ return context
diff --git a/navbuilder/tests/test_base.py b/navbuilder/tests/test_base.py
index f8b6dfe..0e59157 100644
--- a/navbuilder/tests/test_base.py
+++ b/navbuilder/tests/test_base.py
@@ -37,3 +37,39 @@ def load_fixtures(kls):
kls.sub_menuitem = models.MenuItem.objects.create(
**kls.sub_menuitem_data
)
+
+
+def load_crumb_fixtures(kls):
+ kls.menu_data_2 = {
+ "title": "Menu 2",
+ "slug": "menu-2"
+ }
+ kls.menu_2 = models.Menu.objects.create(**kls.menu_data_2)
+
+ kls.link_data_2 = {
+ "title": "Link 2",
+ "slug": "link-2",
+ "url": "/link/2/"
+ }
+ kls.link_2 = Link.objects.create(**kls.link_data_2)
+
+ kls.menuitem_data_2 = {
+ "title": "Menu Item 2",
+ "slug": "menu-item-2",
+ "position": 2,
+ "menu": kls.menu_2,
+ "link": kls.link
+ }
+ kls.menuitem_2 = models.MenuItem.objects.create(**kls.menuitem_data_2)
+
+ kls.sub_menuitem_data_2 = {
+ "title": "Sub Menu Item 2",
+ "slug": "sub-menu-item-2",
+ "position": 2,
+ "parent": kls.menuitem_2,
+ "target": "blank",
+ "link": None
+ }
+ kls.sub_menuitem_2 = models.MenuItem.objects.create(
+ **kls.sub_menuitem_data_2
+ )
diff --git a/navbuilder/tests/test_breadcrumbs.py b/navbuilder/tests/test_breadcrumbs.py
new file mode 100644
index 0000000..7f5ee32
--- /dev/null
+++ b/navbuilder/tests/test_breadcrumbs.py
@@ -0,0 +1,75 @@
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+from django.test.client import Client
+
+from navbuilder import models
+from navbuilder.tests.test_base import load_fixtures, load_crumb_fixtures
+
+from django.template import Context, Template
+
+crumb_template_1 = Template(
+ "{% load navbuilder_tags %}"
+ "{% navbuilder_breadcrumbs 'menu-1' %}"
+ )
+
+crumb_template_2 = Template(
+ "{% load navbuilder_tags %}"
+ "{% navbuilder_breadcrumbs 'menu-2' %}"
+ )
+
+crumb_template_3 = Template(
+ "{% load navbuilder_tags %}"
+ "{% navbuilder_breadcrumbs 'menu-3' %}"
+ )
+
+
+class BreadcrumbsTestCase(TestCase):
+ def setUp(self):
+ self.client = Client()
+ load_fixtures(self)
+ load_crumb_fixtures(self)
+ # Reorganise the items
+ self.menuitem.link = None
+ self.sub_menuitem_2.link = self.link
+
+
+ def test_single_level(self):
+ # The link object maps to a single level in menu 2
+ out = crumb_template_2.render(Context({"object": self.link}))
+ self.assertHTMLEqual(out, """
+
+ Link 1
+
+ """
+ )
+
+ def test_multilevel(self):
+ # The link object maps to the submenu in menu 1
+ out = crumb_template_1.render(Context({"object": self.link}))
+ self.assertHTMLEqual(out, """
+
+ Link 1
+
+ >
+
+ Link 1
+
+ """
+ )
+
+ def test_menu_slug_not_found(self):
+ # If we cannot identify the menu it comes from, take the first one.
+ out = crumb_template_3.render(Context({"object": self.link}))
+ self.assertIn("Link 1", out)
+
+ def test_no_matching_menuitem(self):
+ # If the object does not show up in any menu, render nothing
+ self.menuitem_2.link = None
+ out = crumb_template_3.render(Context({"object": self.link_2}))
+ self.assertHTMLEqual("", out)
+
+ def tearDown(self):
+ pass