3: Traversal Hierarchies¶
Objects with subobjects and views, all via URLs.
Background¶
In 2: Basic Traversal With Site Roots we took the simplest possible step: a root object with little need for the stitching together of a tree known as traversal.
In this step we remain simple, but make a basic hierarchy:
1 2 3 4 5 | /
doc1
doc2
folder1/
doc1
|
Objectives¶
- Use a multi-level nested hierarchy of Python objects.
- Show how
__name__
and__parent__
glue the hierarchy together. - Use objects which last between requests.
Steps¶
We are going to use the previous step as our starting point:
$ cd ..; cp -r siteroot hierarchy; cd hierarchy $ $VENV/bin/python setup.py develop
Provide a richer set of objects in
hierarchy/tutorial/resources.py
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
class Folder(dict): def __init__(self, name, parent, title): self.__name__ = name self.__parent__ = parent self.title = title class Root(Folder): pass class Document(object): def __init__(self, name, parent, title): self.__name__ = name self.__parent__ = parent self.title = title # Done outside bootstrap to persist from request to request root = Root('', None, 'My Site') def bootstrap(request): if not root.values(): # No values yet, let's make: # / # doc1 # doc2 # folder1/ # doc1 doc1 = Document('doc1', root, 'Document 01') root['doc1'] = doc1 doc2 = Document('doc2', root, 'Document 02') root['doc2'] = doc2 folder1 = Folder('folder1', root, 'Folder 01') root['folder1'] = folder1 # Only has to be unique in folder doc11 = Document('doc1', folder1, 'Document 01') folder1['doc1'] = doc11 return root
Have
hierarchy/tutorial/views.py
show information about the resource tree:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
from pyramid.location import lineage from pyramid.view import view_config class TutorialViews: def __init__(self, context, request): self.context = context self.request = request self.parents = reversed(list(lineage(context))) @view_config(renderer='templates/home.jinja2') def home(self): page_title = 'Quick Tutorial: Home' return dict(page_title=page_title) @view_config(name='hello', renderer='templates/hello.jinja2') def hello(self): page_title = 'Quick Tutorial: Hello' return dict(page_title=page_title)
Update the
hierarchy/tutorial/templates/home.jinja2
view template:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
{% extends "templates/layout.jinja2" %} {% block content %} <ul> <li><a href="/">Site Folder</a></li> <li><a href="/doc1">Document 01</a></li> <li><a href="/doc2">Document 02</a></li> <li><a href="/folder1">Folder 01</a></li> <li><a href="/folder1/doc1">Document 01 in Folder 01</a></li> </ul> <h2>{{ context.title }}</h2> <p>Welcome to {{ context.title }}. Visit <a href="{{ request.resource_url(context, 'hello') }}">hello</a> </p> {% endblock content %}
The
hierarchy/tutorial/templates/breadcrumbs.jinja2
template now has a hierarchy to show:1 2 3 4 5
{% for p in view.parents %} <span> <a href="{{ request.resource_url(p) }}">{{ p.title }}</a> >> </span> {% endfor %}
Update the tests in
hierarchy/tutorial/tests.py
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
import unittest from pyramid.testing import DummyRequest from pyramid.testing import DummyResource class TutorialViewsUnitTests(unittest.TestCase): def test_home_view(self): from .views import TutorialViews request = DummyRequest() title = 'Dummy Context' context = DummyResource(title=title, __name__='dummy') inst = TutorialViews(context, request) result = inst.home() self.assertIn('Home', result['page_title']) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): result = self.testapp.get('/', status=200) self.assertIn(b'Site Folder', result.body)
Now run the tests:
$ $VENV/bin/nosetests tutorial .. ---------------------------------------------------------------------- Ran 2 tests in 0.141s OK
Run your Pyramid application with:
$ $VENV/bin/pserve development.ini --reload
Open http://localhost:6543/ in your browser.
Analysis¶
In this example we have to manage our tree by assigning __name__
as an
identifier on each child, and __parent__
as a reference to the parent. The
template used now shows different information based on the object URL to which
you traversed.
We also show that @view_config
can set a "default" view on a context by
omitting the @name
attribute. Thus, if you visit
http://localhost:6543/folder1/
without providing anything after, the
configured default view is used.
Extra Credit¶
- In
resources.py
, we moved the instantiation ofroot
out to global scope. Why? - If you go to a resource that doesn't exist, will Pyramid handle it gracefully?
- If you ask for a default view on a resource and none is configured, will Pyramid handle it gracefully?