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 thecontext
attribute to associate a particular view withcontext
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¶
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
Our views in
typeviews/tutorial/views.py
need type-specific registrations: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
from pyramid.location import lineage from pyramid.view import view_config from .resources import ( Root, Folder, Document ) class TutorialViews: def __init__(self, context, request): self.context = context self.request = request self.parents = reversed(list(lineage(context))) @view_config(renderer='templates/root.jinja2', context=Root) def root(self): page_title = 'Quick Tutorial: Root' return dict(page_title=page_title) @view_config(renderer='templates/folder.jinja2', context=Folder) def folder(self): page_title = 'Quick Tutorial: Folder' return dict(page_title=page_title) @view_config(renderer='templates/document.jinja2', context=Document) def document(self): page_title = 'Quick Tutorial: Document' return dict(page_title=page_title)
We have a new contents subtemplate at
typeviews/tutorial/templates/contents.jinja2
:1 2 3 4 5 6 7 8
<h4>Contents</h4> <ul> {% for child in context.values() %} <li> <a href="{{ request.resource_url(child) }}">{{ child.title }}</a> </li> {% endfor %} </ul>
Make a template for viewing the root at
typeviews/tutorial/templates/root.jinja2
:1 2 3 4 5 6 7 8
{% extends "templates/layout.jinja2" %} {% block content %} <h2>{{ context.title }}</h2> <p>The root might have some other text.</p> {% include "templates/contents.jinja2" %} {% endblock content %}
Now make a template for viewing folders at
typeviews/tutorial/templates/folder.jinja2
:1 2 3 4 5 6 7
{% extends "templates/layout.jinja2" %} {% block content %} <h2>{{ context.title }}</h2> {% include "templates/contents.jinja2" %} {% endblock content %}
Finally make a template for viewing documents at
typeviews/tutorial/templates/document.jinja2
:1 2 3 4 5 6 7
{% extends "templates/layout.jinja2" %} {% block content %} <h2>{{ context.title }}</h2> <p>A document might have some body text.</p> {% endblock content %}
More tests are needed in
typeviews/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 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
import unittest from pyramid.testing import DummyRequest from pyramid.testing import DummyResource class TutorialViewsUnitTests(unittest.TestCase): def _makeOne(self, context, request): from .views import TutorialViews inst = TutorialViews(context, request) return inst def test_site(self): request = DummyRequest() context = DummyResource() inst = self._makeOne(context, request) result = inst.root() self.assertIn('Root', result['page_title']) def test_folder_view(self): request = DummyRequest() context = DummyResource() inst = self._makeOne(context, request) result = inst.folder() self.assertIn('Folder', result['page_title']) def test_document_view(self): request = DummyRequest() context = DummyResource() inst = self._makeOne(context, request) result = inst.document() self.assertIn('Document', 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_it(self): res = self.testapp.get('/', status=200) self.assertIn(b'Root', res.body) res = self.testapp.get('/folder1', status=200) self.assertIn(b'Folder', res.body) res = self.testapp.get('/doc1', status=200) self.assertIn(b'Document', res.body) res = self.testapp.get('/doc2', status=200) self.assertIn(b'Document', res.body) res = self.testapp.get('/folder1/doc1', status=200) self.assertIn(b'Document', res.body)
$ $VENV/bin/nosetests
should report running 4 tests.Run your Pyramid application with:
$ $VENV/bin/pserve development.ini --reload
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¶
- Should you calculate the list of children on the Python side, or access it on the template side by operating on the context?
- What if you need different traversal policies?
- 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?
- 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?