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.
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:
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):
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']
|
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>
|
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.
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")
|
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.
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;"> </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>
|
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>
|
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>
|
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: