Monday, February 27, 2006

Python: Django-palooza

I think the Django guys have written some really nice software. It's good stuff. No, it won't cure cancer; it's not a CMS; it's not a shopping cart; but it does what it does well.

In fact, I must admit that I felt really at home using Django. Django + WSGI reminds me a lot of how Aquarium works. In many subtle places, the designs are very similar. There are some things I feel Aquarium does better, but I can readily admit there are some things that Django does better. Despite the fact that I too am a perfectionist, I think Django may be more polished--apparently, one perfectionist just can't code as fast as two perfectionists ;) Actually, I wish it was closer to meeting my needs (stackless Python, coroutines, a custom data store instead of an RDBMS, etc.), but I can appreciate it for what it is.

Since Django can't possibly meet everyone's needs--for instance, it doesn't make a good replacement for Linux or Vim (yes, that's a joke)--let me state some of the things that bothered me most about Django while I was using it to write a small application like this one.
  1. In my application, end users have a form that allows them to create new messages. I created a new message that had <script>alert('hi!')</script> for its subject. My code correctly escaped things. However, in the admin interface, I got an alert "hi" when I went to update the message. That means that you cannot safely use the admin interface to look at end user generated records. Otherwise, end users might inject JavaScript that causes the admin user's browser to "do bad things."

  2. In the templating language, if you use a variable that doesn't exist, it will be silently replaced by "". This is like Perl, and I really dislike it. I'd much rather have an appfault so that I can fix the problem.

  3. Certain filters take arguments. A filter argument looks like this: {{ bio|truncatewords:"30" }}. I personally don't like this syntax, but I understand that that's just a matter of taste.

  4. You can't treat templates like functions. "include" is not a valid replacement for functions because you can't pass arguments. That means that you can't call it twice passing different arguments (don't even think about trying recursion). I think that functions are the most basic tool for applying DRY (don't repeat yourself), and I think that DRY applies to HTML too! Redundant code is bad, period. Even if the guy getting paid to update it is getting paid less than a programmer, it still takes a long time to make changes to redundant code, and it's an error-prone process.

  5. Nor are custom tags a good replacement for functions. First of all, template authors don't have access to write them. Secondly, consider the following custom tag call: {{ create_form_field email "Please enter your email:" }} Did you know that it's up to the coder to parse that? The custom tag infrastructure can't even break that call into two arguments, let alone take care of unquoting the string. It doesn't even resolve the variables within the caller's context--you have to do that explicitly. Consider an exmaple straight out of the Django docs:
    <form method="post" action=".">
    <p>
    <label for="id_name">Name:</label> {{ form.name }}
    {% if form.name.errors %}*** {{ form.name.errors|join:", " }}{% endif %}
    </p>
    <p>
    <label for="id_address">Address:</label> {{ form.address }}
    {% if form.address.errors %}*** {{ form.address.errors|join:", " }}{% endif %}
    </p>
    <p>
    <label for="id_city">City:</label> {{ form.city }}
    {% if form.city.errors %}*** {{ form.city.errors|join:", " }}{% endif %}
    </p>
    <p>
    <label for="id_state">State:</label> {{ form.state }}
    {% if form.state.errors %}*** {{ form.state.errors|join:", " }}{% endif %}
    </p>
    <p>
    <label for="id_zip_code">Zip:</label> {{ form.zip_code }}
    {% if form.zip_code.errors %}*** {{ form.zip_code.errors|join:", " }}{% endif %}
    </p>
    <p>
    <label for="id_place_type">Place type:</label> {{ form.place_type }}
    {% if form.place_type.errors %}*** {{ form.place_type.errors|join:", " }}{% endif %}
    </p>
    <input type="submit" />
    </form>
    Perhaps you'll disagree with me, but I think that code is verbose and redundant. I think the template author should have a way of factoring out the common bits (what a row looks like), but neither includes nor custom tags are suitable replacements. I'm going to make due with a custom tag, but I'm not happy about it.

  6. I like the urls.py stuff, but when you generate URLs, the structure of those URLs is hardcoded. That is, urls.py is used to parse URLs, but not to create them. If I suddenly want to change how my URLs work, I can't just go to urls.py and change it. I also have to change all the places where the URLs are generated.

  7. Blocks are not suitable replacements for functions either. For instance, you can't just call a block. You can only override a parent template's block. My parent template had a "title" block that the child templates were overriding. I wanted to call the "title" block somewhere else in the child template, but I couldn't. I felt like my hands were chained together.

  8. The builtin media server is only meant for development. I can respect that. It's not supposed to be efficient. However, it doesn't understand the IfModifiedSince header. Hence, media is re-downloaded on every page hit. (By the way, I'd be happy to donate this from Aquarium, but I fear the authors aren't interested in my contributions.)

  9. I see form = formfields.FormWrapper(manipulator, new_data, errors). This is similar to how FormUtil works in Aquarium, however, it would be better if the new_data argument could be a list of dicts, i.e. "Use the user's previous data if present, otherwise use the current value of the data from the database, otherwise use..." Using a list of dicts is a nice bit of flexibility.

  10. Why are the Django guys so rude to other programmers? (You'll note that this blog contains compliments as well as critiques.) Here are some reasons it's bad to be rude to other programmers:

    • Those other programmers are human beings. They have feelings too!

    • I can guarantee Django will one day be obsolete, and younger programmers will say rude things about the authors. It's best if we all just treat each other with respect all the time.

    • Being rude to other programmers turns valuable bug and patch submitters into competitors. That makes it a double no-no.

  11. I wonder why they picked Dojo Toolkit instead of MochiKit. Afterall, the MochiKit guy is definitely a fellow perfectionist, and the Dojo guy most definitely isn't (even though he's a really nice guy). Personally, despite the fact that I'm a perfectionist, I like Dojo because it's very complete (it has stuff that I need that MochiKit doesn't). I can only hope they picked Dojo for this reason. It'd be awful if they picked it simply because MochiKit is used by TurboGears.

  12. If the Django guys are perfectionists, why don't they follow Guido's style guide more closely? That kind of stuff will drive obsessive compulsive perfectionists like me crazy!

  13. Did I mention that it's nearly impossible to apply DRY to templates in Django?
The following are some small points that are fixed in Aquarium, but not in Django. Again, I'd be happy to offer code.
  1. The login cookie isn't being set correctly, because the domain of the cookie sent out by Django doesn't match the domain in your browser.
    Aquarium automatically respects the X_FORWARDED_HOST header.

  2. You have to restart the Web server everytime you make a change under mod_python.

  3. The broken pipe exceptions are irritating. They're easy enough to handle more gracefully.
So anyway, Django guys, if you're reading this, remember that I continue to have a lot of respect for what you've done. In general, it's good stuff.

11 comments:

Anonymous said...

My biggest gripe with Django is the lack of end-user authentication and user management. This is something that was missing from rails for a long-time too, but the community (Bruce Parens, actually) ended up supplying. Hopefully, someone will step up for Django too. (Maybe I'll clean up my copy).

That's some of where I see philosophical differences between rails and django. Rails seems more 'web-app' oriented, while django seems more 'web-site' oriented.

You see this in quite a few places. Rails has end-user management (self signup, password changing, salted logins), while django has the unbelievably cool admin site.

Rails also has some things like acts_as_tagging, and open id, and some text searching. I wouldn't be surprised to see some built-in shopping cart management stuff.

All of that stuff is do-able in django, that's not my point. Some of this is because django a smaller community than rails does right now.

But, as the rails guys say, philosophy matters. And the rails philosophy seems more geared to web-apps than content management. (which toolkit has built-in hooks for comments and rss? django).

That's one of the things that concerns me about django. I want to tie my fortunes to web-apps, not content. The distinction probably is slim, but I think notable.

Too bad I really dislike ruby. After that whole rant, I still like django better!

Kieran Holland said...

Hey Shannon,

I have been using Django since soon after the initial release and the core Django team have always seemed incredibly helpful to me. Admittedly there are a few members of the community who occasionally voice some rather strong opinions, but I don't think you should hold it against the core team who have perhaps been too run off their feet lately with development, job changes and PyCon etc to always be able to reply in person.

Anyway, I hope you haven't been put off because you have made some really good points here - a few of which are already being addressed - and I am sure that the Django team would love to integrate at least some of the ideas you are offering.

Anonymous said...

Hrm, my dealings with the Django guys have been only polite; and far from rude.

Anonymous said...

I know there are some guys on the django-dev list that don't come across that well, but the vast majority are extremely helpful.

Adrian Holovaty said...

Hey JJ,

Thanks very much for this feedback.

I apologize on behalf of the Django team for any rudeness you encountered in the community. As a free Internet forum, it inevitably gets terse at times, but overall I've been happy with the quality of discussion and civility on the Django mailing lists.

We're *definitely* interested in any and all contributions from you/Aquarium. You mentioned at least three things that I'd commit in a second: The IfModifiedSince thing, X_FORWARDED_HOST support and a solution to the broken-pipes issue. If you're still willing to help, please do!

To the first anonymous commenter: We've got some cool authentication stuff cooking during the sprints that are currently happening. See more info, and get involved if you want to shape how the system gets designed.

Adrian
(Django dev)

Shannon -jj Behrens said...

Adrian, it takes a mature programmer to accept criticism with a "thank you" and an apology ;) Keep up the good work! Django's success is well deserved!

Anonymous said...

In fact, I'd say one of the biggest reasons behind Django's success is the very positive-spirited attitude of the core developers, even in the face of harsh criticism.

Some of the late-comers to the more-core-y group are a bit pushy at times, I've noticed, but they're not part of the original team, and perhaps haven't quite caught the spirit.

pbx said...

I agree that the civil and open attitude exemplified by Adrian's post has been a huge part of what's making Django a success.

It's especially impressive when you realize that since long before the day this was open-sourced it has been working, production code in Lawrence, and over and over Adrian and crew have unflinchingly incorporated suggestions from the community -- graciously and on merit, IMO.

There are a lot of good points above. I was nodding my head through many (though not all) of them until I got to the one about rudeness (I noticed it's one of the only points without examples!).

By the way, today I was listening to a recorded session from last year's PyCon, and in the questions period at the end of Ian Bicking's WSGI session one brave web framework author volunteered to yield his planned time slot in order to allow further discussion of the integration issues, because he was "tired of doing it alone." I thought that was pretty cool. I think the framework was called Aquarium.

Group hug!

Shannon -jj Behrens said...

Quoted:
one brave web framework author volunteered to yield his planned time slot in order to allow further discussion of the integration issues, because he was "tired of doing it alone."

hahahaha Yep, that was me ;) In fact, when I yielded my time slot, it gave me a chance to meet the Django guys who weren't famous at the time. ;)

Ah, it's such a small world. Who knows what I missed at this year's PyCon?

Ian Bicking said...

Paste's paste.urlparser.StaticURLParser is a static file serving WSGI app with support for If-Modified-Since (and a couple other things, I think). If Django made it easy to embed or compose WSGI apps, then it could fairly easily make use of this code. AFAIK, it's efficient enough for most uses. The integration effort would, IMHO, be worth more than just the resulting static file serving, since it applies to embedding/composing any WSGI app.

(Incidentally, though you can use Paste Deploy config files to compose a static directory with a dynamic app, I usually hard code the StaticURLParser directly into my app -- it's not something I think should be exposed to configuration, since the whole point is to have a reliable fallback in the absence of configuration)

Separate media servers don't work at all for some things anyway -- e.g., media behind Python-based authentication.

Anonymous said...

It's a symptom of the whole python community. Sound like it has spilled over into the django list a little (in fact, looking at some of the names, it has). See for example http://www.oreillynet.com/ruby/blog/2006/01/bambi_meets_godzilla.html

But don't blame django or the django developers, please. I'm not using the tool, but they seem to be doing a great job so far.

I do though like the ruby community better than the python community. Much more civilized (heh heh).