[Grok-dev] how should we compute URLs for transient objects?

Brandon Craig Rhodes brandon at rhodesmill.org
Thu Sep 6 00:25:02 EDT 2007


Imagine that I have legacy code that returns "Person" objects with
unique "id" attributes.  We have already discussed how, using either a
grok.Model with a traverse() method, or a grok.Traverser with a
Traverse() method, or a virtual container, we could make a URL like
"/app/person/23" wind up resolving to the "Person" object with the
given id.

And we have seen how, if I'm careful enough, and do not break the
containment hierarchy, then one can ask for the URL of the "Person"
object, and the __parent__ links will successfully be followed back to
the Application, generating the URL (in reverse) as they go.

My problem now is with objects to which the user has *not* just
traversed.

Imagine, for example, a Person with attributes like "person.boss" and
"person.mother" and "person.father", where each of these attributes
returns yet another "Person" object from the object model.  If we ask
for the URL of any of these additional "Person" objects - for example,
if we ask for the URL of "person.boss" because we want the person's
View to present a link to the View of their boss - then we encounter
complete failure, since no raw "Person" object will have any notion of
supporting ILocation or being able to tell Zope where it is.

Now, seems like a wonderful example of where an Adapter could step in
and solve our problem!  We could smile, and stop worrying that a
"Person" is not Zope-aware and does not support ILocation, by just
providing that ability ourselves, through something like:

class PersonLocation(grok.Adapter):
    grok.context(Person)          # or "IPerson", if one gets fancy
    grok.provides(ILocation)
    def __init__(self, context):
        self.__parent__ = PersonContainer()
        self.__name__ = str(context.id)

Now, this looks very nearly like it should work, until we actually
read the "AbsoluteURL" class in zope/traversing/browser/absoluteurl.py
and realize that it performs no adaptation at all before trying to
access the __parent__ attribute of its victim!  The lines are:

        container = getattr(context, '__parent__', None)
        if container is None:
            raise TypeError(_insufficientContext)

At this point I need some perspective.  My impression had been - but
maybe I was reading too much of Design Patterns into Zope 3? - that an
adapter-centric way of programming always dictated that, before
demanding a behavior of an object, one asked for the object to be
adapted to an interface that provides such functionality.  So I had
expected to see, in place of the above code, something like:

        try:
            location = ILocation(context)
        except TypeError:
            raise TypeError(_insufficientContext)
        container = getattr(location, '__parent__', None)
        if container is None:
            raise TypeError(_insufficientContext)

I expected to read something like this because, unless one first
demands an ILocation interface to the object, then there is no
"contract" between you and the object that would lead you to expect
the presence of __parent__ at all.

Am I biased because, if "AbsoluteURL" did adapt its context first,
then my code would work? :-) There seems to be something larger at
stake here; something about being able to get a collection of naive
objects that know nothing about Zope 3, and being able to build a thin
hedge of functionality around them that makes them *appear* as capable
as, say, a native "grok.Model", while not disturbing any of their
actual code.

-- 
Brandon Craig Rhodes   brandon at rhodesmill.org   http://rhodesmill.org/brandon


More information about the Grok-dev mailing list