Making a templated UX, as we’ve seen, is easy to get started on. Quickly, though, the UX designer starts to repeat things. In our example, the “site menu” <ul> appears in each page template.
We want a “main” template with all the common stuff. Then, each view’s template simply jams the markup for that screen into a spot in the main template.
ZPT has a “macros” facility for this, which we use in this step.
$ cd ../../creatingux; mkdir step05; cd step05
(Unchanged) Copy the following into step05/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 step05/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 | from pyramid.renderers import get_renderer
from pyramid.view import view_config
def site_layout():
renderer = get_renderer("templates/global_layout.pt")
layout = renderer.implementation().macros['layout']
return layout
@view_config(renderer="templates/index.pt")
def index_view(request):
return {"layout": site_layout(),
"page_title": "Home"}
@view_config(renderer="templates/about.pt", name="about.html")
def about_view(request):
return {"layout": site_layout(),
"page_title": "About"}
@view_config(renderer="templates/company.pt", name="acme")
def company_view(request):
return {"layout": site_layout(),
"page_title": COMPANY + " Projects",
"company": COMPANY,
"projects": PROJECTS}
@view_config(renderer="templates/people.pt", name="people")
def people_view(request):
return {"layout": site_layout(),
"page_title": "People", "company": COMPANY, "people": PEOPLE}
# 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'},
]
|
Copy the following “global template” into step05/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 | <!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><a href="/">Home</a></li>
<li><a href="/about.html">About Projector</a></li>
<li><a href="/acme">ACME, Inc.</a></li>
<li><a href="/people">People</a></li>
</ul>
<h1>${page_title}</h1>
<div metal:define-slot="content">
</div>
</body>
</html>
|
Copy the following into step05/templates/index.pt:
1 2 3 4 5 | <div metal:use-macro="layout">
<div metal:fill-slot="content">
<p>Home page content goes here.</p>
</div>
</div>
|
Copy the following into step05/templates/about.pt:
1 2 3 4 5 6 7 | <div metal:use-macro="layout">
<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 step05/templates/company.pt:
1 2 3 4 5 6 7 8 9 | <div metal:use-macro="layout">
<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 step05/templates/people.pt:
1 2 3 4 5 6 7 8 9 | <div metal:use-macro="layout">
<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 step05/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 | 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 test_hello_view(self):
from views import index_view
result = index_view({})
self.assertEqual(result['page_title'], 'Home')
def test_about_view(self):
from views import about_view
result = about_view({})
self.assertEqual(result['page_title'], 'About')
def test_company_view(self):
from views import company_view
result = company_view({})
self.assertEqual(result["company"], "ACME, Inc.")
self.assertEqual(len(result["projects"]), 2)
def test_people_view(self):
from views import people_view
result = people_view({})
self.assertEqual(result["company"], "ACME, Inc.")
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.
We had to add a little more ceremony to the unit tests, to get a proper configuration.