On Django and Similar Software
1 March 2010
I have been looking for an opportunity to brush up on my Python and play around with Django for some time. This combo has a number of cool Portland kids in a tizzy lately, and I’ve wanted to see what all the fuss is about. I recently got a chance to do so.
Django is pretty good at doing the thing which it does: generating dynamic web pages. Many software packages have cropped up over the past 15 years which do this, and they’re all fairly similar in structure, mode of operation, and capability. Django is quite opinionated about how your programs should be written, and it enforces a nice, uniform structure on apps built with it. The framework itself is easy to learn and it’s easy to get up to speed on a project built with Django.
User interaction with Django apps works as follows. The server renders an HTML page with an HTML form on it. After the user thinks maybe he’s filled everything out properly, he sends the form’s data to the server as a lump of querystring-encoded key-value pairs, and he gets back a lump of HTML containing the values he submitted and some additional text describing what he did wrong. Repeat. Eventually he submits valid input and is redirected to another lump of HTML that indicates to him that his action succeeded, and displays any result data.
We can see this embodied in this code snippet from the Django documentation.
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
This function corresponds to a path on the server. It handles both the case of “rendering” an HTML page with a form in response to a GET request, as well as saving data and issuing a redirect to another HTML page in response to a POST request.
There is a subtle ambiguity given to the concept of POST in the above code snippet. The first time it’s being used is correct—it’s being compared to the incoming HTTP request method. In the second instance however, POST means “form data (either querystring-encoded key-value pairs or multipart/form-data) in the request body”. It’s unfortunate that Django’s API conflates these two concepts. Rather than elucidate what’s happening on a protocol level (“the client has made a POST request; the data in the request body is querystring-encoded key-value pairs”), Django’s API introduces an idiom (“POST is the thingy with which you save data from forms”.)
This idiom is the essence of my critique of Django and similar software. It allows you to take Tim Berners-Lee’s idea of navigation between interlinked documents, and turn that navigation into some kind of software program. This is wrong. In HTTP, data and documents are synonymous: user-agents create, read, update, and delete documents. The content of an HTTP document generated by server code should never attempt to code for a user experience, as dynamic, forms-drive websites do. Instead, an HTTP server should be conceptualized as a document-oriented backing store for user agents which themselves wholly define the user experience.
Ideally, an internet application works like this: client code (e.g.: AJAX, iPad) validates user input in a nice, immediate feedback sort of way. Once it has valid user input, the client invokes a simple HTTP API on the server with some nicely-structured JSON, and the server responds with an indication of success or some JSON error messages. The server never thinks about rendering views or forms or anything like that, because it is merely a backing store.
As web developers, we have been pounding at interaction problems with our form-post hammers for well over a decade. Until recently, we’ve felt that the resulting user experience was passable. But the new generation of mobile devices we’ve seen in the past two years has fundamentally changed what it means for an application to be ubiquitous, and more importantly, it has raised the bar for interaction design. It is quickly becoming clear that if the web application is to remain ubiquitous, it must provide a user experience which meets or exceeds that of a native mobile app.
The overlapping worlds of HTML, JavaScript, and CSS may be inelegant in practice, but in combination they are sufficiently powerful to deliver outstanding user experiences. It is a fallacy that we must submit ourselves to the tyranny of the web browser’s history controls and code for user experience on our servers using navigation. Instead, we should be writing servers that expose the functionality of our applications to the internet over HTTP, without a particular client in mind, and we should write clients which offer excellent user experiences on whatever platform they target.