[z3-five] Traversal with methods other than GET or POST
Paul Winkler
pw_lists at slinkp.com
Fri Sep 21 23:47:13 CEST 2007
Hi folks,
I've been playing with a toy REST service implemented in Five (tried
it with both zope 2.9 and 2.10) and wanted to warn people of an
annoying stumbling block that I wasted a lot of time on. I don't blog
so this seemed like a good place to post it.
When I'm prototyping an app that doesn't use the ZODB for persistence,
I have a habit of hanging views directly on OFS.Application; sometimes
this is even good enough for my finished product. For example:
<browser:view
class=".views.MyViewClass"
for="OFS.interfaces.IApplication"
name="myview"
permission="zope2.View"
layer="default"
/>
Now we can visit http://example:8080/myview.
It's possible to handle traversal from there by implementing a custom
traverser (ITraverser for zope 2.9, IPublishTraverse for 2.10) and
registering it on the above view, like so:
<view
for=".interfaces.IMyViewInterface"
type="zope.publisher.interfaces.browser.IBrowserRequest"
factory=".views.MyTraversalClass"
provides="zope.publisher.interfaces.browser.IBrowserPublisher"
permission="zope2.View"
/>
Now we can traverse http://example:8080/myview/foo/bar/...
But here's the gotcha. None of this works if the request method is not
GET or POST.
Why? Because we're starting traversal at OFS.Application.Application.
Its __bobo_traverse__() does something odd: if no attribute with the
correct name is found, it checks the HTTP verb. If the verb is
anything other than 'GET' or 'POST', it returns an instance of
webdav.NullResource.NullResource.
Presumably this makes sense for Zope 2's semi-automagic support of
WebDAV. But it's not nice in the Five world, where Five views only
get looked up if __bobo_traverse__() raises an error or is not
defined.
The code path that ZPublisher follows is different between 2.9 and
2.10, but the result in both is the same: once the NullResource gets
into the picture, traversal is done and your Five views are never
looked up.
So if you're working on a REST application and, like me, stupidly
trying to beging traversal at the Zope app root, you're out of luck
:-)
Possible solution: Create a simple custom persistent object which
implements just enough traversal magic to get Zope to use your custom
traversal class:
class StupidTraversalRoot(SimpleItem):
...
def __bobo_traverse__(self, request, name):
raise AttributeError
(Or I suppose it could inherit from Persistent but not Traversable,
either way should work.)
Then create an instance of this in Zope and use it as the place to
hang our views. This should prevent the unwanted NullResource object
from mucking things up.
By the way, here's the basic idea of how I handle HTTP verbs.
I have a base class for my views that consists of little more than
this:
def __call__(self):
response = self.request.response
verb = self.request.get('REQUEST_METHOD')
method = getattr(self, verb, None)
if method == None:
# Not implemented.
response.setStatus(405)
return "helpful error message"
return method()
Verbs can then be implemented by subclasses, like so:
def PUT(self):
"""create a child resource"""
...
An alternative approach can be seen in Schooltool, where views are
associated with verbs via zcml.
See eg.
http://source.schooltool.org/viewcvs/trunk/schooltool/src/schooltool/app/rest/configure.zcml?rev=6558&view=markup
--
Paul Winkler
http://www.slinkp.com
More information about the z3-five
mailing list