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   doc1
3   doc2
4   folder1/
5      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

  1. 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
    
  2. Provide a richer set of objects in hierarchy/tutorial/resources.py:

     1class Folder(dict):
     2    def __init__(self, name, parent, title):
     3        self.__name__ = name
     4        self.__parent__ = parent
     5        self.title = title
     6
     7
     8class Root(Folder):
     9    pass
    10
    11
    12class Document(object):
    13    def __init__(self, name, parent, title):
    14        self.__name__ = name
    15        self.__parent__ = parent
    16        self.title = title
    17
    18# Done outside bootstrap to persist from request to request
    19root = Root('', None, 'My Site')
    20
    21
    22def bootstrap(request):
    23    if not root.values():
    24        # No values yet, let's make:
    25        # /
    26        #   doc1
    27        #   doc2
    28        #   folder1/
    29        #      doc1
    30        doc1 = Document('doc1', root, 'Document 01')
    31        root['doc1'] = doc1
    32        doc2 = Document('doc2', root, 'Document 02')
    33        root['doc2'] = doc2
    34        folder1 = Folder('folder1', root, 'Folder 01')
    35        root['folder1'] = folder1
    36
    37        # Only has to be unique in folder
    38        doc11 = Document('doc1', folder1, 'Document 01')
    39        folder1['doc1'] = doc11
    40
    41    return root
    
  3. Have hierarchy/tutorial/views.py show information about the resource tree:

     1from pyramid.location import lineage
     2from pyramid.view import view_config
     3
     4
     5class TutorialViews:
     6    def __init__(self, context, request):
     7        self.context = context
     8        self.request = request
     9        self.parents = reversed(list(lineage(context)))
    10
    11    @view_config(renderer='templates/home.jinja2')
    12    def home(self):
    13        page_title = 'Quick Tutorial: Home'
    14        return dict(page_title=page_title)
    15
    16    @view_config(name='hello', renderer='templates/hello.jinja2')
    17    def hello(self):
    18        page_title = 'Quick Tutorial: Hello'
    19        return dict(page_title=page_title)
    
  4. Update the hierarchy/tutorial/templates/home.jinja2 view template:

     1{% extends "templates/layout.jinja2" %}
     2{% block content %}
     3
     4  <ul>
     5    <li><a href="/">Site Folder</a></li>
     6    <li><a href="/doc1">Document 01</a></li>
     7    <li><a href="/doc2">Document 02</a></li>
     8    <li><a href="/folder1">Folder 01</a></li>
     9    <li><a href="/folder1/doc1">Document 01 in Folder 01</a></li>
    10  </ul>
    11
    12  <h2>{{ context.title }}</h2>
    13
    14  <p>Welcome to {{ context.title }}. Visit
    15    <a href="{{ request.resource_url(context, 'hello') }}">hello</a>
    16  </p>
    17
    18{% endblock content %}
    
  5. The hierarchy/tutorial/templates/breadcrumbs.jinja2 template now has a hierarchy to show:

    1{% for p in view.parents %}
    2<span>
    3  <a href="{{ request.resource_url(p) }}">{{ p.title }}</a>
    4>> </span>
    5{% endfor %}
    
  6. Update the tests in hierarchy/tutorial/tests.py:

     1import unittest
     2
     3from pyramid.testing import DummyRequest
     4from pyramid.testing import DummyResource
     5
     6
     7class TutorialViewsUnitTests(unittest.TestCase):
     8    def test_home_view(self):
     9        from .views import TutorialViews
    10
    11        request = DummyRequest()
    12        title = 'Dummy Context'
    13        context = DummyResource(title=title, __name__='dummy')
    14        inst = TutorialViews(context, request)
    15        result = inst.home()
    16        self.assertIn('Home', result['page_title'])
    17
    18
    19class TutorialFunctionalTests(unittest.TestCase):
    20    def setUp(self):
    21        from tutorial import main
    22        app = main({})
    23        from webtest import TestApp
    24        self.testapp = TestApp(app)
    25
    26    def test_home(self):
    27        result = self.testapp.get('/', status=200)
    28        self.assertIn(b'Site Folder', result.body)
    
  7. Now run the tests:

    1$ $VENV/bin/nosetests tutorial
    2..
    3----------------------------------------------------------------------
    4Ran 2 tests in 0.141s
    5
    6OK
    
  8. Run your Pyramid application with:

    $ $VENV/bin/pserve development.ini --reload
    
  9. 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

  1. In resources.py, we moved the instantiation of root out to global scope. Why?

  2. If you go to a resource that doesn't exist, will Pyramid handle it gracefully?

  3. If you ask for a default view on a resource and none is configured, will Pyramid handle it gracefully?