================= Custom Traversers ================= But what about situations where you want each of your transient objects to have its own URL on your site? The answer is that you can create ``grok.Traverser`` objects that, when the user enters a URL and Grok tries to find the object which the URL names, intercept those requests and return objects of your own design instead. For our example application ``app``, let's make each ``Natural`` object live at a URL like:: http://localhost:8080/app/natural/496 There is nothing magic about the fact that this URL has three parts, by the way — the three parts being the application name ``"app"``, the word ``"natural"``, and finally the name of the integer ``"496"``. You should easily be able to figure out how to adapt the example application below either to the situation where you want all the objects to live at your application root (which would make the URLs look like ``/app/496``), or where you want URLs to go several levels deeper (like if you wanted ``/app/numbers/naturals/496``). The basic rule is that for each slash-separated URL component (like ``"natural"`` or ``"496"``) that does not actually name an object in the ZODB, you have to provide a ``grok.Traverser``. Make the ``grok.context`` of the Traverser the object that lives at the previous URL component, and give your Traverser a ``traverse()`` method that takes as its argument the next name in the URL and returns the object itself. If the name submitted to your traverser does not name an object, simply return ``None``; this is very easy to do, since ``None`` is the default return value of a Python function that ends without a ``return`` statement. So place the following inside your ``app.py`` file: .. code-block:: python import grok from transient.natural import Natural class TransientApp(grok.Application, grok.Container): pass class BaseTraverser(grok.Traverser): grok.context(TransientApp) def traverse(self, name): if name == 'natural': return NaturalDir() class NaturalDir(object): pass class NaturalTraverser(grok.Traverser): grok.context(NaturalDir) def traverse(self, name): if name.isdigit() and int(name) > 0: return Natural(int(name)) class NaturalIndex(grok.View): grok.context(Natural) grok.name('index.html') And you will only need one template to go with this file, which you should place in ``app_templates/naturalindex.pt``: .. code-block:: html This is the number x!
It is prime. Its prime factors are: f,
Now, if you view the URL ``/app/natural/496`` on your test server, you should see:: This is the number 496! Its prime factors are: 2, 2, 2, 2, 31 Note that there is no view name after the URL. That's because we chose to name our View ``index.html``, which is the default view name in Zope 3. (With ``grok.Model`` and ``grok.Container`` objects, by contrast, the default view selected if none is named is simply ``index`` without the ``.html`` at the end.) You can always name the view explicitly, though, so you will find that you can also view the number 496 at:: http://kenaniah.ten22:8080/app/natural/496/index.html It's important to realize this because, if you need to add more views to a transient object, you of course will have to add them with other names — and to see the information in those other views, users (or the links they use) will have to name the views explicitly. Two final notes: * In order to make this example brief, the application above does not support either the user navigating simply to ``/app``, nor will it allow them to view ``/app/natural``, because we have provided neither our ``TransientApp`` application object nor the ``NaturalDir`` stepping-stone with ``grok.View`` objects that could let them be displayed. You will almost always, of course, want to provide a welcoming page for the top level of your application; but it's up to you whether you think it makes sense for users to be able to visit the intermediate ``/app/natural`` URL or not. If not, then follow the example above and simply do not provide a view, and everything else will work just fine. * In order to provide symmetry in the example above, neither the ``TransientApp`` object nor the ``NaturalDir`` object knows how to send users to the next objects below them. Instead, they are both provided with Traversers. It turns out, I finally admin here at the bottom of the example, that this was not necessary! Grok objects like a ``grok.Container`` or a ``grok.Model`` already have enough magic built-in that you can put a ``traverse()`` method right on the object and Grok will find it when trying to resolve a URL. This would not have helped our ``NaturalDir`` object, of course, because it's not a Grok anything; but it means that we can technically delete the first Traverser and simply declare the first class as: .. code-block:: python class TransientApp(grok.Application, grok.Container): def traverse(self, name): if name == 'natural': return NaturalDir() The reason I did not do this in the actual example above is that showing two different ways to traverse in the same example seemed a bit excessive! I preferred instead to use a single method, twice, that is universal and works everywhere, rather than by starting off with a technique that does not work for most kinds of Python object.