Step 04: Skeleton Views

Enough “hello world”, now we start working on Projector. The UX person usually has a series of screens they need to prototype, mapping to a URL structure.

We want to make that process fast and productive.

In this step we mock up the structure of the site map:

/
/about.html
/acme
/people

...and make a series of URLs that implement these. Along the way, we make more views and more templates.

Goals

  • Introduce UX workflow of “screens” using dummy data

Objectives

  • “Default” and “named” views
  • More ZPT constructs

Steps

  1. $ cd ../../creatingux; mkdir step04; cd step04

  2. (Unchanged) Copy the following into step04/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()
    
  3. Copy the following into step04/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
    from pyramid.view import view_config
    
    @view_config(renderer="index.pt")
    def index_view(request):
        return {}
    
    
    @view_config(renderer="about.pt", name="about.html")
    def about_view(request):
        return {}
    
    
    @view_config(renderer="company.pt", name="acme")
    def company_view(request):
        return {"company": COMPANY, "projects": PROJECTS}
    
    
    @view_config(renderer="people.pt", name="people")
    def people_view(request):
        return {"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'},
    ]
    
  4. Copy the following into step04/index.pt:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <html>
    <head>
        <title>Projector - Home</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>Projector - Home</h1>
    </body>
    </html>
    
  5. Copy the following into step04/about.pt:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <html>
    <head>
        <title>Projector - About</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>Projector - About</h1>
    <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>
    </body>
    </html>
    
  6. Copy the following into step04/company.pt:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <html>
    <head>
        <title>${company} - Projects</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>Projects</h1>
    <ul>
        <li tal:repeat="project projects">
            <a href="${project.name}">${project.title}</a>
        </li>
    </ul>
    </body>
    </html>
    
  7. Copy the following into step04/people.pt:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <html>
    <head>
        <title>Projector - People</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>People</h1>
    <ul>
        <li tal:repeat="person people">
            <a href="${person.name}">${person.title}</a>
        </li>
    </ul>
    </body>
    </html>
    
  8. Copy the following into step04/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
    import unittest
    
    class ProjectorViewsUnitTests(unittest.TestCase):
        def test_hello_view(self):
            from views import index_view
            result = index_view({})
            self.assertEqual(len(result.keys()), 0)
    
        def test_about_view(self):
            from views import about_view
            result = about_view({})
            self.assertEqual(len(result.keys()), 0)
    
        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_home(self):
            res = self.testapp.get('/', status=200)
            self.assertTrue(b'Home' in res.body)
    
        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)
    
  9. $ nosetests should report running 6 tests.

  10. $ python application.py

  11. Open http://127.0.0.1:8080 in your browser.

Extra Credit

  1. What happens if you have two view registrations with no @name attribute, meaning both as the default?
  2. Is Chameleon (now at version 2) any better at showing you error messages? Give it a try, put some errors into your Python expressions.
  3. Will the WebTest correctly trigger that error?
  4. Does ending your URLs in .html have any effect?

Analysis

We are beginning the process of making a URL space that maps to objects and a hierarchy in our application. At the moment, we are simulating this with views.

Although our number of tests went up, each are still very small. Even such a simple test will still catch most of the silly errors that creep up during initial development. Hopefully you’ll find that running nosetests is more productive than clickety-click.

Discussion

  • How do the registrations happen under the hood?
  • Chameleon, caching, and writing compiled versions to disk

Table Of Contents