November 29, 2012

My Flask to Django Experience

I’ve recently "had" to work with Django again when creating Oath. It was an interesting experience because since Flask was released as an evolved April Fool’s joke I’ve never used anything but it.

Django’s batteries included approach and early arrival on the scene made it a much loved framework at the time, and it still is. As is the nature of most ecosystems, specialization breeds success. The monolithic approach holds Django back in some subtle ways. It’s burdened by backwards incompatibility but also bad decisions. Over the years since the release of Django the community has developed a stronger sense of Pythonicism; the written and unwritten agreement that guides the way problems are approached and solved in Python.

When you get "pythonic" right it will appear to you as an aesthetic. Hidden complexity without magic, exposing innards when needed but otherwise hidden, PEP8 within reason and as simple as possible without being too simple. Everyone loves Kenneth’s requests because it adheres so strongly to these principles.

Let’s talk about the most clear-cut example where Django simply fails: Templating. Django nailed the syntax and top level features: block inheritance and {{ moustaches }}. What it didn’t get was where to constrain the user. Jinja2, of course, gets this right when allowing much richer looping, extending, mangling and even gasp Python code (albeit a subset thereof). Django’s template philosophy is one of constraint whereas Jinja2 embraces the complexity, but allows you to carry it over to where it belongs. Pragmatists know code is documentation. Iffy presentation is therefore documented where it concerns itself: in the templates! This is why Flask views come damn close to working for multiple content type responses (JSON and HTML).

A view shouldn’t prepare a presentation. It should more or less just hand over data. To give the user more power it falls back to template tags, which is really just a reinvented callable with mysterious undocumented powers and horrible stack traces. How could you not just want a Python callable here? Here’s a bit of Flask that should blow your mind if you are using Django: {{ url('user', pk=user.pk, _external=True) }}. It can do that because:

  1. The request object is implicitly context aware. Import and use without passing around routes, views and render functions. It’s a hack, but a hack well documented and thoughtfully coded. That’s why url() can reconstruct the protocol and hostname to create an external link to a view. Without a bloody SITE_ID setting.
  2. Callables in templates are just that, callable.

So I’d say going back to Django was more or less frustrating although some of it was just me having to recall a bunch of edge cases and gotchas. Here are some points I gathered:

  • Regular expressions are overkill for URL routing.
  • There are too many ways to render templates (render, render_to_response, HttpResponse, ...) all of them with a set of drawbacks.
  • URL reversal is a mess especially in templates, like nobody thought of the interface, just the implementation. Why the explicit args here reverse('name', args=[])?
  • Why no top level models.py. "Everything is an app" forces premature optimization for most (all?) projects.
  • request.json would be nice ... requests and Flask both have it.
  • While we’re at it, jsonify() for application/json responses.
  • WSGI as an Application objects is so much better than this magic bootstrappin DJANGO_SETTINGS_MODULE and URL_CONFIG stuff.

Thought exercise: Make a single app.py in Django. Is it forced or pretty?

Now the reason I stuck with Django for this project was that the codebase had gathered some battle experience I didn’t want to discard. A rewrite would have killed the momentum. Another reason was Haystack, a very slick library that wraps fulltext indexers in a pythonic. It’s sort of beyond me why it’s coupled with Django though. A Celery approach would be welcome here.

The libraries that came after Django had the benefit of learning from its strengths and weaknesses. It became popular at a time when "pythonic" hadn’t penetrated the community psyche. For what it is, it presents a compelling case for newcomers. Great documentation for 90% of all you need, in one fucking place.

I see some parallels with jQuery. Both have well known extensions that have no reason to couple to the framework other than marketing to people that are safezoned by the framework ecosystem. Both are big and challenged by smaller more nimble libraries, and both have seen tremendous growth and did a good job with documentation and managing community efforts. Times have changed I guess.