Traversing subpaths in views¶
Author: | Peter Bengtsson |
---|
Purpose¶
A URL points to a view of an object, but if the URL contains more than that, we’ll show you how to use that in the view. What we’re essentially doing is using the URL and its sub-path be positional arguments to the view. In this how-to, we’ll use an example where you want a URL that looks like this: http://localhost/app/showcalendar/2008/08/01 rather than this: http://localhost/app/showcalendar?year=2008&month=08&day=01
Prerequisities¶
A Grok app with a view.
Step by step¶
The trick is to add a method called publishTraverse(self, request, name)
to the view class. In this view, not only do you pick up what the extra subpath is but you also modify it right there so that Grok doesn’t try to find the remaining things in the URL.
So, suppose you have a view called ShowCalendar
that displays a nice calendar based on a date (or todays date if nothing else specified):
class ShowCalendar(grok.View):
def update(self):
form = self.request.form
if 'year' in form and 'month' in form and 'day' in form:
self.date = datetime(int(form.get('year')),
int(form.get('month')),
int(form.get('day')))
else:
self.date = datetime.now()
def render(self):
return self.date.strftime('%d %B %y')
A quick doctest explains how it works in action:
>>> from zope.testbrowser.testing import Browser
>>> browser = Browser()
>>> browser.open('http://localhost/app/showcalendar')
>>> from time import strftime
>>> strftime('%d %B %y') == browser.contents
True
>>> browser.open('http://localhost/app/showcalendar?year=2007&month=7&day=2')
>>> browser.contents
'02 July 2007'
So far so good. Now we decide we want to extend this to work based on a URL and not CGI parameters like above. The trick is to add the publishTraverse()
method. Do note that the name
argument is the first part of the subpath from the left and then request.getTraversalStack()
is the rest in reversed order. Here’s the extended view:
class ShowCalendar(grok.View):
def publishTraverse(self, request, name):
self.traverse_subpath = request.getTraversalStack() + [name]
request.setTraversalStack([])
return self
def update(self):
form = self.request.form
subpath = getattr(self, 'traverse_subpath', [])
if subpath and len(subpath) == 3:
self.date = datetime(int(subpath[2]), int(subpath[1]), int(subpath[0]))
elif 'year' in form and 'month' in form and 'day' in form:
self.date = datetime(int(form.get('year')),
int(form.get('month')),
int(form.get('day')))
else:
self.date = datetime.now()
def render(self):
return self.date.strftime('%d %B %y')
An extension of the doctest above explains how it works:
>>> browser.open('http://localhost/app/showcalendar/2009/08/01')
>>> browser.contents
'01 August 2009'
This demonstrates three important ideas:
- The
request.getTraversalStack()
method- The
request.setTraversalStack()
method and notice how easy it is to use- In the
update()
method we usegetattr(self, 'traverse_subpath', [])
rather thanself.subpath
because it might not exist if the view is published without an explicit subpath on the URL.
Further information¶
The example above is rather simple and there might be more checks you want to add and for example raise NotFound
exception which is done like this for example:
from zope.publisher.interfaces import NotFound
class ShowCalendar(grok.View):
def publishTraverse(self, request, name):
if name != u'2008':
# poor example but gets the job done
raise NotFound(self.context, name, request)
...
Another important thing to consider is that the above will not work with a default view which are views that are called Index
unless the word index
is in the URL itself. The reason for this is that the Index
isn’t necessarily published unless it appears in the URL.