My Favorite Secret Django Idiom
Here’s a fun Django trick I “invented”…
And later learned a few others discovered first.
But not enough people know about it yet. So let me explain. And even if you are not into Django, this is also a fun Python trick. So buckle up.
Quick intro paragraph, which you can skip if you speak fluent Django:
Django lets you define functions called “views”, which generate the response back to the client: returning the HTML, or JSON, etc. This function takes an argument called “request”, with info about the HTTP request from that client. And when a webpage has a form, there’s a request.POST attribute containing the submitted form data. Oh, and Django views often use a function called “render()” to generate and return that HTML, JSON, etc. to respond with.
Okay, now that you’re a Django expert, let’s continue:
Imagine you have a Django view named choose_course()
, using a form called CourseSelectForm
. So the user can pick a course to enroll in. If you follow what’s suggested in the Django docs, you might write it like this:
from .forms import CourseSelectForm
def choose_course(request):
if request.method == "POST":
form = CourseSelectForm(request.POST)
if form.is_valid():
return render("courses/finish.djt", {
"form": form,
})
else:
form = CourseSelectForm()
return render(request, "courses/course-prep.djt", {
"form": form,
})
Everyone who uses Django knows they have some of the best documentation in all of open-source software…
But in this case, they are wrong.
Actually, they’re not wrong. That code works great.
But in almost every instance of a Django view using a form, there is a better way. A way that is simpler, more readable, and less error-prone.
That way is to use “request.POST or None”. Like so:
def choose_course(request):
form = CourseSelectForm(request.POST or None)
if not form.is_valid():
return render("courses/finish.djt", {
"form": form,
})
return render(request, "courses/course-prep.djt", {
"form": form,
})
Only four logical lines of code in the body. Simple and clear, compared to the previous version.
Let’s break it down:
- When the page first loads in the browser, it will be done as an HTTP GET. So
request.POST
will beNone
. - That means the expression
request.POST or None
evaluates asNone or None
, i.e.(boolean false) or None
, reducing toNone
. - So
form
is set toCourseSelectForm(None)
. But the first argument of Django Form objects defaults to None, so that’s the same asCourseSelectForm()
. - Because of that, the call to
form.is_valid()
will returnFalse
. So it will skip to the finalrender()
line, presenting the ready-to-fill-out form.
Now, that’s the case when the page first loads. What happens when the form is submitted?
- Because this is a form submission, it will use an HTTP POST request, and
request.POST
will be an object containing the form data. - Hence,
request.POST
is “truthy” andrequest.POST or None
will short-circuit to the value ofrequest.POST
. - This is then fed as a non-default-value first argument to
CourseSelectForm()
. - And if the form data is good, then
form.is_valid()
will return True. Causing the view function to return the firstrender()
. - If the form data was bad, it falls through to the final
render()
, so they can try again.
I have implemented many dozens of Django webapps, and this is the way I nearly always implement my form views.
And it works great.
Not only does it work correctly — which of course is critical and non-negotiable. It is also more concise, while staying readable; simpler, and thus less error-prone; and easier to modify and maintain.
All great stuff!