Web applications include many static assets in the UX: CSS and JS files, images, etc. Web frameworks need to support productive development by the UX team, but also the richness and complexity required by the core developers and the deployment team.
It’s a surprisingly hard problem, supporting all these needs while keeping the simple case easy.
Pyramid accomplishes this using the view machinery and static assets.
$ cd ../../creatingux; mkdir step08; cd step08
Copy the following into step08/application.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from wsgiref.simple_server import make_server
from pyramid.config import Configurator
def main():
config = Configurator()
config.scan("views")
config.add_static_view('static', 'static/',
cache_max_age=86400)
app = config.make_wsgi_app()
return app
if __name__ == '__main__':
app = main()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
|
$ mkdir static
Copy the following into step08/static/global_layout.css:
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 | body {
font-family: Arial, sans-serif;
font-size: 1em;
padding: 0;
margin: 0;
background-color: white;
}
#header {
height: 3em;
background-color: lightgray;
}
#header ul {
margin-left: 1em;
padding: 0;
}
#header li {
display: inline-block;
margin-top: 1em;
padding-right: 0.8em;
}
#header a {
color: black;
}
#main {
margin: 2em;
}
|
Copy the following into step08/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 | from pyramid.view import view_config
from dummy_data import COMPANY
from dummy_data import PEOPLE
from dummy_data import PROJECTS
from layouts import Layouts
class ProjectorViews(Layouts):
def __init__(self, request):
self.request = request
@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 step08/layouts.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 | from pyramid.renderers import get_renderer
from pyramid.decorator import reify
from dummy_data import COMPANY
from dummy_data import SITE_MENU
class Layouts(object):
@reify
def global_template(self):
renderer = get_renderer("templates/global_layout.pt")
return 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
|
Copy the following into step08/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 step08/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 27 28 29 30 | <!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>
<link rel="stylesheet" href="/static/global_layout.css"/>
</head>
<body>
<div id="header">
<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>
</div>
<div id="main"><h1>${page_title}</h1>
<div metal:define-slot="content">
</div>
</div>
</body>
</html>
|
Copy the following into step08/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 step08/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 step08/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 step08/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 step08/test_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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 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)
|
Copy the following into step08/test_layout.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 | import unittest
from pyramid.testing import DummyRequest
from pyramid.testing import setUp
from pyramid.testing import tearDown
class LayoutUnitTests(unittest.TestCase):
def setUp(self):
request = DummyRequest()
self.config = setUp(request=request)
def tearDown(self):
tearDown()
def _makeOne(self):
from layouts import Layouts
inst = Layouts()
return inst
def test_global_template(self):
from chameleon.zpt.template import Macro
inst = self._makeOne()
self.assertEqual(inst.global_template.__class__, Macro)
def test_company_name(self):
from dummy_data import COMPANY
inst = self._makeOne()
self.assertEqual(inst.company_name, COMPANY)
def test_site_menu(self):
from dummy_data import SITE_MENU
inst = self._makeOne()
inst.request = DummyRequest()
self.assertEqual(len(inst.site_menu), len(SITE_MENU))
|
$ nosetests should report running 8 tests.
$ python application.py
Open http://127.0.0.1:8080 in your browser.
Being able to point your Pyramid app at an entire directory and publish it is a boon for quick development. We grabbed the configurator and, with one line, published a directory of assets. No need to individually publish each file and set mime-type.
Setting expires headers is a fiddly part of the development cycle.
Not much to cover. We have a config method that lets us jam in a new part of the URL space, serving up static files.