Providing Links To Other Objects¶
What if the object you are wrapping can return other objects to which the user might want to navigate?
Imagine the possibilities: a filesystem object you are presenting on the web
might be able to return the files inside of it; a genealogical application
might have person objects that can return their spouse, children, or
grandparents. In the example we are working on here, a Natural
object can
return both the previous and the next number; wouldn’t it be nice to give the
users links to them?
If in a page template you naively ask your Grok view for the URL of a transient
object, you will be disappointed. Grok does know the URL of the object to
which the user has just navigated, because, well, it’s just navigated there, so
adding this near the bottom of your naturalindex.pt
should work just fine:
This page lives at: <b tal:content="python: view.url(context)">url</b><br>
But if you rewrite your template so that it tries asking for the URL of any
other object, the result will be a minor explosion. Try adding this to your
naturalindex.pt
file:
Next number: <b tal:content="python: view.url(context.next)">url</b><br>
and try reloading the page. On the command line, your application will return the exception:
TypeError: There isn't enough context to get URL information.
This is probably due to a bug in setting up location information.
Do you see the problem? Because this new Natural
object does not live
inside of the ZopeDB, Grok cannot guess the URL at which you intend it to live.
In order to provide this information, it is best to call a Zope function called
locate()
that marks as object as belonging inside of a particular
container. To get the chance to do this magic, you’ll have to avoid calling
Natural.previous
and Natural.next
directly from your page template.
Instead, provide your view with two new properties that will grab the
previous
and next
attributes off of the Natural
object which is
your current context, and then perform the required modification before
returning them:
class NaturalIndex(grok.View):
...
@property
def previous(self):
if getattr(self.context, 'previous', None):
n = self.context.previous
traverser = BaseTraverser(grok.getSite(), None)
parent = traverser.publishTraverse(None, 'natural')
return zope.location.location.located(n, parent, str(n))
@property
def next(self):
n = self.context.next
traverser = BaseTraverser(grok.getSite(), None)
parent = traverser.publishTraverse(None, 'natural')
return zope.location.location.located(n, parent, str(n))
This forces upon your objects enough information that Zope can determine their
URL — it will believe that they live inside of the object named by the URL
/app/natural
(or whatever other name you use in the PublishTraverse
call). With the above in place, you can add these links to the bottom of your
naturalindex.pt
and they should work just fine:
<tal:if tal:condition="view/previous">
Previous number: <a tal:attributes="href python: view.url(view.previous)"
tal:content="view/previous">123</a><br>
</tal:if>
Next number: <a tal:attributes="href python: view.url(view.next)"
tal:content="view/next">123</a><br>
This should get easier in a future version of Grok and Zope!