How do I grab variables from arbitrary URLs?
Example using IPublishTraverse? customizations
There was a discussion on using this technique at http://www.nabble.com/Specialized-URL-traversal..-Best-way--t824862.html on the Zope 3 users mailing list:
from zope.interface import implements
from zope.publisher.interfaces import IPublishTraverse
from zope.app.publisher.browser import BrowserView
from zope.app import zapi
class UrlDispatcher(BrowserView):
implements(IPublishTraverse)
def __init__(self, context, request):
self.context = context
self.request = request
self.traverse_subpath = []
def publishTraverse(self, request, name):
self.traverse_subpath.append(name)
return self
def __call__(self):
"""Call another view"""
# Could also match via regexs like django
if 'view1' in self.traverse_subpath:
return zapi.getMultiAdapter((self.context, self.request), name="view1")()
elif self.traverse_subpath[-1] == 'someValue':
# Random untested example:
self.request['subpaths'] = self.traverse_subpath[:-1]
return zapi.getMultiAdapter((self.context, self.request), name="view2")()
class TheFirstView(BrowserView):
def __init__(self, context, request ):
self.context = context
self.request = request
class TheSecondView(BrowserView):
def __init__(self, context, request ):
self.context = context
self.request = request
self.subpaths = self.request['subpaths']
# may want to look up DB based on contents of subpaths
###
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="travtest"
>
<browser:page
for="*"
name="root"
class=".travtest.UrlDispatcher"
permission="zope.Public"
/>
<browser:page
for="*"
name="view1"
class=".travtest.TheFirstView"
template="./first.html"
permission="zope.Public"
/>
<browser:page
for="*"
name="view2"
class=".travtest.TheSecondView"
template="./second.html"
permission="zope.Public"
/>
</configure>
###
The example uses __init__ to set up an empty list publishTraverse to add each url portion to the list and __call__ to dispatch to the appropriate browser view.
URL examples using Grok
The Grokstar application grabs variables from URLs?, see traverse.py and calendary.py in http://svn.zope.org/Grokstar/trunk/src/grokstar/ for an example. Grokstar breaks a URL such as /grokstar/2007/02/11 into a series of Year, Month and Day models that have corresponding views for each model using a custom Traverser class that sub-classes from a grok.Traverser class, which in turn implements the zope.publisher.interfaces.browser.IBrowserPublisher? interface.
Another possible approach to URL traversal using Grok is to provide a dateindex view for your collection, and then fiddle with the traversal stack, stuffing arguments that are embedded in the traversal URL into the request.form dictionary. For example, a human-friendly URL such as /myblogentries/2007/01/20 can be thought of as shorthand for a more traditional Zope 3 URL such as /myblogentries/@@dateindex?year:int=2007&month=:int=01&day:int=20.
A possible date range traverser based on this assumption might look like this:
class YearBasedDateTraverser(grok.Traverser):
grok.context(Blog)
def traverse(self, name):
try:
self.request.form['year'] = int(name)
stack = self.request.getTraversalStack()
if stack:
month = stack.pop()
self.request.form['month'] = int(month)
self.request._traversed_names.append(month)
if stack:
day = stack.pop()
self.request.form['day'] = int(day)
self.request._traversed_names.append(day)
self.request.setTraversalStack([])
# throw a 404 if we are still seeing more parts to the URL
if stack:
raise NotFound(self.context, name, self.request)
# return a BlogDateRange view
return component.queryMultiAdapter((self.context, self.request), name='dateindex')
except ValueError:
return self.entry_traverse(name)
def entry_traverse(self, name):
return self.context['entries'].get(name, None)
Note that this requires fiddling with _traversed_names, which may not be entirely safe or proper? It seems to work, at least request/URL does the right thing ...
Then a BlogDateRange? view might then look like this:
class BlogDateRange(grok.View):
grok.name('dateindex')
grok.context(Blog)
# update is called once before the view is rendered
def update(self):
"make the form data more convienent to access in the view"
form = self.request.form
self.year = form.get('year', 0)
self.month = form.get('month', 0)
self.day = form.get('day', 0)
def entries(self):
from_, until = extractDateRange( int(self.year),
int(self.month),
int(self.day),
)
return entriesInDateRange(from_, until)
def url(self, obj=None, name=None):
"customize Grok view.url() behaviour"
if not obj and not name:
url = component.getMultiAdapter((self.context, self.request), IAbsoluteURL)()
if self.year:
url += '/' + self.year
if self.month:
url += '/' + self.month
if self.day:
url += '/' + self.day
return url
else:
return super(BlogDateRange, self).url( obj=obj, name=name )
With the work of extracting a date range from a year, and/or month, and/or day factored out into it's own function, as well as the work of searching for entries within the date range:
def extractDateRange(year, month, day):
"""
Extract a (from, until) date range given a year,
and optionally a month and optionally a day
"""
from_, until = None, None
# year range, e.g. /blogcontainer/2007/
if not month:
from_ = datetime(year, 1, 1)
until = datetime(year + 1, 1, 1)
# month range, e.g. /blogcontainer/2007/02/
elif not day:
from_ = datetime(year,
month,
1)
month = month + 1
if month > 12:
month = 1
year += 1
until = datetime(year, month, 1)
# day range, e.g. /blogcontainer/2007/02/11/
else:
from_ = datetime(year,
month,
day)
until = from_ + timedelta(days=1)
return from_, until
def entriesInDateRange(from_, until):
entries = Query().searchResults(
query.And(query.Between(('entry_catalog', 'published'), from_, until),
query.Eq(('entry_catalog', 'workflow_state'), PUBLISHED)))
return sorted(
entries, key=lambda entry: entry.published, reverse=True
)
.
