Programming is a wonderful mix of art and science; source code is both a poem and a math problem. It should be as simple and elegant as it is functional and fast. This blog is about that (along with whatever else I feel like writing about).

Sunday, October 26, 2008

Breadcrumb History in Django

This afternoon I wanted to add a breadcrumb-like feature to an application I've been writing in Django. I figured the best way to do this is with sessions, so the first step is to turn those on.

The way I wanted it to work is like a rolling history of pages visited. So there's no implied hierarchy of pages, it just shows you the pages you've visited most recently. I figured limiting it to four pages was reasonable, but I can certainly see myself increasing that number in the future.

The first thing to do is to create a helper function that adds the current page to the history. You want to create the default empty history if there's nothing for it in the session, and you want to make sure it doesn't get too long. Also, you access the session and the address of the current page through the HttpRequest object that's available to every view in Django, so we just pass that in.

Here's my function:
# the breadcrumb history is a list of the last pages the user has visited
# the newest page is at the end of the list
# the length of the list is limited; currently it is at four pages
def add_breadcrumb_history(request):
    history = request.session.get('breadcrumb_history', [])

    # if the last item in the history is the current page, we don't want to add this page to the history
    # if it's not the last item in the history, we do add it
    if len(history) == 0 or history[len(history)-1] != request.path:
        history.append(request.path)

    # if there are more than four items in the history, pop the first one
    if len(history) > 4:
        history.pop(0)

    # save the history to the session
    request.session['breadcrumb_history'] = history

    # return the current breadcrumb
    return history

And in your views, its usage is simple enough. You call:
breadcrumb = add_breadcrumb_history(request)

in your view, preferably after you verify the user's permission to view that page. Then you want to add it to the list of variables exposed to your template, usually in your call to render_to_response().

Then you have to add the breadcrumb handler to the template. I put mine in my base.html, so it's available on every page.
{% if breadcrumb %}
<p class="breadcrumb">
{% for page in breadcrumb %}
<a href="{{ page }}">{{ page }}</a>
{% endfor %}
</p>
{% endif %}

Very simple; if there's no breadcrumb history, it doesn't display anything. If there is, it displays the links in order.

I like this feature; while I recognize it's close to useless in some situations (namely those where the data actually adheres to a strict hierarchy), in others it can be really useful. It means I have to do a lot less state checking on my pages to try to "calculate where the user came from" to get to that page, and can trust that users can easily navigate back to recent pages even if they POSTed information in between. (While I like that "You POSTed information on this page, do you really want to hit the back button?" feature, it can be really annoying in practice.)

Just thought I'd toss this up there in case anyone found it useful -- or is aware of similar functionality elsewhere.

No comments: