For Django sites with URLs structured in a hierarchical way I found it helpful to have an app to quickly transform an URL like https://example.com/reference/instrument/guitar/ into Home > Reference > Instrument > Guitar with links to each path part.
The following code does that:
- A context processor analyze the URL request e.g.
https://example.com/reference/instrument/guitar/, and extracts the path:/reference/instrument/guitar/
dynamic_breadcrumbs/context_processors.py
from dynamic_breadcrumbs.utils import Breadcrumbs
def breadcrumbs(request):
"""
Add breadcrumbs dict to the context.
"""
base_url = request.build_absolute_uri("/")
breadcrumbs = Breadcrumbs(base_url=base_url, path=request.path)
return {"breadcrumbs": breadcrumbs.as_list()}
- For each part separated by
/tries toresolveit and get the specific View that handles that URL (for customizing that part of the breadcrumbs label, and possibly further customization in the future)
dynamic_breadcrumbs/utils.py
from django.urls import resolve, Resolver404
from urllib.parse import urljoin
from . import app_settings
class Breadcrumbs:
def __init__(self, base_url="", path=None):
self.base_url = base_url
self.path = path
self.items = []
def get_items(self):
if not self.items:
self._fill_items()
return self.items
def as_list(self):
return [item.as_dict() for item in self.get_items()]
def _split_path(self, path=None):
"""Returns a list of the path components between slashes"""
if not path:
path = self.path
if path.endswith("/"):
path = path[:-1]
if path.startswith("/"):
path = path[1:]
result = path.split("/")
return result
def _fill_items(self):
# add home
b_item = BreadcrumbsItem(
base_url=self.base_url,
name_raw=app_settings.DYNAMIC_BREADCRUMBS_HOME_LABEL,
path="/",
position=1
)
self.items.append(b_item)
# add paths
path = "/"
for i, item in enumerate(self._split_path()):
path = urljoin(path, item + "/")
b_item = BreadcrumbsItem(
base_url=self.base_url, name_raw=item, path=path, position=i + 1
)
self.items.append(b_item)
class BreadcrumbsItem:
def __init__(self, name_raw, path, position, base_url=None):
self.name_raw = name_raw
self.path = path
self.position = position
self.resolved_url = self._get_resolved_url_metadata()
self.base_url = base_url
def _get_resolved_url_metadata(self):
try:
func, args, kwargs = resolve(self.path)
return True
except Resolver404:
return False
def get_url(self):
result = urljoin(self.base_url, self.path)
return result
def get_name(self):
# if self.resolved_url:
# # check view
return self.name_raw
def as_dict(self):
result = {
"position": self.position,
"name": self.get_name(),
"path": self.path,
"url": self.get_url(),
"resolved": self.resolved_url,
}
return result
def __str__(self):
return "{}: {} {}".format(self.position, self.name_raw, self.path)
- Adds to the request context a list of names and urls
- home -> https://example.com
- reference -> https://example.com/reference
- instrument -> https://example.com/reference/instrument
- guitar
A template shows the above list with links for each level
Home > Reference > Instrument > Guitar
templates/dynamic_breadcrumbs/breadcrumbs.html
{% for item in breadcrumbs %}
{% if item.resolved %}<a href="{{item.url}}">{% endif %}
<span>{{item.name}}</span>
<meta itemprop="position" content="{{item.position}}" />
{% if item.resolved %}</a>{%endif%}
{% endfor %}
The way the Breadcrumbs class processes the path can be improved or can be structured in a simpler way?