
I’ve been talking about doing this for some time, so I decided to go ahead and try it to see how it would go. Ra, Whit, Josh: I’m hoping to get feedback from you guys. Josh, I know that you spent some time on how the current context menu gets generated. Let me know if you think this is a step forward, or a step backwards.
The results are here: http://trac.openplans.org/openplans/log/opencore/branches/viewlet_menuitems
The second commit in the branch uses more mixins to attempt to cut down on code duplication, even though I think there’s actually more code there. I struggled to find a balance between removing duplication and over-engineering the thing.
Anyway, here are my thoughts about it:
It turns out that viewlets simplified things a lot less than I thought they would. The reason why is that I was unable to take advantage of the “for” attribute as much as I hoped, which is where I thought most of the savings would have come from. Here’s the problem:
We need to know if we’re in the “context” of a project, member area, searching, or no context (portal). This precludes me from registering viewlets for an IProject, because what I really want is whether the context object, or any of its parents, provides a particular interface. I essentially want the interfaces that are provided to be “acquired”. Ideally, there would be a zope way to specify this. I ended up having to do all these parent context checks myself, instead of having zope take care of them.
This is not the first time this problem has come up, and I don’t think it will be the last. When this came up last was when we wanted to only give xinha to experimental projects. So, we marked projects as IAmExperimental, and figured this would take care of all our problems. However, we are viewing pages within a project, and cannot lean on zope’s machinery to use a different registration based on the project marking. So, we added the experimental check in code. The view class itself checked if its parent provided IAmExperimental. It was simple to get around this one case, but as we roll out more experimental features, this will become more difficult to manage.
Something I considered to get around this, is simply marking all objects with an interface as they are being created, with their particular context. Simply register an event handler to listen to when an object gets added to a container, and mark it accordingly. Then, we really can leverage the “for” part of the configuration. The code becomes much, much simpler, with almost all of it being controlled through configuration. For example, if a new page is created in a project, an event gets fired. Somebody (hopefully the project, although I haven’t look at these kinds of events so I’m not sure offhand) handles this event by marking the object with something like: IHaveAProjectDaddy. Or, more realistically, something like IProjectSpace.(Those of you who have paired with me know I like to come up clever little sentences for interface names. Clever imho of course 
We could also do the same kind of thing with the experimental projects. We’d fire the same event, and have a handler that would first check if the project is experimental, and if so, mark the new object with the experimental interface. Then we gain all of zope’s configuration power.
Somehow though, this seems like a big change. Even though it won’t be much code, it feels big. Like I’m using the wrong tool for the job. And, it’s not going to help in the case of our featurelets that don’t live in zope. These guys don’t have any zope objects to mark, so we would still have to do the context dance in this case.
As a side note, I only ran into one zope hiccup when moving to viewlets. If it’s not already common knowledge, the viewlets for a particular manager are in no particular order. All is not lost however. There is a viewlet manager sort method that you can override to impose your own ordering. I used the strategy Philipp does in the zope 3 book: sort them by name, and prefix the viewlet names with an integer. I ran into problems specifying a custom viewlet manager class. I tried using a completely empty subclass of the base viewlet manager zope uses, and still had the problem. It was a security error. Well, since zope was fighting dirty, I also had a trick up my sleeve. Since the default sort behavior sorts the viewlet objects themselves, I just added a custom __cmp__ to a base viewlet class that would compare their names. This turned out to work just fine. But, if anybody knows how to get this working with the viewlet manager (since it makes more sense to put this logic there), please let me know. Maybe there’s some five declarations I need to sneak in somewhere.
Besides all these downsides that I mentioned above, things went really smoothly. Surprisingly so in fact. And even though I didn’t gain as much as I had hoped for with viewlets, imho there’s still a gain. I got some bang out of the permission configuration, and I was able to use “for” configuration to filter out the menu items for the searches, since those don’t have child objects to deal with. This also moves us more in the direction of having component logic live with the component. The menu items for featurelets now live with the featurelet. You don’t need a part of the application to know about everything else.
What it comes down to though really, is whether the viewlet approach is easier to maintain. I think it is, and like it because it’s following a zope pattern. But, I’m biased, so I’ll let you guys be the judges of that.