Step 02: Basic Hierarchy for Traversal

In Step 01: Most Basic Resource: Site Root 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:

/
   doc1
   doc2
   folder1/
      doc1

Goals

  • Multi-level nested hierarchy of Python objects

Objectives

  • Show how __name__ and __parent__ glue the hierarchy together
  • Objects which last between requests
  • Bring back templates

Steps

  1. $ cd ../../resources; mkdir step02; cd step02

  2. (Unchanged) Copy the following into step02/application.py:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    from wsgiref.simple_server import make_server
    
    from pyramid.config import Configurator
    from resources import bootstrap
    
    
    def main():
        config = Configurator(root_factory=bootstrap)
        config.scan("views")
        app = config.make_wsgi_app()
        return app
    
    
    if __name__ == '__main__':
        app = main()
        server = make_server(host='0.0.0.0', port=8080, app=app)
        server.serve_forever()
    
  3. Copy the following into step02/views.py:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    from pyramid.view import view_config
    
    class ProjectorViews(object):
        def __init__(self, context, request):
            self.context = context
            self.request = request
    
        @view_config(renderer="templates/default_view.pt")
        def default_view(self):
            # XXX Might be done more cleanly
            parent = self.context.__parent__
            if parent:
                parent_title = parent.title
            else:
                parent_title = "None"
            return {
                "page_title": self.context.title,
                "name": self.context.__name__,
                "parent_title": parent_title,
                }
    
  4. Copy the following into step02/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
    class Folder(dict):
        def __init__(self, name, parent, title):
            self.__name__ = name
            self.__parent__ = parent
            self.title = title
    
    
    class SiteFolder(Folder):
        pass
    
    
    class Document(object):
        def __init__(self, name, parent, title):
            self.__name__ = name
            self.__parent__ = parent
            self.title = title
    
    root = SiteFolder('', None, 'Projector Site')
    
    def bootstrap(request):
        # 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
    
  5. Copy the following into step02/templates/default_view.pt:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:metal="http://xml.zope.org/namespaces/metal"
          xmlns:tal="http://xml.zope.org/namespaces/tal"
          metal:define-macro="layout">
    <head>
        <title>Projector - ${context.title}</title>
    </head>
    <body>
    <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>
    <h1>${page_title}</h1>
    
    <div>__name__: ${name}</div>
    <div>__parent__: ${parent_title}</div>
    </body>
    </html>
    
  6. Copy the following into step02/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
    29
    30
    31
    import unittest
    
    from pyramid.testing import DummyRequest
    from pyramid.testing import DummyResource
    
    class ProjectorViewsUnitTests(unittest.TestCase):
        def test_default_view(self):
            from views import ProjectorViews
    
            request = DummyRequest()
            title = "Dummy Context"
            context = DummyResource(title=title, __name__='dummy')
            inst = ProjectorViews(context, request)
            result = inst.default_view()
            self.assertEqual(result['page_title'], 'Dummy Context')
            self.assertEqual(result['parent_title'], 'None')
            self.assertEqual(result['name'], 'dummy')
    
    class ProjectorFunctionalTests(unittest.TestCase):
        def setUp(self):
            from application import main
    
            app = main()
            from webtest import TestApp
    
            self.testapp = TestApp(app)
    
        def test_it(self):
            res = self.testapp.get('/', status=200)
            self.assertTrue('Site Folder' in res.body)
            self.assertTrue('Projector Site' in res.body)
    
  7. $ nosetests should report running 2 tests.

  8. $ python application.py

  9. Open http://127.0.0.1:8080/ in your browser.

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. What happens if you use a __name__ that already exists in the container?

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 which you traversed to.

Discussion

  • Full discussion of how traversal and lookup works
  • pyramid_traversaltools and other convenience tools (e.g. repoze.folder)

Table Of Contents