Django views versus the Zen of Python

A while ago, I wrote about how I write Django views, arguing that the base View class hits the sweet spot between the simplicity of function-based views and the often-overwhelming complexity of generic class-based views. The response to that article made it clear that I’m not alone in this thinking.

It got me wondering: why does this approach feel so right? The answer, I believe, lies in the guiding principles of Python itself. The Zen of Python isn’t just a collection of clever aphorisms; it’s a framework for writing clear, maintainable, and effective code. When we look at Django’s view layer through this lens, it becomes apparent where things start to go astray.

Here are all the ways that Django’s views (especially the generic class-based kind) break the Zen of Python.

“There should be one - and preferably only one - obvious way to do it.”

Python developers cherish this principle. It promotes consistency and readability. Yet, when it comes to something as fundamental as rendering “Hello, world!” in Django, the options are dizzying.

Just look at the many ways to accomplish this simple task:

The simple function:

from django.http import HttpResponse

def hello_world(request):
    return HttpResponse("Hello, world!")

The base View class:

from django.http import HttpResponse
from django.views import View

class HelloWorldView(View):
    def get(self, request):
        return HttpResponse("Hello, world!")

The generic TemplateView:

from django.views.generic import TemplateView

class HelloWorldView(TemplateView):
    template_name = "hello.html"

The render shortcut:

from django.shortcuts import render

def hello_world(request):
    return render(request, "hello.html")

The TemplateResponse:

from django.template.response import TemplateResponse

def hello_world(request):
    return TemplateResponse(request, "hello.html")

This abundance of choice leads to inconsistency across projects and even within a single codebase. It creates cognitive overhead for new developers who are forced to learn five ways to do one thing. By standardizing on a single, clear pattern, like the base View class, we can bring a project back in line with this core Pythonic principle.

“Flat is better than nested.”

Have you ever tried to understand where the magic in a generic DetailView comes from? You might need a map. The inheritance tree for even the most common generic views is a deep, tangled web. A DetailView inherits from SingleObjectTemplateResponseMixin, BaseDetailView, which inherits from SingleObjectMixin, ContextMixin, and finally the base View.

This nesting is the antithesis of “flat.” Logic is scattered across multiple parent classes, making it difficult to track down the source of behavior. Contrast this with the base View. All its methods—get(), post(), setup(), dispatch()—are at the same level. The logic is right there in front of you, not hidden five levels deep in an inheritance tree.

“If the implementation is hard to explain, it’s a bad idea.”

This follows directly from the last point. The deep nesting of generic CBVs makes them incredibly hard to explain. To truly understand how a DetailView works, you need to read the method flowcharts in the documentation and trace method calls like get(), get_context_data(), and get_object() across five different classes.

Contrast with this: “A request comes in, the dispatch method on the View class checks the HTTP method, and then it calls the method with that name, like get or post. That’s it.”

One of these is a bad idea. When you have to keep a mental map of a framework’s internal machinery just to display a single database object, the abstraction has failed. It has become more complex than the problem it was meant to solve.

“Errors should never pass silently.”

This is one of my biggest frustrations, and it lies in Django’s template language. By design, the template engine swallows errors. If you make a typo in a variable name, it doesn’t raise an AttributeError or KeyError. It fails silently, rendering an empty string.

This design decision has a direct impact on debugging our views. Did I forget to add user_profile to my context dictionary in the get_context_data method? Did I misspell it as user_pofile in the view? Or did I misspell it in the template? The silent failure makes it much harder to pinpoint the source of the bug, forcing you to debug in three places at once. This is a clear violation of a principle designed to make debugging easier.

“Practicality beats purity.”

The Django template language was designed with the “purity” of separating logic from presentation in mind. It’s a noble goal, but in practice, it has led to some deeply impractical limitations.

It’s wild to me that after all these years, we still can’t do something as basic as mydict[key] or mylist[0] in a template. Instead, we have to reinvent the same getitem template tag in nearly every project.

This is a classic case of purity getting in the way of practicality. The template language is intentionally crippled in its capabilities to enforce a philosophical ideal, leaving developers with a less powerful and more frustrating tool.

Finding the path

Django is a powerful and wonderful framework, but like any tool, it can be misused. The generic class-based view system, in its attempt to be a one-size-fits-all solution, often creates more complexity than it saves.

By returning to the principles of the Zen of Python, we can find a better way. The base View class offers a more Pythonic path. It’s simple, not complex. It’s flat, not nested. Its implementation is easy to explain. It encourages us to be explicit and to build solutions that are practical and maintainable for the long term.

Django doesn’t force you to write views in a way that violates the Zen of Python, but it often nudges you there. By resisting the pull of generic CBVs and sticking with simpler patterns, you can build Django projects that feel a lot more like Python itself.

Written by

Kevin Renskers

Related articles

› See all articles