Step 03: Type-Specific Views

In Step 03: Type-Specific Views we had 3 “content types” (SiteFolder, Folder, and Document.) All, however, used the same view and template.

Pyramid traversal though lets you bind a view to a particular content type.

Goals

  • Type-specific views by registering a view against a class

Objectives

  • @view_config which uses the context attribute to associate a particular view with context instances of a particular class
  • Views and templates which are unique to a particular class (aka type)
  • Patterns in test writing to handle multiple kinds of contexts

Steps

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

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

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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 step03/views.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
    from pyramid.view import view_config
    
    from resources import SiteFolder
    from resources import Folder
    from resources import Document
    
    class ProjectorViews(object):
        def __init__(self, context, request):
            self.context = context
            self.request = request
    
        @view_config(renderer="templates/site_view.pt",
                     context=SiteFolder)
        def site_view(self):
            return {"children": self.context.values()}
    
        @view_config(renderer="templates/folder_view.pt",
                     context=Folder)
        def folder_view(self):
            return {"children": self.context.values()}
    
    
        @view_config(renderer="templates/document_view.pt",
                     context=Document)
        def document_view(self):
            return {}
    
  4. (Unchanged) Copy the following into step03/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
    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 step03/templates/document_view.pt:

    1
    2
    3
    <div>
        <h2>Document Title: ${context.title}</h2>
    </div>
    
  6. Copy the following into step03/templates/folder_view.pt:

    1
    2
    3
    4
    5
    6
    7
    8
    <div>
        <h2>Folder Title: ${context.title}</h2>
        <ul>
            <li tal:repeat="child children">
                <a href="${child.__name__}">${child.title}</a>
            </li>
        </ul>
    </div>
    
  7. Copy the following into step03/templates/site_view.pt:

    1
    2
    3
    4
    5
    6
    7
    8
    <div>
        <h2>SiteFolder Title: ${context.title}</h2>
        <ul>
            <li tal:repeat="child children">
                <a href="${child.__name__}">${child.title}</a>
            </li>
        </ul>
    </div>
    
  8. Copy the following into step03/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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    import unittest
    
    from pyramid.testing import DummyRequest
    from pyramid.testing import DummyResource
    
    class DummySite(object):
        title = "Dummy Title"
        __name__ = "dummy"
        __parent__ = None
    
        def values(self):
            return [1,2,3,4,5]
        
    class ProjectorViewsUnitTests(unittest.TestCase):
    
        def _makeOne(self, context, request):
            from views import ProjectorViews
            inst = ProjectorViews(context, request)
            return inst
    
        def test_site_view(self):
            request = DummyRequest()
            context = DummySite()
            inst = self._makeOne(context, request)
            result = inst.site_view()
            self.assertEqual(len(result['children']), 5)
    
        def test_folder_view(self):
            request = DummyRequest()
            context = DummySite()
            inst = self._makeOne(context, request)
            result = inst.folder_view()
            self.assertEqual(len(result['children']), 5)
    
        def test_document_view(self):
            request = DummyRequest()
            context = DummyResource()
            inst = self._makeOne(context, request)
            result = inst.document_view()
            self.assertEqual(result, {})
    
    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('SiteFolder' in res.body)
            res = self.testapp.get('/folder1', status=200)
            self.assertTrue('Folder' in res.body)
            res = self.testapp.get('/doc1', status=200)
            self.assertTrue('Document' in res.body)
            res = self.testapp.get('/doc2', status=200)
            self.assertTrue('Document' in res.body)
            res = self.testapp.get('/folder1/doc1', status=200)
            self.assertTrue('Document' in res.body)
    
  9. $ nosetests should report running 4 tests.

  10. $ python application.py

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

Extra Credit

  1. In Zope, interfaces were used to register a view. How do you do register a Pyramid view against instances that support a particular interface?
  2. Can Pyramid accept view registrations against a superclass?
  3. Can you associate a view with multiple classes?

Analysis

Our container views now calculate the list of children and pass into the template, letting us navigate to children.

The tests now need more ceremony to mock up some dummy values, then test against them.

Discussion

  • Should you calculate the list of children on the Python side, or access it on the template side by operating on the context?
  • In what cases are interfaces a better binding mechanism?
  • Are there times when you want different traversal policies that Pyramid’s default traverser?

Table Of Contents