What's Wrong With Django?

Nothing!

In 2010, I discovered Django when I took on a client with a project that was already in progress.

Up until that time I had been coding command line tools using Python for many years, but for the Web I was using PHP and Zend Framework.

I had previously experimented with Django as well as web2py, Pyramid, and Pylons, but no serious attempt was made to learn or use any of these because of the substantial, historical investment in PHP.

(Not that I was happy with PHP or Zend Framework, but like many developers, I had a large collection of resources and libraries built up around the language.)

This new project was messy and in trouble. Lots of developers had been involved but had all fallen away over time. The last developer had announced he was leaving the project.

The code base was fragmented and mostly undocumented. At roughly 300,000 lines, it was quite a challenge to learn what was going on.

Django, however, made this somewhat easier because, regardless of whatever disjointed combination of apps, packages, and modules a developer may assemble in a project, everything still boils down to models, views, and templates.

Sure, there may be middleware, context processors, and other things going on, but one can always start with settings.py and urls.py and reverse engineer from there.

Additionally, Django provides a degree of consistency in implementation which provides welcome landmarks for the intrepid explorer of a new project.

Django makes it harder to do things badly.

So What's "Wrong" With Django?

Not much. The ORM is fantastic. The architecture is easy to learn and use. If one understands the conventions, it is possible to quickly build a useable project.

There are some short-comings.

No API

REST is not built in. It would be great to define an api.py module similar to admin.py with a standard means of implementing REST.

It turns out, though, that this is okay. The excellent, Django REST framework provides this functionality.

Class-Based Views

Class-based views, in theory, are great. As implemented, not so much. They make things unnecessarily complex under the hood. This criticism should be tempered with the fact that the implementation had to be usable across a wide array of use-cases.

However, it is possible to simplify: Django Vanilla Views

Models and Forms Are Ignorant of the Current Request

Models and forms are not "aware" of the current HTTP request. This means, for example, that auditing a record's history cannot be easily built into a model.

Architecturally, views are meant to handle this sort of thing, but apart from the Admin, this isn't done for you.

Migration Pitfalls

The addition of database migrations is of huge benefit to Django developers. A third party tool or manually managing SQL changes is no longer required.

There are some pitfalls, though.

Perhaps I'm overly paranoid, but I consider migrations to be serious business. And I've had them go wrong. For live applications. During midnight deployments.

So I try to code my apps in such a way that migrations are less necessary.

Field Choices Harmful

Field choices are included in migrations, which means they cannot change without also generating a migration. I once had a project break in the live environment because a field was depending on timezone choices from an older version pytz than the one installed in development.

Yes, this could have been solved by making sure packages were up to date in development, and that's now part of our process. However, it's worth investigating other options, including:

  1. Use another model as a lookup instead of defining a choice list.
  2. Move choices to a form.
  3. If you're sure the choices won't change (much), then it's probably okay to include them as part of the model. But never build in choices from a third-party app. This last restriction is problematic, but so is fixing a live system at 3 in the morning.

Default Values Painful

Like field choices above, default values are included in migrations. For this reason, a default value cannot be included, for example, in settings.py.

Additionally, while default values may be a callable, there is no access to the request or user at the model-level, which is perhaps the most common scenario in responding to a default.

Control over defaults is perhaps more pernicious than field choices, because they will almost certainly impact logic somewhere downstream. (Field choices may have a similar impact, but are more likely to fail early, e.g. during a migration attempt.)

The best option appears to be: Provide default values from views to forms rather than including them as part of the model. If a default is truly a default across all possible use cases and conditions, then it's fine to include in the model.

You may think this just applies to re-usable apps, but consider that, even in a custom or project-specific app, a default value may need to change dynamically. If so, look at coding the default away from the model from the beginning.

Labels and Help Text Inconsequential

Labels and help text are also included in migrations, which I personally find super annoying. This includes verbose names for models as well. Perhaps I'm missing something, but I can't think of any reason why changes to text should be picked up by the migration system.

Changes to labels and help text should be inconsequential.

The only option here — which I don't like — is to omit verbose_name and help_text from field definitions, and instead including label and help_text (respectively) in form definitions.

However, since it is possible to override these attributes in a form, assigning them in the model and living with migrations (when, for example, one fails to punctuate or capitalize some text) is the way to go.

The Admin is Not for Everyone

The Admin is not for end-users. This isn't a flaw, but a statement of fact. There have been many attempts to control the look and features of the admin, but in the end, it is still composed of a restricted set of functionality.

Is it possible to provide the Admin to end-users? Yes. We have a 10 year old project with a customized admin interface that the client is perfectly happy with.

Most of the time, though, the Admin is just not for users, and forcing it just leads to pain and suffering down the road.

User Account Stuff

Modern web applications typically come with things like password resets, user profiles, and so on.

Django does provide some forms and views for implementing logins and password recovery, and it used to ship with basic support for user profiles..

This required extra work on the developer's part, which in some cases is okay, because:

  1. Handling accounts and logins may be specific to a project.
  2. User profiles often require fields that are also specific to project.

Still it would be nice if some of this were built in to Django in a more concrete fashion.

What Is SuperDjango?

Django says it's a "batteries included" framework — and it certainly is. There is so much horse power waiting to be used after running django-admin startproject.

But wouldn't it be great if there were some additional apps or libraries that provided even "more batteries"? A unified system that provided the same conventions as the Admin and built along a familiar architecture?

That is SuperDjango.

  1. Middleware, context processors, and utilities that are often needed for a project.
  2. A collection of abstract models and supporting fields representing commonly used behaviors, as well as concrete "contrib" apps that implement familiar web-application patterns.
  3. A user interface system similar to the Admin that is highly customizable and extensible, but ships with defaults that provide for rapid prototyping.


Posted in Philosophy by Shawn Davis, January 15, 2020