=================
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.