4: Type-Specific Views

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

Background

In 3: Traversal Hierarchies we had 3 "content types" (Root, Folder, and Document.) All, however, used the same view and template.

Pyramid traversal lets you bind a view to a particular content type. This ability to make your URLs "object oriented" is one of the distinguishing features of traversal, and makes crafting a URL space more natural. Once Pyramid finds the context object in the URL path, developers have a lot of flexibility in view predicates.

Objectives

  • Use a decorator @view_config which uses the context attribute to associate a particular view with context instances of a particular class.

  • Create views and templates which are unique to a particular class (a.k.a., type).

  • Learn patterns in test writing to handle multiple kinds of contexts.

Steps

  1. We are going to use the previous step as our starting point:

    $ cd ..; cp -r hierarchy typeviews; cd typeviews
    $ $VENV/bin/python setup.py develop
    
  2. Our views in typeviews/tutorial/views.py need type-specific registrations:

     1from pyramid.location import lineage
     2from pyramid.view import view_config
     3
     4from .resources import (
     5    Root,
     6    Folder,
     7    Document
     8    )
     9
    10
    11class TutorialViews:
    12    def __init__(self, context, request):
    13        self.context = context
    14        self.request = request
    15        self.parents = reversed(list(lineage(context)))
    16
    17    @view_config(renderer='templates/root.jinja2',
    18                 context=Root)
    19    def root(self):
    20        page_title = 'Quick Tutorial: Root'
    21        return dict(page_title=page_title)
    22
    23    @view_config(renderer='templates/folder.jinja2',
    24                 context=Folder)
    25    def folder(self):
    26        page_title = 'Quick Tutorial: Folder'
    27        return dict(page_title=page_title)
    28
    29
    30    @view_config(renderer='templates/document.jinja2',
    31                 context=Document)
    32    def document(self):
    33        page_title = 'Quick Tutorial: Document'
    34        return dict(page_title=page_title)
    
  3. We have a new contents subtemplate at typeviews/tutorial/templates/contents.jinja2:

    1<h4>Contents</h4>
    2<ul>
    3    {% for child in context.values() %}
    4        <li>
    5            <a href="{{ request.resource_url(child) }}">{{ child.title }}</a>
    6        </li>
    7    {% endfor %}
    8</ul>
    
  4. Make a template for viewing the root at typeviews/tutorial/templates/root.jinja2:

    1{% extends "templates/layout.jinja2" %}
    2{% block content %}
    3
    4    <h2>{{ context.title }}</h2>
    5    <p>The root might have some other text.</p>
    6    {% include "templates/contents.jinja2" %}
    7
    8{% endblock content %}
    
  5. Now make a template for viewing folders at typeviews/tutorial/templates/folder.jinja2:

    1{% extends "templates/layout.jinja2" %}
    2{% block content %}
    3
    4    <h2>{{ context.title }}</h2>
    5    {% include "templates/contents.jinja2" %}
    6
    7{% endblock content %}
    
  6. Finally make a template for viewing documents at typeviews/tutorial/templates/document.jinja2:

    1{% extends "templates/layout.jinja2" %}
    2{% block content %}
    3
    4    <h2>{{ context.title }}</h2>
    5    <p>A document might have some body text.</p>
    6
    7{% endblock content %}
    
  7. More tests are needed in typeviews/tutorial/tests.py:

     1import unittest
     2
     3from pyramid.testing import DummyRequest
     4from pyramid.testing import DummyResource
     5
     6
     7class TutorialViewsUnitTests(unittest.TestCase):
     8    def _makeOne(self, context, request):
     9        from .views import TutorialViews
    10
    11        inst = TutorialViews(context, request)
    12        return inst
    13
    14    def test_site(self):
    15        request = DummyRequest()
    16        context = DummyResource()
    17        inst = self._makeOne(context, request)
    18        result = inst.root()
    19        self.assertIn('Root', result['page_title'])
    20
    21    def test_folder_view(self):
    22        request = DummyRequest()
    23        context = DummyResource()
    24        inst = self._makeOne(context, request)
    25        result = inst.folder()
    26        self.assertIn('Folder', result['page_title'])
    27
    28    def test_document_view(self):
    29        request = DummyRequest()
    30        context = DummyResource()
    31        inst = self._makeOne(context, request)
    32        result = inst.document()
    33        self.assertIn('Document', result['page_title'])
    34
    35
    36class TutorialFunctionalTests(unittest.TestCase):
    37    def setUp(self):
    38        from tutorial import main
    39        app = main({})
    40        from webtest import TestApp
    41        self.testapp = TestApp(app)
    42
    43    def test_it(self):
    44        res = self.testapp.get('/', status=200)
    45        self.assertIn(b'Root', res.body)
    46        res = self.testapp.get('/folder1', status=200)
    47        self.assertIn(b'Folder', res.body)
    48        res = self.testapp.get('/doc1', status=200)
    49        self.assertIn(b'Document', res.body)
    50        res = self.testapp.get('/doc2', status=200)
    51        self.assertIn(b'Document', res.body)
    52        res = self.testapp.get('/folder1/doc1', status=200)
    53        self.assertIn(b'Document', res.body)
    
  8. $ $VENV/bin/nosetests should report running 4 tests.

  9. Run your Pyramid application with:

    $ $VENV/bin/pserve development.ini --reload
    
  10. Open http://localhost:6543/ in your browser.

Analysis

For the most significant change, our @view_config now matches on a context view predicate. We can say "use this view when looking at this kind of thing." The concept of a route as an intermediary step between URLs and views has been eliminated.

Extra Credit

  1. Should you calculate the list of children on the Python side, or access it on the template side by operating on the context?

  2. What if you need different traversal policies?

  3. In Zope, interfaces were used to register a view. How do you register a Pyramid view against instances that support a particular interface? When should you?

  4. Let's say you need a more specific view to be used on a particular instance of a class, letting a more general view cover all other instances. What are some of your options?