[Zope3-Users] Specialized URL traversal.. Best way?

Jeff Shell eucci.group at gmail.com
Sat Dec 31 17:34:18 EST 2005


On 12/31/05, Marius Gedminas <mgedmin at b4net.lt> wrote:
> On Thu, Dec 29, 2005 at 11:22:28PM -0700, Jeff Shell wrote:
> > Again, this is to have URLs like:
> >
> > myapp/@@tags/zope/viewlet
> >
> > And turn that into a catalog search for anyof {'zope', 'viewlet'}
> >
> >     def publishTraverse(self, request, name):
> >         namestack = request.getTraversalStack()
> >         if name not in namestack:
> >             namestack.append(name)
> >         namestack.reverse()
> >         tags = tuple(namestack)
> >         request.setTraversalStack([])
> >
> >         results = TaggedArticleFinder(self.__name__,self.__parent__,tags)
> >         results.prepareSearch(request)
> >         return results
>
> My coworker Albertas recently implemented something like this in
> SchoolTool (I've CCed him).  Unfortunately it turned out to have some
> unforeseen consequences, for example, request.URL does not contain the
> path elements you "eat" manually, breaking self-posting forms (those
> that use <form tal:attributes="action request/URL">) and our login
> mechanism.

Yeah, I know of that problem. Looking back, it looks like there have
been a couple of ways of doing this. In Zope 2, there are a couple of
prominent "cool path tricks" in the core content objects. Python
Scripts allow you to get at the rest of the path. But the resulting
affect is like you describe: the traversal stack is chopped off, and
all URLs are relative to the script. We did a pretty cool ECommerce
store in all Python Scripts and SQL Methods that did this to have URLs
to products and categories, building up queries along the way. We
quickly discovered that relative URLs didn't work in that scenario, so
"./product/${product_id}" wouldn't work. I can see why it does this
though - you didn't actually traverse to an object at that point in
the path, so the request.parents attribute can't really have [<app
root>, <product traverser>, <category 1>, <category 3>, <products
marker>, <product 12>] in it, when <product traverser> just takes the
rest of the path and turns it into various script/object calls and
queries.

Zope 2 SQL Methods, on the other hand, allowed you do to direct
traversal and turn a URL into a query. This may have even existed in
the pre-principia version of Aqueduct. So for a two-category
classified system you can do URLs like:

/categorylist/category1_id/34/category2_id/12/list_ads.dtml

What it does is actually create non-persistent traversable objects
that implement the __bobo_traverse__ method. It accumulates the parts
of the URL and somewhere along the way does the search. Acquisition
binding and all of that still worked since the result was ultimately
an object that was bound to the right parent.

The code (In Zope 2: Shared/DC/ZRDB/DA.py, class Traverse) is rather
old and cryptic, but it does provide an example of the other way of
doing custom maps - traverse each name individually and create new
objects along the way. This way, request.URL is not affected. If you
were trying to make a custom traverser like date based archive
searching and where at 2005/12/ in the url, you could then just have
<a href="./28"> to go to the date.

Both situations have their uses. They also have their consequences and
side effects. Playing with the traversalStack seems fine when you
don't need to do anything relative [per say] to the current URL. I
almost always use URL generators, either absolute URL or in the case
of the tags view I made I construct the joined URL in the view, so
this hasn't affected me too much yet.

But I'd certainly like to have the second option. The underlying tools
are there, but knowing which traversal interface / adapter to apply
isn't easy right now... Is __getitem__ enough? No one seems to have a
good answer. IPublishTraverse? ITraversable? It seems like
IPublishTraverse is the best one since it's from zope.publisher and
not zope.app, and is specific to object / URL publishing. But that
comes back to my dilemma:

Is it cool to play with the traversal stack in publishTraverse? Why
isn't 'furtherPath' part of the publishTraverse interface, but part of
zope.app.traversing.interfaces.ITraversable's traverse? Why doesn't
browser traversal seem to use zope.app...ITraversable?

Oh man. Here it is, New Years Eve, I've got errands to run for my dog
that I was going to go do an hour ago, yet here I sit consumed by an
idea that I'd love to try but should put off to some other time.
Basically it would be nice to specify a map... somehow.. for long URLs
that are used to construct queries. A view object could use this map
to match a URL piece by piece by traversing it one by one and making
"proper" objects along the way that are locatable. When it gets to the
end of the match, it could call a provided factory for that particular
Map and pass in the matched URL since the start of the view as a list
or mapping. This suite of tools could provide some map constructors
that allow one to specify repeating path parts. The rest of the URL
would still be in place, and maybe that final object could have more
traversal happen after it, such as view getting. A quick example of
what I'm thinking:

# ViewOrEnd is a marker for end of path or a segment that starts with [@+]
from example.urlmapping.parts import repeating, ViewOrEnd
from example.urlmapping import Maps, Map

class TagQueryURLView(URLMapView):
    urlmap = Maps(Map(map=(repeating(r'\w+'),),
factory='example.tags.TagQuery'))

provide as view name 'tags'

class DateQueryURLView(URLMapView):
    urlmap = Maps(
        Map('example.dates.YearMonthDate', (r'\d{4}', r'\d{1,2}',
r'\d{1,2}', ViewOrEnd)),
        Map('example.dates.YearMonth', (r'\d{4}', r'\d{1,2}', ViewOrEnd)),
        )

I don't know... Something like that. Something to facilitate easy
complex URLs that aren't containment related but instead related to
doing some sort of query, something that would do the kind of thing
that the SQL Method traverser has done for so many years, and actually
keep the parts in the URL and in the parent chain, even if each item
in the parent chain is just 'URLAccumulator' instance. Different map
implementations may allow for providing ways of providing a factory
for individual path matches, and still make things easier to write
than individual 'PublishTraverse' implementors.

Anyways, I've spent too long on this for a holiday.


More information about the Zope3-users mailing list