Django 2008/12/15
I asked Malone the other week why
Django made me go all return render_to_response('foo.html')
in my view named foo
instead of assuming some defaults.
To be explicit, candidates for those defaults might go something along
the lines of:
- If a view function returns
None
(which is, it seems what happens when you don't return anything), render the template that shares its name with the view. - If a view function returns a
dict
, render the template that shares its name with the view, passing thisdict
wrapped up in aContext
. - If the view function returns a string, take the string as the template name.
- If the view function returns a 2-
tuple
containing a string and adict
, take the string as the template name and make thedict
into aContext
.
On top of that, I asked him why Django didn't figure out what response format makes sense, given the URL and headers in the request and the formats available for the template chosen. His response was basically that Django hates magic. If it did all of these things, it'd favor convention over configuration and be called Rails. Or Pails.
After playing around with Django for a few days and now starting to use it for a real project, I'm seeing the wisdom in Django's (and indeed, Python's) choice to avoid magic. I've already used the word "explicit" once in this post and that's what this is really all about.
Additionally, Ruby seems to skew towards implicitness in the language, while Python skews toward explicitness. I like explicitness. — Joe Stump
I'm not here to start a Ruby-versus-Python or Rails-versus-Django fight. I'm here to talk about being explicit with code.
My efforts a while back hacking
the Ruby interpreter were a bit misguided. I was heavily and
prematurely optimizing my desired use case at the expense of code clarity
and at least seven other traits generally considered to be positive. In
the resulting Frankenruby interpreter, @foo
could show
up in a file outside of a class with no warning, meaning that file could
only ever be used by my very streamlined URL-to-code routing script. In
retrospect, it's a good thing I never got it to work just right.
The desire to remove as many declarative, housekeeping-esque lines of
code is seemingly noble but it is at odds with another principle of mine,
learned from years in the trenches writing C(++): always
#include
everything a file uses. This makes files instantly
more portable and less magical. Despite Django's mascot being a magical
pony, I gather that it, too, avoids magic in most scenarios.
Being explicit allows Django to be loosely-coupled. Imagine trying to
remove the Django ORM or templating system if they were omnipresent in
every Django app from start to finish. Tedious. As it is, a single
"#
" sets you free. Even the concept of having many apps
within a project enables further explicit use or disuse of each package.
Being explicit also lowers the barriers-to-entry for would-be framework
hackers like myself. My first serious afternoon saw me trying to create
a fairly involved template tag. It would have made page titles work just
about like the excellent Headliner plugin for Rails. I discovered along
the way that the {% block foo %}
template tag, if placed within
another block, would both override the named block and output its contents
in place. Observe, here's how I decided to do page titles in Django:
In templates/layout.html
:
<title>Foo — {% block title %}{% endblock %}</title>
In other templates:
{% extends 'layout.html' %} {% block title %}Only shows up in the title tag{% endblock %} {% block content %} <p>foo</p> {% endblock %}
{% extends 'layout.html' %} {% block content %} <h1>{% block title %}Shows up here and in the title tag{% endblock %}</h1> {% endblock %}
The ORM looks similarly easy to figure out though I haven't taken the
plunge yet. I already know what I'm going to build to learn it, though:
a command-line SQL tool that works through a combination of Python and
hard links. The python script will find the closest Django settings module
and use the database connection info there. The command will be invoked by
typing out a SQL statement on the command line. The SQL statement will be
' '.join(sys.argv)
and the commands SELECT
,
INSERT
, etc. will be routed to the Python script using hard
links. I don't know if this is a good idea but I know it'll be relatively
easy to do thanks to how easy it seems to be to dive into the Django
internals.
Comments (1)
I think part of the reason Django can get away with avoiding magic like this is because of the dynamism of Python. For example, a simple function decorator is sufficient to duplicate the view rendering behavior you're missing from Rails. Here's my 5 minute implementation (minus error handling or any sort of testing):
from django.shortcuts import render_to_response
from functools import wraps
def default_template(view_func):
@wraps(view_func)
def _wrapper(*args, **kwargs):
result = view_func(*args, **kwargs)
if result is None:
return render_to_response('%s.html' % func.__name__)
elif hasattr(result, 'items'):
# result is a dictionary
return render_to_response('%s.html' % func.__name__, result)
elif isinstance(result, basestring):
# result is a strong
return render_to_response(result)
elif hasattr(result, '__iter__') and len(result) == 2:
# result is an iterable with two elements
return render_to_response(result[0], result[1])
return result
return _wrapper
It'd be equally easy to write a decorator that chooses a template based on extension. In fact that's exactly how I tend to do data serialization for APIs written in django. The view returns a dictionary (or a subclass of some object I know how to serialize) and then I look at the extension and serialize to whatever format is desired. That way I'm not duplicating the logic to choose serialization format in every view function. To accomplish this I usually require that view functions have a kwarg called extension that's parsed out of the request URL by the URL handler logic. The nice thing is that I'm imposing this convention on myself, which gives me lots of flexibility. The downside is that I have to write a bit more code (or find it on djangosnippets.org).
— Mike Malone — 2009/01/20 2:30 pm