Thursday, August 31, 2006

My new favourite place to put a pdb

I'm not sure if this is a good thing. In fact, I'm pretty certain it's a bad thing. But sometimes, using Five views and acqusition hurts. The main problem is that Zope 2 (still) depends on acqusition for security, and our code tends to rely on implicit acquistion (acquiring attributes, tools, templates from portal_skins...) to stitch our application together. Zope 3, on the other hand, doesn't have this kind of implicit acquistion-is-everywhere magic. Which is probably a good thing, but it does make for some awkward marriage of the two in Five sometimes.

The basic pattern is, you mix Acquistion.Explicit into your view classes and other objects (or if you have to, Acquistion.Implicit, or sometimes OFS.SimpleItem.SimpleItem... I'm still trying to work out the hard and fast rules, and I'll tell you when I can). You then sometimes have to do some aq-wrapping as well. obj.__of__(aq_inner(self.context)) seems to be a favourite. And quite often you have to do some aq-unwrapping - aq_base() and aq_inner() are your friends.

But it's often quite hard to know where the acquistion chains are coming from, and what they are at various stages of traversal. Today, for example, I discovered that if you have a Zope 3 style container (something that provides IItemMapping) traversal will be handled by zope.app.container.traversal.ItemTraverser. This looks like this:

def publishTraverse(self, request, name):
"""See zope.publisher.interfaces.IPublishTraverse"""
try:
return self.context[name]
except KeyError:
view = queryMultiAdapter((self.context, request), name=name)
if view is not None:
return view

raise NotFound(self.context, name, request)


Great. It looks for the given name that you're traversing to in the container, and returns it if it can, otherwise it tries to look it up as a view, which is what you'd expect.

The problem, though, is that this view never gets acqusition-wrapped. Thus, the acquisition chain is broken at the view, and it has no appropriate security context, for example. This was giving me incredibly non-debuggable security errors.

After some helpful suggestions from Zope 3 + Five guru Philipp von Weitershausen, I decided to put a pdb break point in ZPublisher/BaseRequest.py, specifically the traverseName() function. (This is Zope 2.10, btw, it's different in earlier versions). This gets passed the name ('name') that is being traversed to, and the object ('ob') that is "as far as I've gotten so far". Thus, let's say you're traversing to /plone-site/container/@@myview - it will start out with ob as the Application and name = 'plone-site', then ob as the Plone site and name='container' and so on.

Of course, this is a bit annoying, because if it does render a page, it will traverse to tons of resources like images and stylesheets. Therefore, it may be better to put a conditional break point in there, by doing something like:

if 'myview' in name:
import pdb; pdb.set_trace()


It was here I discovered that the view being looked up was not automatically acquistion-wrapped. My custom traverer for my own type looks like this - and it works!

class PortletAssignmentMappingTraverser(ItemTraverser):
"""A traverser for portlet assignment mappings, that is acqusition-aware
"""
implements(IBrowserPublisher)
adapts(IPortletAssignmentMapping, IBrowserRequest)

def publishTraverse(self, request, name):
ob = ItemTraverser.publishTraverse(self, request, name)
return ob.__of__(self.context)

Now - Philipp has been waving at his plan for world domination - make acquisition less important in cases like this. Dude - we can't wait! :)

In the meantime, I hope we can work out the common patterns and document them, at least.

No comments: