Adding data to a template

Suppose we have some data that we want to use in a template. We therefore need to pass that data into the template’s “context”. It could be anything — a simple value or a list of objects retrieved using the ORM. Using The Pattern I described earlier, how do we do that?

For the sake of argument, we are going to put today’s date into the context, with the name today, and I’m going to assume you are writing the home page view for your site.

As we said, the answer to how do anything in a view is “Just do it”:

from datetime import date

def home(request):
    return TemplateResponse(request, "home.html", {
        'today': date.today(),   # This is the line you add
    })

Note

Formatting: I’m formatting my code examples in line with PEP8, and after that for clarity, especially to highlight things that have changed. So this example adds one line to our pattern, and I’ve formatted it accordingly. There is no need to following the formatting, and you (or your tools) might have other ideas!

Imports: For brevity I’ll omit any imports I’ve already mentioned. If you want full source code, have a look at the code folder.

The template will decide how to format the date (most likely using the date filter), so we used a date object rather than a string. Our pattern already had an empty context dictionary sitting there, waiting to be filled up, so we just put the value right in. Done!

There is a variation on this, which is that sometimes it helps to pull out the context data into a variable first, especially if we are conditionally adding data to it:

def home(request):
    today = date.today()
    context = {
        'today': today,
    }
    if today.weekday() == 0:
        context['special_message'] = 'Happy Monday!'
    return TemplateResponse(request, "home.html", context)

That’s it! Next up: Common context data.

Discussion: Embarrassingly simple?

This code is so simple it might not seem worth mentioning. Yet, with Class Based Views, the equivalent is anything but simple. Suppose we start with a TemplateView, or a subclass:

class HomeView(TemplateView):
    template_name = "home.html"

The context dictionary passed to the template is nowhere visible in this code. The fact that there is such as thing as a context dictionary is not obvious — all this has been hidden from the developer.

The minimum I can possibly write as a developer is to calculate the data — date.today() — and pick a name for it — 'today'. With the FBV, the code I actually end up adding is:

'today': date.today(),

So it’s extremely hard to see how this can be improved.

With a CBV, however, what you have to add is this:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['today'] = date.today()
    return context

If I’m lucky then most of this method has already been written for me (in which case we then have a boilerplate issue), but it might not have been. I have to know this API, and there is plenty that can go wrong — a wrong signature, or failing to call super() (which may not have immediate problems, but could cause problems down the road) — enough that people need to write blog posts about it.

Is this a real problem? Am I making a mountain out of a molehill?

Here is another blog post about putting data on your home page. The author’s first attempt involved using template tags to solve this problem — something he was very embarrassed about. But he shouldn’t be embarrassed — for a newbie, you would have to be a pretty capable developer to actually successfully pull off all the parts needed for a custom template tag.

Rather, he struggled for so long because of a bad starting point that was making a simple thing hard. If we as the Django community have made this hard, we are the ones who should be embarrassed.

Discussion: Boilerplate

With the above in mind, do we have more boilerplate with CBVs or FBVs?

Before we answer that, the first thing to note about boilerplate (by which I mean repeated code that just Needs To Be There For Some Reason) is that a small amount of it is not a big problem in software development. We don’t spend most of our time typing, we spend most of our time reading code. This means that clarity is much more important than shaving a few keystrokes. Arguments about small amounts of boilerplate should not be the major factor.

The real issue with boilerplate, in fact, is not how much typing it involves, but that verbose code hinders comprehension due to the low signal-to-noise ratio. Boilerplate reduction should be almost entirely about code comprehension, not typing reduction.

For example, if we wanted, we could reduce the “repetition” of having request as an parameter to each view function using threadlocals and an import. We could go further, and remove the import using some magic like web2py does. But we recognise both of these as bad ideas, because they reduce clarity. Those functions have request as a parameter because it is an input to the function. Making it an implicit one, instead of an explicit one, would hurt you in lots of ways.

With that said, let’s do a comparison. Once you have added the need for context data, as above, the CBV equivalent to the view I wrote above is as follows:

from django.views.generic import TemplateView


class HomeView(TemplateView):
    template_name = "home.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['today'] = date.today()
        return context
urlpatterns = [
    path('', views.HomeView.as_view(), name='home'),
]

This is now significantly longer than the FBV, by any measure. I think this is a fairer comparison in terms of boilerplate, because you almost always need to add extra context data. In fact, so many people have found this, that they have created boilerplate generators/snippets for starting new CBVs — such as this for emacs and this for vim, and this for Sublime Text.

The result of this is that if you search GitHub for get_context_data, you’ll find hundreds and hundreds of examples that look like this, which otherwise wouldn’t make much sense.

class HomeView(TemplateView):
    # ...

    def get_context_data(self):
        context = super(HomeView, self).get_context_data()
        return context

In other words:

  • The boilerplate you need for a basic CBV is bigger than for an FBV.

  • It’s so big and tedious that people feel the need of snippets libraries.

Maybe the boilerplate issue will be better when it comes to DetailView etc? We’ll see about that.

OK, we have more boilerplate, but have we improved code comprehension? Well, the CBV is very explicit about use of templates, but so is the FBV. Now that we’ve added get_context_data, the CBV is clear about context data, but so was the FBV. However, CBV has made the key elements of the view invisible, as we noted before, and obscured all the control flow, so I think it is difficult to argue this is a win for comprehension.