Sitemap
Django Unleashed

Unleashing the Full Potential of Web Development

Photo by Helen Ast on Unsplash

Django Tutorial: Writing a Breadcrumb CBV Mixin

4 min readMay 13, 2025

--

One of the things I love about Django CBVs is the capability to create reusable blocks of functionality that can be bolted-on to any existing View, simply by inheriting from Mixins. In my last two articles, I showed how to write two example mixins for creating single record and multi-record custom actions. In this article, I will do something a little different and show how to write a Mixin to add easily extend any View with fully-functional breadcrumbs.

The Environment

For this article, let’s assume we have a Django project with multiple apps, each with multiple views — the conventional setup for a typical Django project. The one we’ll be working with will have the following directory structure:

- MyDjangoProject
\- apps
|- produce
\- geometry

Within these projects, we’ll have a number of models and CBVs already up and running — for example, here is the views.py for the produce app:

class FruitListView(ListView):
# ...

class FruitDetailView(DetailView):
# ...

class FruitCreateView(CreateView):
# ...

class FruitUpdateView(UpdateView):
# ...

class VeggieListView(ListView):
# ...

class VeggieDetailView(DetailView):
# ...

If we want to add breadcrumbs to every view across every app, we would need to update every template with each specific set of breadcrumbs:

<div class="row">
<a href="{% url 'home' %}">Home</a> &gt;
<a href="{% url 'fruits-home' %}">Fruits</a> &gt;
Add Fruit
</div>

The obvious first step to make this more reusable is to abstract this code into the base.html template and have all views add some context data with the view-specific breadcrumb names and links:

<div class="row">
<a href="{% url 'home' %}">Home</a> &gt;
<a href="{{ app_url }}">{{ app_name }}</a> &gt;
{{ view_name }}
</div>

On the View side:

class FruitCreateView(CreateView):
def get_context_data(self):
context = super().get_context_data()
context['app_name'] = 'Fruits'
context['app_url'] = reverse_lazy('fruits_home')
context['view_name'] = 'Add Fruit'
return context
# ...

While this gets the job done, it carries some clear downsides: we need to set extra_context variable (or override the get_context_data method) on all views, with most of the code being copy-pasted boilerplate.

We can improve on this functionality by turning it into a fully-fledged Mixin with a clear API which Views can inherit from and customize the list of crumbs to display.

Creating the Mixin

Our new Mixin will start very simple — we’ll define a list of tuples to keep track of each individual “crumb”, which subclasses can overwrite, and then we’ll automatically add this list to the context by injecting additional context vars via the get_context_data method:

class BreadcrumbMixin:
breadcrumbs = []

def get_context_data(self, **kwargs):
"""
Add breadcrumbs to the context
"""
context = super().get_context_data(**kwargs)
context['breadcrumbs'] = [{'title': title, 'url': url} for title, url in self.breadcrumbs]
return context

Next, let’s provide the capability for subclasses to overwrite/customize the list of breadcrumbs. We can take inspiration from how existing Django Views do this — by also providing an overwritable method in addition to the existing class variable.

class BreadcrumbMixin:
breadcrumbs = []

def get_breadcrumbs(self) -> list:
breadcrumbs = [('Home', 'home'),]
breadcrumbs.extend(self.breadcrumbs)
return breadcrumbs

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['breadcrumbs'] = [{'title': title, 'url': url} for title, url in self.get_breadcrumbs()]
return context

Note that the get_context_data method has been updated to call into our new get_breadcrumbs method. By default, we automatically add the home breadcrumb to the list to save consumers the extra step. Here is an example of how a CBV can implement this new Mixin:

class FruitCreateView(BreadcrumbMixin, CreateView):
breadcrumbs = [
('Fruits', 'fruits-home'),
('Add Fruit', 'fruits-add'),
]
# ...

On the template side, we encapsulate the existing code into a subtemplate which we can either add to our base.html template or simply include on all our templates:

{% include 'subtemplates/breadcrumbs.html' %}

Now we have a fully functioning reusable Mixin for breadcrumbs! Next, let’s see if we can have it auto-generate the defaults.

Auto-generating Defaults

Ideally, Views can use the defaults most of the time and overwriting the methods becomes the exception rather than the rule. Since every page is different we’ll need to add some logic to the Mixin so that it can automatically detect and generate its own list of breadcrumbs. The best way to do this is to expand the get_breadcrumb() method in the Mixin. The tricky part will be automatically finding the URL name for the app’s “correct” home page. One way to do this is to guess it by importing the current module’s urls.py then finding the empty (“home”) URL entry in the app’s urlpatterns.

    def get_breadcrumbs(self) -> list:
breadcrumbs = [('Home', 'home'),]
# If breadcrumbs are defined, use those:
if self.breadcrumbs:
breadcrumbs.extend(self.breadcrumbs)
return breadcrumbs
# Otherwise, automatically generate breadcrumbs:
if issubclass(self.__class__, SingleObjectMixin):
# Determine the current app then find the empty route (i.e. index page):
urls_module = self.__module__.replace('.views', '.urls')
urls_module = importlib.import_module(urls_module)
# Iterate over the URL patterns in the module to find the blank pattern
blank_urlpattern_name = None
for pattern in urls_module.urlpatterns:
if isinstance(pattern, URLPattern) and pattern.pattern._route == '':
blank_urlpattern_name = pattern.name
break
list_name = self.get_object()._meta.verbose_name_plural
breadcrumbs.append((list_name, blank_urlpattern_name),)
# Add the current view to the breadcrumbs:
breadcrumbs.append((self.get_object().__str__(), None),)
else:
breadcrumbs.append((resolve(self.request.path).func.__name__, None),)
return breadcrumbs

In the code above, we check against SingleObjectMixin so we can catch all of the view types that operate on a single record: DetailView, UpdateView, CreateView, DeleteView.

Now our CBVs can implement the Mixin with minimal or no additional changes at all:

class FruitCreateView(BreadcrumbMixin, CreateView):
model = Fruit
template_name = 'create_fuit.html'

With a single new Mixin, all the CBVs across our apps can now very easily gain the breadcrumb functionality with minimal effort, while still maintaining the ability to adapt and change the generated links whenever the situation calls for it.

--

--

No responses yet