FEEDS Layout

As mentioned in the tutorial home page, our goal is to gradually introduce KARL3 technologies in the creation of a new tool.

To avoid too much technology at once, we will actually start outside KARL3. Instead, we will use the sample project provided by repoze.bfg which we created in the “Getting Started” step.

In this step, we modify the sample project to prepare it for development of a FEEDS tool.

Objectives

  1. Modify the models and unit tests to use a FeedsContainer folder as the root object.
  2. Change the ZCML configuration file to point at a FeedsContainer.
  3. Alter the views and templates to reflect the new names, as well as the unit tests for the views.
  4. Re-organize the template to use a common “layout” (ZPT macro) that we can share between templates, providing a common look-and-feel.

Before Making Changes

Let’s make sure our sample application was setup correctly. First, as we will do regularly, let’s run the unit tests:

$ cd FeedsTool
$ python setup.py test -q
(lots of stuff deleted)
..
-------------------------------------------------------------------
Ran 2 tests in 0.153s

OK

The first time we run the unit tests, a lot of development versions of packages (eggs) are downloaded. The second time we run tests, though, it takes much less time.

As we can see, the tests run ok. Next, let’s fire up the application and make sure it runs correctly:

$ paster serve FeedsTool.ini --reload
Starting subprocess with file monitor
Starting server in PID 77246.
serving on 0.0.0.0:6543 view at http://127.0.0.1:6543

Open a browser and go to http://localhost:6543/ where you should see:

../../_images/home01.png

Classes, Interfaces, and Tests in Models

The Paster template we used created a repoze.bfg project named FeedsTool. We would like to make some changes, first in the “model” (the content):

  • Instead of having the root object be a FeedsTool, we want the root object to be a Site.
  • We plan to use interface declarations for Zope-style development, so we need an IFeedsContainer interface. The Site class should say that it supports this IFeedsContainer interface.
  • The tests should be changed to reflect this change, as well as adding tests for the interface.

Let’s change feedstool/models.py to look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from repoze.folder.interfaces import IFolder
from zope.interface import implements
from repoze.folder import Folder

class IFeedsContainer(IFolder):
    pass

class Site(Folder):
    implements(IFeedsContainer)

def appmaker(zodb_root):
    if not 'app_root' in zodb_root:
        app_root = Site()
        zodb_root['app_root'] = app_root
        import transaction
        transaction.commit()
    return zodb_root['app_root']
  1. Lines 1-3. We import code for support of interfaces and the Folder class, which provides performant storage and retrieval of content.
  2. Line 5. Make an interface that signifies a feed container. We can then attach behavior to objects that support this interface.
  3. Line 8-9. The template made a FeedsTool class for the instance at the root of the site. Let’s call this Site, and then say that instances of this class support the behavior of IFeedsContainer.
  4. Line 13. Have the bootstrapping process create an instance of our Site class for the root.

To get repoze.folder into our development sandbox, we use easy_install:

$ easy_install -i http://dist.repoze.org/lemonade/dev/simple repoze.folder

When we run our tests, we see the errors we need to fix:

$ python setup.py test -q
(snip)
ZopeXMLConfigurationError: File "/Users/paul/venvs/k3training/FeedsTool/feedstool/configure.zcml", line 6.2-9.7
  ConfigurationError: ('Invalid value for', 'for', 'ImportError: Module feedstool.models has no global MyModel')

-------------------------------------------------------------------
Ran 2 tests in 0.052s

FAILED (errors=1)

This is to be expected: we changed some names of classes. Let’s fix the tests first by editing the feedstool/configure.zcml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<configure xmlns="http://namespaces.repoze.org/bfg">

  <!-- this must be included for the view declarations to work -->
  <include package="repoze.bfg.includes" />

  <view
     for=".models.IFeedsContainer"
     view=".views.my_view"
     />

  <view
     for=".models.IFeedsContainer"
     view=".views.static_view"
     name="static"
     />

</configure>
  1. Line 7 and Line 12. Instead of associating a view with a class, we associate a view with an interface. In this case, IFeedsContainer.

Running the tests now leads to all tests passing:

$ python setup.py test -q
(snip)
--------------------------------------------------------------------
Ran 2 tests in 0.029s

OK

We want to add some tests to the model to make sure our class lives up to its “contract”. Add the following class to feedstool/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
class SiteTests(unittest.TestCase):

    def setUp(self):
        cleanUp()

    def tearDown(self):
        cleanUp()

    def _getTargetClass(self):
        from feedstool.models import Site
        return Site

    def _makeOne(self):
        tc = self._getTargetClass()
        return tc()

    def test_class_conforms_to_IFeedsContainer(self):
        from zope.interface.verify import verifyClass
        from feedstool.models import IFeedsContainer
        verifyClass(IFeedsContainer, self._getTargetClass())

    def test_instance_conforms_to_IFeedsContainer(self):
        from zope.interface.verify import verifyObject
        from feedstool.models import IFeedsContainer
        verifyObject(IFeedsContainer, self._makeOne())

This unit test makes an instance of a Site and makes sure it supports the right interfaces.

Run the unit tests again and ensure that all tests pass.

With the models now working, let’s do some cleanup on the views and templates.

Views, Templates, and a Common Layout

To continue fitting the sample application into our tutorial layout, we’ll next change the names of the existing view and templates to be more meaningful.

In particular, we would like to have each template be as simple as possible, sharing a common look-and-feel that is easy to apply to all templates and maintain in the long-term. In ZPT, this is achieved through “macros”. In KARL3, each differently look-and-feel we use (office, community, generic, etc.) are different “layouts”, implemented as a different ZPT macro template.

First we’ll edit feedstool/views.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from repoze.bfg.chameleon_zpt import render_template_to_response
from repoze.bfg.view import static
from repoze.bfg.chameleon_zpt import get_template

static_view = static('templates/static')

def list_feeds_view(context, request):
    layout = get_template('templates/layout.pt')

    return render_template_to_response(
        'templates/list_feeds.pt',
        request = request,
        layout=layout,
        page_title="List Feeds")
  1. Line 3. We will later need to load a new ZPT for the “layout” macro, so import the function that lets us load a template.
  2. Line 7. Change the name of the view function from my_view to list_feeds_view.
  3. Line 8. Load the “layout” template, containing the ZPT macro that controls look-and-feel across all pages.

Note

The get_template runs inside the view function, meaning it is executed on every request. Isn’t that a performance penalty? Fortunately not, as repoze.bfg provides caching of compiled templates.

  1. Lines 10-14. Stop passing in the project value to the template rendering, and instead pass in a page_title as well as the layout.

That’s it for the changes to the view. The template changes are more substantial.

First, save the following as feedstool/templates/layout.pt. The “layout” is a ZPT that defines a slot:

 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
<!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:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal" 
      metal:define-macro="master">
  <head>
    <title>${page_title} Feed</title>
    <link href="${request.application_url}/static/default.css" 
	  rel="stylesheet" type="text/css" />
  </head>
  <body>
    <!-- start header -->
    <div id="logo">
      <h2><code>FeedsTool</code>, a <code>repoze.bfg</code> application</h2>
    </div>
    <div id="header">
      <div id="menu">
      </div>
    </div>
    <!-- end header -->
    <div id="wrapper">
      <!-- start page -->
      <div id="page">

	<!-- start content -->
	<div class="post">
	  <h1 class="title">${page_title}</h1>
	  <div metal:define-slot="content" id="content"/>
	</div>
	<!-- end content -->

	<!-- start sidebar -->
	<div id="sidebar">
	  <ul>
	    <li>
	      <a href="/">Home</a>
	    </li>
	  </ul>
	</div>
	<!-- end sidebar -->
	<div style="clear: both;">&nbsp;</div>
      </div>
    </div>
    <!-- end page -->
    <!-- start footer -->
    <div id="footer">
      <p id="legal">( c ) 2008. All Rights Reserved. Template design
	by <a href="http://www.freecsstemplates.org/">Free CSS
	  Templates</a>.</p>
    </div>
    <!-- end footer -->
  </body>
</html>
  1. Lines 5-6. Add the metal namespace and say that the html element is a ZPT macro named master.
  2. Line 8. The HTML <title> will be a value passed in from each view, into the rendering.
  3. Lines 26-31. This is where most of the action is located. First, we ensure consistency between the <title> in the <head> and the visible “page heading” by reusing the same value. We then define a slot named content. Each template that uses this layout thus fills this slot.

We now make a new list_feeds.pt template that “fills” the content slot in the layout:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<div xmlns="http://www.w3.org/1999/xhtml" 
     xmlns:tal="http://xml.zope.org/namespaces/tal"
     xmlns:metal="http://xml.zope.org/namespaces/metal"
     metal:use-macro="layout.macros['master']">

  <div metal:fill-slot="content">

    <p>Welcome to the FeedsTool.  Below is a list of current
    feeds.</p>

    <ul>
      <li>
	<a href="#">Some Feed</a>
      </li>
    </ul>

  </div>

</div>
  1. Line 3-4. Bring in the metal namespace and have the layout.macros['master'] take over.
  2. Line 6. We tell this <div> to have its content shoved into the content slot in the layout.macros['master'] macro.

The configure.zcml needs to be pointed at the list_feeds_view:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<configure xmlns="http://namespaces.repoze.org/bfg">

  <!-- this must be included for the view declarations to work -->
  <include package="repoze.bfg.includes" />

  <view
     for=".models.IFeedsContainer"
     view=".views.list_feeds_view"
     />

  <view
     for=".models.IFeedsContainer"
     view=".views.static_view"
     name="static"
     />

</configure>
  1. Line 8. Point at the renamed list_feeds_view function.

The unit tests now fail because we renamed the view function:

$ python setup.py test -q
(snip)
..EE
======================================================================
ERROR: test_my_view (feedstool.tests.ViewIntegrationTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/paul/venvs/k3training/FeedsTool/feedstool/tests.py", line 89, in test_my_view
    from feedstool.views import my_view
ImportError: cannot import name my_view

======================================================================
ERROR: test_my_view (feedstool.tests.ViewTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/paul/venvs/k3training/FeedsTool/feedstool/tests.py", line 53, in test_my_view
    from feedstool.views import my_view
ImportError: cannot import name my_view

----------------------------------------------------------------------
Ran 4 tests in 0.028s

FAILED (errors=2)

Edit tests.py and replace the six occurrences of my_view with list_feeds_view. Also, replace mytemplate.pt with list_feeds.pt.

Running the tests again shows that the test expected project to be passed into the template rendering. Change line 59:

renderer.assert_(page_title='List Feeds')

This ensures that the HTML result of the rendering has a certain string.

Running the tests should now show that 4 tests completed OK.

And to finish, let’s now restart our application. If you try to do so and visit the URL, you might note that either the application won’t start or you get a Not Found. This is because we redefined the root object to be a Site and mapped the default view for that object to be interface-driven.

Thus, remove the ZODB files before restarting your application:

$ rm Data.*
$ paster serve FeedsTool.ini --reload

Note

The --reload is very handy. It tells the server to continously scan for code changes. Any time Python code changes on disk, the server automatically restarts.

With this in place, your browser should now show the following screen:

../../_images/home02.png

Table Of Contents

Previous topic

Preparation

Next topic

Add Forms and Persistence

This Page