Wow. I touched a live wire today in IRC without meaning to. I lamented over the inconsistency that Django has between it's generic views. It has to do with what the Python community calls magic and goes something like this.
Django ships with these great generic views (if you're coming from another web framework, consider their views as controllers). Working with any framework long enough, you know that there's only so much to do. Handle posts to update, grab a list of objects or a single object, pass them off to the presentation layer, hand it off to designers and call it a day. They work great.
There's a series of generic views for handling object updating and deleting. They're the
django.views.generic.create_update views. Of course, you don't want just anybody updating your content, so you probably want to be able to require that users are logged in. Easy enough, add the
login_required variable to the dictionary of options you specify for the view and call it a day. One line of code in the dictionary that both your add and edit rely on and you're done.
Until you get to the list of objects. It's located over in
django.views.generic.list_detail.object_list and it complains rabidly when you include a
login_required value. Turns out, that generic view doesn't support what seems like an obvious option to modify it's behavior. After all, you might not want lists of your objects just floating around out there. It's an inconsistency between the generic views, and annoyed me.
I mentioned something to that effect on IRC and ended up in a lengthy discussion about it. Turns out there is an acknowledged inconsistency, but the problem is that the
create_update views take
login_required rather than the
list_detail is missing it. There's a perfectly acceptable way to fix it: decorators.
There's a problem with this, though. Decorators have multiple syntaxes. The convenient way to do them is like this:
@login_required def some_view(request): ...
In a generic view where you're referencing a function that's already declared, that doesn't work. Instead, you have to declare it like this:
Still doesn't seem horrible, but here's the catch. Everywhere else you work with URL patterns in Django, the tendency is to refer to the view as a string. Note that the parameter to
login_required isn't a string. That means you have to explicitly import the views you want to use, otherwise Python has no way of knowing where that function exists.
That personally offended my sense of code aesthetics. I mentioned it and said that I would just allow
login_required to be there. It's ubiquitous enough that it seems like it should be available all of the time, for every view that accepts parameters.
That was quickly shown to have errors as there were two ways to handle that:
- Every view everyone will ever write in every Django application everywhere must take a "login_required" argument and include the appropriate code to handle it.
- Anybody who wants the behavior can do "login_required(some_function)"
That's the two obvious ones. I added the third.
- or, the framework looks for the presence of login_required and decorates the function appropriately
And thus I touched the live wire and magic missiles started flying. If you're reading this post via Planet PHP and you've made it this far, you're undoubtedly wondering what all this Python and Django talk has to do with PHP. Well, it has to do with magic and Java.
See in PHP circles, the quickest way to end a discussion is to say that some piece of code acts or looks too much like Java. Try to code in object-oriented PHP and risk being dismissed with "PHP isn't Java". Once that's thrown into the conversation, you might as well call it quits. Java is tainted goods in PHP community. The culture is such that it's shear mention is enough to send shivers down the spine of aspiring PHP'ers.
That shiver inducing phrase in Python appears to be "magic." Be explicit, that's their motto. Doing anything without explicitly asking for it is considered "magic" and should be shunned like a leper with the plague. To be flip, you could change it around to "I'm fine, I can do it myself, leave me alone."
Me personally, I'm lazy. I like having hooks deep into a system where I can apply things at will. I think things like the user authentication system should be able to add framework-wide hooks so when a
login_required is encountered in the parameters it can auto-magically make sure that the user is authenticated. I love it when a framework takes work away from me, that's why I use them in the first place.
I've always been slightly amused and distressed at the PHP community's need to shun Java. As an outsider to the other communities, I've often wondered what set them off. Now I've found at least one in Python and I'll keep my eyes open as I start my foray into Ruby to see what sets them off. Guess this just does show how similar all us geeks really are. Different words, same reactions.
Random question to my readers (all five of you): do you do Ruby? If so, what's the sacred cows there?