In this post, I show a small fix to an issue that I encountered while building a Django-based application.

Introduction

I'm quite a fan of the Python standard library, which has many useful gems that ease the development of application.

One of these are defaultdicts, which act like a regular dict, but instead of throwing errors on missing keys, they allow you to set a default value.

This can help simplify code, for example: Sometimes I take a set of models out of the database, and want to group them by one of their attributes in Python. I won't do this in SQL, as I need each single object, not an aggregated result.

Without a defaultdict, you'd have to check during iteration if the dict already has a list for the given key, and if not set it.

django-defaultdict/defaultdict-example.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# --- Grouping models using key existence checks ---
grouped_models: dict[str, list] = {}

for obj in Model.objects.all():
    if obj.category not in grouped_models:
        grouped_models[obj.category] = []
    grouped_models[obj.category].append(obj)


# --- Grouping models using setdefault ---
grouped_models: dict[str, list] = {}

for obj in Model.objects.all():
    grouped_models.setdefault(obj.category, []).append(obj)


# --- Grouping models using  defaultdict ---
from collections import defaultdict

grouped_models: dict[str, list] = defaultdict(list)

for obj in Model.objects.all():
    grouped_models[obj.category].append(obj)

While the approach using setdefault has the least amount of lines, I personally find it quite ugly and never used it in projects.

The problem

When you try to iterate such a defaultdict in a Django template, nothing will be rendered in the page at all.

Django
{% for key in grouped_models %}
  ...
{% end %}

Even if you explicitly use the dict.items() method, nothing happens:

Django
{% for key, value in grouped_models.items %}
  ...
{% end %}

The reason for this is described in a quite old bug ticket from 2011, which led to a documentation change, which unfortunately is no longer part of the current documentation.

The fix

To fix this issue, just cast your variable back to a regular dict before passing it to the template engine:

django-defaultdict/template-fix.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from collections import defaultdict

from django.shortcuts import render


def view(request):
    ...
    grouped_models: dict[str, list] = defaultdict(list)
    ...
    return render(
        request,
        "template.html",
        grouped_models=dict(grouped_models),
    )