[Grok-dev] more on temporary objects

Brandon Craig Rhodes brandon at rhodesmill.org
Wed Aug 29 10:30:30 EDT 2007


I continue to learn Grok while writing my tutorial on "temporary
objects", which is the name that I happen to be using today (the name
changes every few days) for "Python objects in Grok which do not live
in the ZODB, but which are instantiated on demand, either when the
user navigates to them with a URL, or when they are created during the
update() of a View - like search results, or objects representing
relational database rows."

I would like help with a few large issues:

 * When To Use Traversers?

   Imagine you are using Grok to build a web interface for an existing
   application that has Person objects stored in a database that are
   instantiated by using an ID number like Person(901), and that you
   want users to be able to visit them at URLs like /myapp/person/901.

   There seem to be a bewildering array of ways to accomplish this.
   Should the tutorial try to cover them all, or are only one or two
   of them "best practices"?  You can use grok.Models with traverse()
   methods:

     '/myapp' selects the "MyApp" instance from the ZODB
      ... and Grok finds that MyApp has a traverse() method
      ... and Grok calls it as traverse('person')
      ... and you return a PersonBox() instance
      ... and Grok finds that the PersonBox has a traverse() method
      ... and Grok calls it as traverse('901')
      ... and you return Person('901').

   But you can also keep your Models free of traverse() methods, by
   creating grok.Traversers that adapt your Model classes!  Then:

     '/myapp' selects the "MyApp" instance from the ZODB
      ... and Grok finds that your MyAppTraverser will adapt a MyApp
      ... and Grok creates your MyAppTraverser on your MyApp
      ... and Grok calls its traverse('person') method
      ... and you return a PersonBox() instance
      ... and Grok finds that your PersonBoxTraverser will adapt a PersonBox
      ... and Grok creates your PersonBoxTraverser on your PersonBox
      ... and Grok calls its traverse('901') method
      ... and you return Person('901').

   Making grok.Traversers rather than putting traverse() methods on
   grok.Models themselves seems like more work, but someone obviously
   thought it would be useful; what is the use case, so that I can
   show an example in my tutorial?

 * Whether To Use Containers?

   When I first mentioned on this mailing list the possibility of a
   two-Traverser cascade, someone mentioned that they were surprised I
   did not simply implement a Container instead.  I have experimented
   with this possibility, and believe that the following two code
   snippets behave in essentially the same way:

      class PersonBox(grok.Model):
          def traverse(self, name):
              return Person(name)

   and:

      from zope.app.container.interfaces import IItemContainer
      class PersonBox(grok.Model):
          grok.implements(IItemContainer)
          def __getitem__(self, name):
              p = Person(name)
              p.__parent__ = self
              p.__name__ = name
              return p

   Obviously, for this simple case, creating a Container takes more
   work; but having a true Container opens the possibility of later
   expanding the PersonBox into a more sophisticated container like an
   IReadContainer, whose contents could be enumerated on a web page
   (so that /myapp/person could show all registered people, for
   example).

   Should I try to work this case into the Tutorial, since it is
   another interesting way to support temporary objects?

 * What About Objects That Are Not grok.Models?

   This seems a very big issue to me: for all of the above to work,
   your object must be a grok.Model, which will not be the case for
   *any* object that one is importing from an existing object model!

   There are two problems with, for example, a Person not being a
   grok.Model:

   1. The first problem is that the Grok traversal logic will not be
      willing to set the __name__ and __parent__ attributes on the
      object.  This breaks the ability to call the view.url() and see
      what URL we have arrived at, and is because the grok.

      The reason is that grok.util.safely_locate_maybe() function
      refuses to mark up an object with a __name__ and __parent__
      unless the object implements the ILocation interface.  Instead,
      it creates a zope.location.LocationProxy() on top of the object
      and returns that instead.  I am entirely confused about why it
      bothers to do this instead of just returning the bare object;
      since view.url() breaks despite the proxy, I am not sure what
      the point of a LocationProxy is!  (Or, is the View failing to
      adapt the object before asking for its __name__ or __parent__?)

      Anyway: since the LocationProxy is broken and fails to make
      view.url() work, this whole issue can be solved by declaring
      your object (assuming it doesn't already use __name__ or
      __parent__ for something else) to be an implementor of
      IContained.  This can even be done dynamically, without monkey
      patching the Person class, by putting code in your traverser
      like:

      from zope.interface import alsoProvides
      ...
          def traverse(self, name):
              p = Person(name)
              alsoProvides(p, IContained)
              return p

       The view.url() call will now work.

    2. Grok cannot associate normal objects with a view.  Even if
       Person simply inherits from "object", then Grok will allow you
       to create a view like:

       class PersonView(grok.View):
           grok.name('index')
           grok.context(Person)

       But the view will then be ignored!  Your Traverser can return a
       Person object, and no error will be given, but Grok will not
       have prepared the View to adapt its context.  I assume that you
       would have to write ZCML to register this View, but have not
       attempted the experiment myself.

       Is there any way one can induce Grok to import a normal class?
       From what I can tell, the ModelGrokker in grok.meta is only
       willing to work on instances of the grok.Model class; but is
       there, for example, any interface one could add to an old class
       to make grok "see" it?  It would be neat to be able to do
       something like:

          from All_My_Legacy_Code import Person
          alsoProvides(Person, grok.IModel)

       and then have my Views with grok.context(IModel) get correctly
       associated with my legacy class.

       Absent a feature like that, one winds up wrapping old classes
       in new ones just to get membership in grok.Model:

          class PersonModel(grok.Model):
              def __init__(self, id):
                  self.person = Person(id)

              def __getattr__(self, name):
                  ... the usual mess ...

       which creates endless problems because everywhere that a Person
       method returns other Person objects (from person.get_boss() and
       so forth), one must remember to wrap it in a PersonModel before
       letting Grok see it.

       By the way: I note that grok.Model not only serves as the
       "marker" for which classes Grok is willing to Grok, and is
       subclassed from IContained so that Zope is willing to handle
       its __name__ and __parent__ attributes, but it also is a
       subclass of persistent.Persistent.  Should it worry me that
       when I follow the instructions of the mailing list and create
       my non-persistent objects as grok.Models, they all are
       instances of Persisent?  Is this dangerous?  Or, as long as I
       am careful to never set _p_changed, will the fact they are
       Persistents never matter?

 * How Can Objects Know Where They Live?

   So: we can now traverse to objects, and the objects know through
   containment where they live, so their view can return its .url().
   There remains the problem of how to find the URL of an object to
   which the user has *not* traversed.  For example, if on a Person's
   page I want to display a link to their supervisor - which is a
   Person object that is returned by Person.boss - then I am in
   trouble!  (Unless I just cheat and use the formula /myapp/person/%s
   with the person's ID in place of the %s; but what's the fun in
   that?)

   If the objects lived in the ZODB, of course, they would always "be
   somewhere" in the hierarchy all the time, and thus know their URL.
   But a Person only gets linked to a PersonBox by being Traversed to
   through it.

   It is this point I am currently researching.  It feels like there
   ought to be an adapter I can write, something like:

      class WhereAPersonObjectLives(object):
          grok.context(Person)
          grok.provides(IChild)
          @property
          def __name__(self):
              return self.context.id
          def __parent__(self):
              return PersonBox()

   that would provide my objects with URLs, but I have not yet figured
   out how it would work.

Thanks for any pointers!  I fear that when I get all of this straight,
the result will be a tutorial that is probably only a tenth the
combined size of all of the emails I've written asking for advice. :-/

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


More information about the Grok-dev mailing list