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