[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