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:
login_required(some_view)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?
18 comments
You've played with Symfony, right? (I don't remember if we had this discussion.) You have to love sfGuard and its ability to set "is_secure: on" in the config for your app/module and it does exactly as you ask: auth for any type of access. I, too, appreciate the magic that goes on behind the scenes in a framework like Symfony, that can be tweaked under the hood to take care of the autonomic parts of my app.
This is a fundamentally different issue than Java in the PHP community. I don't know what the Java hatred is based off of, but it's hatred of a single language. The "magic" thing is hatred of a concept, which can be present (and bad) in any language.
You say:
"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."
But what does this mean? Does this mean if I accept a parameter of that name, the framework ought to decide, without my consent or yours, that authentication will always be required for this view? What if I want that requirement but you don't? How do you tell the framework, "I want you to make assumptions about anything that looks like this, _except_ for that one and that one and that one"? What if I'm using some other authentication system -- with its own requirements -- and your "helpful" automatic rewriting of my code decides to stick in its own bits anyway?
To compensate for these types of cases, any such system would end up looking like some sort of annotation or even -- gasp! -- decoration of the function to indicate the desired behavior. Which is, you'll note, what's already there in Django.
Which gets us back to having a simple, explicit way to write what you mean and, more importantly, to why all the Python people you know feel the way they feel about this sort of thing. We've learned, the hard way, that the "convenience" of systems that "do our work for us", often, turns out to be a blasted _in_convenience that creates even more work for us.
Unwillingness to follow conventions a framework operates under is what causes people to hate frameworks, in my opinion.
@John: I've only briefly looked at Symfony a long time ago. At that time there where too many hoops to jump through to get it running. Just didn't fit right. Yeah, I know. I'm contradicting myself. I want a framework that does a lot for me, but not too much.
@Jake: No, its the same thing. PHP developers don't hate Java as a language, they hate it as a concept. They consider it bloated and heavyweight. Mentioning that code looks like Java isn't saying it's like the language; it's saying that it follows OO too close. Why use 50 classes to handle 50 different concerns when you can smash 'em all into 5 classes each responsible for 10 different functions.
@James: No it doesn't mean the framework should explicitly do things for me, unless I tell it to. This is a classic case of dependency injection. I should be able to inject behavior into the system easily. Ideally there'd be a mechanism for observing the views and adding behavior to them. It's not that I don't like the decorator approach, I think it's just more verbose than need-be.
@Chris: Agreed.
def limited_object_list(*args, **kwargs):
return object_list(*args, **kwargs)
It's not that simple. Most (well designed) websites aren't a matter of either being logged in or not. What happens when you want to have a logged in version of a view and a non-logged in version?
I mean, I'm fairly particular about my code myself, but is it REALLY that ugly to say login_required(some_function) ?
The thing is, I'd rather do more typing and understand what's happening than save some typing and not know what my code is doing.
"Ruby is so slow." / "Well it's fast enough for me, so shut up!"
some_view = login_required(some_view)
not:
login_required(some_view)
?
I'm not sure if I'm missing something.
"Any sufficiently advanced technology is indistinguishable from magic."
but it's always making a good job bringing the cpu load on my box up to 100% for a couple of minutes whenever i open or close a project or sometimes just save a file, making the fans cause a lot of noise and co-workers thinking that my box is about to take off or explode.
so if that's the greatness of java...
Throw away Zend and forget about it. It won't be any faster/lighter for next couple of years considerng the eclipse development cycle which is "once a year".
Use netbeans for php and python. You will like it.
I get agitated at some communities' express hatred towards magic. I love magic, so long as the magical behavior is easily overridden should I not need it. I'm lazy, and magic makes me code less. Something about writing the same code 500 times (in a large app) turns me off. If I can make some functionality magical enough that I don't have to worry about it anymore - you best believe, it's getting done!
Thanks for the post. It was fun reading.
