Free-standing functions are the regular way to do views. Many times, though, you have several views that are closely related. For example, a content type might have many different ways to look at it.
For some people, grouping these together makes logical sense. A view class lets you group views, sharing some state assignments and helper functions as class methods.
Even better, from a UX person’s perspective, the methods on the view class look like a “Template API” from the inside the namespace of the view.
$ cd ../../creatingux; mkdir step06; cd step06
(Unchanged) Copy the following into step06/application.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from wsgiref.simple_server import make_server
from pyramid.config import Configurator
def main():
config = Configurator()
config.scan("views")
app = config.make_wsgi_app()
return app
if __name__ == '__main__':
app = main()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
|
Copy the following into step06/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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | from pyramid.renderers import get_renderer
from pyramid.decorator import reify
from pyramid.view import view_config
from dummy_data import COMPANY
from dummy_data import PEOPLE
from dummy_data import PROJECTS
from dummy_data import SITE_MENU
class ProjectorViews(object):
def __init__(self, request):
self.request = request
renderer = get_renderer("templates/global_layout.pt")
self.global_template = renderer.implementation().macros['layout']
@reify
def company_name(self):
return COMPANY
@reify
def site_menu(self):
new_menu = SITE_MENU[:]
url = self.request.url
for menu in new_menu:
if menu['title'] == 'Home':
menu['current'] = url.endswith('/')
else:
menu['current'] = url.endswith(menu['href'])
return new_menu
@view_config(renderer="templates/index.pt")
def index_view(self):
return {"page_title": "Home"}
@view_config(renderer="templates/about.pt", name="about.html")
def about_view(self):
return {"page_title": "About"}
@view_config(renderer="templates/company.pt",
name="acme")
def company_view(self):
return {"page_title": COMPANY + " Projects",
"projects": PROJECTS}
@view_config(renderer="templates/people.pt", name="people")
def people_view(self):
return {"page_title": "People", "people": PEOPLE}
|
Copy the following into step06/dummy_data.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # Dummy data
COMPANY = "ACME, Inc."
PEOPLE = [
{'name': 'sstanton', 'title': 'Susan Stanton'},
{'name': 'bbarker', 'title': 'Bob Barker'},
]
PROJECTS = [
{'name': 'sillyslogans', 'title': 'Silly Slogans'},
{'name': 'meaninglessmissions', 'title': 'Meaningless Missions'},
]
SITE_MENU = [
{'href': '', 'title': 'Home'},
{'href': 'about.html', 'title': 'About Projector'},
{'href': 'acme', 'title': COMPANY},
{'href': 'people', 'title': 'People'},
]
|
Copy the following “global template” into step06/templates/global_layout.pt:
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 | <!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>Projector - ${page_title}</title>
</head>
<body>
<ul>
<li tal:repeat="menu view.site_menu">
<tal:block tal:condition="menu.current">
<span>${menu.title}</span>
</tal:block>
<tal:block tal:condition="not menu.current">
<span><a href="/${menu.href}">${menu.title}</a></span>
</tal:block>
</li>
</ul>
<h1>${page_title}</h1>
<div metal:define-slot="content">
</div>
</body>
</html>
|
Copy the following into step06/templates/index.pt:
1 2 3 4 5 | <div metal:use-macro="view.global_template">
<div metal:fill-slot="content">
<p>Home page content goes here.</p>
</div>
</div>
|
Copy the following into step06/templates/about.pt:
1 2 3 4 5 6 7 | <div metal:use-macro="view.global_template">
<div metal:fill-slot="content">
<p>Projector is a simple project management tool capable of hosting
multiple projects for multiple independent companies,
sharing a developer pool between autonomous companies.</p>
</div>
</div>
|
Copy the following into step06/templates/company.pt:
1 2 3 4 5 6 7 8 9 | <div metal:use-macro="view.global_template">
<div metal:fill-slot="content">
<ul>
<li tal:repeat="project projects">
<a href="${project.name}">${project.title}</a>
</li>
</ul>
</div>
</div>
|
Copy the following into step06/templates/people.pt:
1 2 3 4 5 6 7 8 9 | <div metal:use-macro="view.global_template">
<div metal:fill-slot="content">
<ul>
<li tal:repeat="person people">
<a href="${person.name}">${person.title}</a>
</li>
</ul>
</div>
</div>
|
Copy the following into step06/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 60 61 62 63 64 65 | import unittest
from pyramid.testing import DummyRequest
from pyramid.testing import setUp
from pyramid.testing import tearDown
class ProjectorViewsUnitTests(unittest.TestCase):
def setUp(self):
request = DummyRequest()
self.config = setUp(request=request)
def tearDown(self):
tearDown()
def _makeOne(self, request):
from views import ProjectorViews
inst = ProjectorViews(request)
return inst
def test_index_view(self):
request = DummyRequest()
inst = self._makeOne(request)
result = inst.index_view()
self.assertEqual(result['page_title'], 'Home')
def test_about_view(self):
request = DummyRequest()
inst = self._makeOne(request)
result = inst.about_view()
self.assertEqual(result['page_title'], 'About')
def test_company_view(self):
request = DummyRequest()
inst = self._makeOne(request)
result = inst.company_view()
self.assertEqual(result["page_title"], "ACME, Inc. Projects")
self.assertEqual(len(result["projects"]), 2)
def test_people_view(self):
request = DummyRequest()
inst = self._makeOne(request)
result = inst.people_view()
self.assertEqual(result["page_title"], "People")
self.assertEqual(len(result["people"]), 2)
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(b'Home' in res.body)
res = self.testapp.get('/about.html', status=200)
self.assertTrue(b'autonomous' in res.body)
res = self.testapp.get('/people', status=200)
self.assertTrue(b'Susan' in res.body)
res = self.testapp.get('/acme', status=200)
self.assertTrue(b'Silly Slogans' in res.body)
|
$ nosetests should report running 5 tests.
$ python application.py
Open http://127.0.0.1:8080 in your browser.
The idea of a view class can be used to form different patterns. In this case, we want a unit of related work, join up the views for that work, and craft our own little API that our templates use.
The test writing gets a little bit harder.