Personal tools
You are here: Home Documentation Tutorials Navigating To Transient Objects Tutorial Providing Links To Other Objects

Providing Links To Other Objects

What if the object you are wrapping can return other objects to which the user might want to navigate?
Thanks to the magic of this database, your web apps can create Python objects that are automatically saved to disk and are available every time your application runs. In particular, every grok.Model and grok.Container object you generate can be written safely to your application's Data.fs file. But sometimes you need to create objects that do not persist in the ZODB, wonderful though it is.
Page 8 of 9.

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!