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: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)
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>
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 %}
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 %}
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 %}
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)
$ $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?