Custom Filters and Ordered Dicts

This week I ran into a minor problem that took a surprising amount of time to resolve. Getting a Django template to produce a dict in sorted order.  While there were answers out there, none seemed to match the environment that I am using (python 3, Django 1.10).  After some experimentation, I finally came up with what I think is a good solution.  Feel free to tell me a better one if you know it.

The Problem

The basic problem was that I have a template which walks through a dict and displays it in a particular format.  As we all know, python dicts are not ordered, so the display would flip around between invocations of the server.  Not a huge deal, but annoying.  The template code looked like:

        {% for kid, days in kids.items %}
            <h1>{{ kid }}</h1>
             ... deleted stuff here ...
        {% endfor %}

StackOverflow to the rescue!

There was a quite good answer on SO which pointed me in the right direction, but it unfortunately did not work for my situation (again, I suspect python 2 vs 3 differences).

After doing some digging, I figured out that invoking the filter with dict.items()

{% for kid, days in kids.items|sort %}

was not resulting in the parameter as a dict, but rather as a ItemsView which is a ‘view’ into a dict.

I played some with using

{% for kid, days in kids|sort %}

which did result in a dict getting passed into the filter, but returning the OrderedDict resulted in some strange iterable being returned (the number of items to unpack changed on each iteration, so I suspect that it was flattening my structure somehow, but I did not pursue that).

I decided to embrace the original form and move forward with the ItemsView object that was getting passed in.  This turned out to be much shorter and easier code than the original, and so I went with that.

My Solution

Here’s where I ended up.  The filter looks like this:

from django import template
from collections import ItemsView

register = template.Library()


@register.filter
def sort(value):
    if isinstance(value, ItemsView) or isinstance(value, list):
        return sorted(value)
    else:
        return value

Note that since ItemsView is iterable and I want an iterable result, I can
just used sorted() on it as we did with lists.

To invoke this we call it with:

{% for kid, days in kids.items|sort %}

in the template.

For completeness, here’s the answer I posted on that SO question.

Author: Jim Anderson

I'm a gray-haired coder and an avid snowboarder. I've had a long career doing all levels of software development from assembly language on 8 bit micros to Java Servlets for enterprise servers. Mainly my work consists of C/C++ on embedded systems, however. I enjoy Python coding and Django as a hobby. Almost as much fun as a powder day!

Leave a Reply

Your email address will not be published. Required fields are marked *