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

  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:

     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
    
  3. 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)
    
  4. 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 %}
    
  5. 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 %}
    
  6. 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)
    
  7. Now run the tests:

    $ $VENV/bin/nosetests tutorial
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.141s
    
    OK
    
  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?