Free-standing functions are the simple way to do views. Many times, though, you have several views that are closely related. For example, a wiki might have many different ways to look at it (home page, add, view, edit, delete, table of contents, etc.)
For some people, grouping these together makes logical sense. A view class lets you group these views, sharing some state assignments and helper functions as class methods.
Let’s again use the previous package as a starting point for a new distribution:
(env33)$ cd ..; cp -r step06 step07; cd step07
(env33)$ python3.3 setup.py develop
Edit tutorial/views.py to include a view class with several more views, along with a “layout” template:
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 | from pyramid.httpexceptions import HTTPFound
from pyramid.renderers import get_renderer
from pyramid.view import view_config
class WikiViews(object):
def __init__(self, request):
self.request = request
renderer = get_renderer("templates/layout.pt")
self.layout = renderer.implementation().macros['layout']
@view_config(route_name='wiki_view',
renderer='templates/wiki_view.pt')
def wiki_view(self):
return dict(title='Welcome to the Wiki')
@view_config(route_name='wikipage_add',
renderer='templates/wikipage_addedit.pt')
def wikipage_add(self):
return dict(title='Add Wiki Page')
@view_config(route_name='wikipage_view',
renderer='templates/wikipage_view.pt')
def wikipage_view(self):
uid = self.request.matchdict['uid']
return dict(title='View Wiki Page', uid=uid)
@view_config(route_name='wikipage_edit',
renderer='templates/wikipage_addedit.pt')
def wikipage_edit(self):
return dict(title='Edit Wiki Page')
@view_config(route_name='wikipage_delete')
def wikipage_delete(self):
return HTTPFound('/')
|
Since we have new views, our __init__.py needs new routes:
1 2 3 4 5 6 7 8 9 10 11 12 from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.add_route('wiki_view', '/') config.add_route('wikipage_add', '/add') config.add_route('wikipage_view', '/{uid}') config.add_route('wikipage_edit', '/{uid}/edit') config.add_route('wikipage_delete', '/{uid}/delete') config.scan() return config.make_wsgi_app()
Now we make some new templates. First, tutorial/templates/layout.pt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!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>Wiki - ${title}</title> </head> <body> <div id="main"> <div> <a href="/">Wiki Home</a> </div> <h1>${title}</h1> <div metal:define-slot="content"> </div> </div> </body> </html>
Next, tutorial/templates/wiki_view.pt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <div metal:use-macro="view.layout"> <div metal:fill-slot="content"> <p>Some sample links:</p> <ul> <li> <a href="/">Wiki Home</a> </li> <li> <a href="/add">Add WikiPage</a> </li> <li> <a href="/100">View WikiPage</a> </li> <li> <a href="/100/edit">Edit WikiPage</a> </li> <li> <a href="/100/delete">Delete WikiPage</a> </li> </ul> </div> </div>
Next, tutorial/templates/wikipage_addedit.pt:
1 2 3 4 5 <div metal:use-macro="view.layout"> <div metal:fill-slot="content"> <p>Content here.</p> </div> </div>
Finally, tutorial/templates/wikipage_view.pt:
1 2 3 4 5 <div metal:use-macro="view.layout"> <div metal:fill-slot="content"> <p>Content here for uid <em>${uid}</em>.</p> </div> </div>
Since we have more views, we need more tests in 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 import unittest from pyramid import testing class WikiViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_wiki_view(self): from tutorial.views import WikiViews request = testing.DummyRequest() inst = WikiViews(request) response = inst.wiki_view() self.assertEqual(response['title'], 'Welcome to the Wiki') class WikiFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main settings = {} app = main(settings) from webtest import TestApp self.testapp = TestApp(app) def test_it(self): res = self.testapp.get('/', status=200) self.assertIn(b'Welcome', res.body) res = self.testapp.get('/add', status=200) self.assertIn(b'Add Wiki Page', res.body) res = self.testapp.get('/100', status=200) self.assertIn(b'100', res.body) res = self.testapp.get('/100/edit', status=200) self.assertIn(b'Edit', res.body) res = self.testapp.get('/100/delete', status=302) self.assertIn(b'Found', res.body)
Run the tests in your package using nose:
(env33)$ nosetests .
..
-----------------------------------------------------------------
Ran 2 tests in 1.971s
OK
Run the WSGI application:
(env33)$ pserve development.ini --reload
Open http://127.0.0.1:6547/ in your browser.
Our __init__ shows us a new feature we are using with our add_route configurations: named parameters. These variables, indicated with curly braces, later become available on request.matchdict.
We are adding multiple views to our site now. Rather than repeat the same stuff in <head> and the other “chrome” common to all templates, we make a master template available in the view class as layout. Attributes and methods of the view class instance are available inside the template from the view variable, as we saw with view.layout.
The master template defines slots that can be filled by the view templates. Our layout.pt used metal:define-slot="content" to make one such slot. Each view template filled this with metal:fill-slot="content".
Our wikipage_delete returns an HTTPFound. This is Pyramid’s way of issuing a redirect.
In our unit tests we have to add an extra step to create an instance of the view class, and then call the view being tested.