ServerZen.Net

Technology news centering around Python

Entries tagged "python"

Starting with Bazaar & bzr-svn

By now the benefits (and negatives) concerning DRCS systems has been talked about alot. Version control applications like git, bazaar, mercurial, and darcs, to name a few have all been getting much attention.

For me, the lure is being able to work on applications and maintain sensible revision history while working offline. Also working in a mode where I can toy in a local (non-public) branch and toss it away if it’s no good helps. Enter Bazaar.

Background Info

A little bit of info is required before we get underway.

bzr checkouts
Like with standard CRCS systems such as Subversion, a bzr checkout functions as a real-time mirror of the linked repository location. So once you’ve done a checkout, making changes and committing them is automatically performed to the linked repo. Working with checkouts directly is not something often done, but merging changes from other branches using a checkout is quite helpful.
bzr branches
Unlike with standard CRCS systems, in a DRCS like bzr branching is encouraged. All bug fixes, feature enhancements, etc, should be done on a separate branch and ultimately merged back to trunk when complete. bzr makes merging back by remembering when branches were merged last so re-merging won’t have any ill effects. Branches also support pushing and pulling allowing one branch to mirror another.

Getting Started

We’ll be performing our bzr tactics against a the publically available subversion repository of ClueBin. I’m using bzr-svn 0.4.10, bzr 1.5, svn 1.5 for all of this.

Begin by creating a local bzr repo for all ClueBin work (this speeds up bzr work and saves space) and make a checkout of the real svn trunks of the projects we care about.

$ bzr init-repo --rich-root-pack repo  # rich-root-pack required for svn stuff
$ cd repo
repo$ bzr co https://dev.serverzen.com/svn/cluemapper/ClueBin/trunk ClueBin-trunk
repo$ cd ..
$

Before we go any further, lets setup the python environment for actually running ClueBin. We use virtualenv (1.1) to setup the sandbox. Setting up virtualenv is beyond the scope of these writings.

$ virtualenv-py2.5 cluebin-app
$ source cluebin-app/bin/activate
(cluebin-app) $ cd repo
(cluebin-app) repo$ cd ClueBin-trunk
(cluebin-app) repo/ClueBin-trunk$ python setup.py develop
(cluebin-app) repo/ClueBin-trunk$ cd ..
(cluebin-app) repo$

Our First Feature Branch

Now that we have an environment setup with the trunks of all the projects activated, we want to branch for feature work.

(cluebin-app) repo$ bzr branch ClueBin-trunk ClueBin-purple-monster
(cluebin-app) repo$ cd ClueBin-purple-monster
(cluebin-app) repo/ClueBin-purple-monster$ python setup.py develop

At this point you make all of your changes, and do commits as you normally would (I recommend small isolated committing myself). Once you have completed your work for today, you should decide what you want to do with your branch. Is this branch something to be tossed away because you don’t want it? Then just remove it. Is this branch something you want to keep working on but you’re not done yet? If so, I suggest you mirror your branch back to the remote svn repo so that the changes aren’t just left on your own hard drive. Due to limitations in bzr itself, to make initial push back into svn (that auto-creates a new folder in svn) you have to use svn-push instead of regular push.

(cluebin-app) repo/ClueBin-purple-monster$ bzr svn-push https://dev.serverzen.com/svn/cluemapper/ClueBin/branches/purple-monster

Now that your branch has been mirrored you can work on your local branch, make more changes, and finally bzr push again when you’re done (this time no url needs to be specified because bzr remembers the last one).

(cluebin-app) repo/ClueBin-purple-monster$ bzr push

If you make changes to that svn branch on some other workstation and want to update your local branch, this is where you would use pull. But be warned this only works if your local branch has no additional changes that were not pushed back to the remote svn mirror branch.

(cluebin-app) repo/ClueBin-purple-monster$ bzr pull

If by some chance you (or someone else) had committed changes to both the local branch and the remove svn mirror branch, you would need to merge. But don’t fret the merge, this is where DVCS’ shine … they remember what was merged!

Merging to Trunk

A more common case of merging is when you’re ready to publish your purple monster changes to trunk for everyone else to see.

First off, you might want to see what changes were done to trunk that you don’t yet have in your purple monster branch. And for that, you want to update your checkout of trunk too.

(cluebin-app) repo/ClueBin-purple-monster$ bzr update
(cluebin-app) repo/ClueBin-purple-monster$ bzr missing ../ClueBin-trunk

You’ll get a list of revisions that haven’t yet been merged into your purple monster branch. If everything looks good, merge them in.

(cluebin-app) repo/ClueBin-purple-monster$ bzr merge ../ClueBin-trunk

Of course you’ll need to commit the changes from the merge (nothing is ever auto-committed).

(cluebin-app) repo/ClueBin-purple-monster$ bzr commit

Now that you know your branch is current with trunk, it’s time to merge your branch changes back to trunk. Remember that since the trunk branch is an actual checkout of trunk, committing directly to that branch will propagate changes right to svn.

(cluebin-app) repo/ClueBin-purple-monster$ cd ../Cluebin-trunk
(cluebin-app) repo/ClueBin-trunk$ bzr merge ../ClueBin-purple-monster

And assuming there are no conflicts to deal with, you can go ahead and commit. (conflicts are handled the same way as with svn or cvs)

(cluebin-app) repo/ClueBin-trunk$ bzr commit

And voila, trunk and purple-monster branch are now synced with one another.

Aug 9, 2008 9:07:00 AM by rocky, 0 comments

Using TextPress

So I’ve been spending a while looking at blog software. My preference was to continue using something Python-based so that I could troubleshoot and contribute to where time allowed. The last blog software that powered serverzen.net was Plone+Quills.

After some searching I came across TextPress by the Pocoo Team. In essence it is a WordPress clone but built with Python using WSGI technologies.

There wasn’t yet an actual release so I took a snapshot from their mercurial repository and built an egged release from it. Since this wasn’t immediately straight forward I’ve decided to package up what I’ve done and describe it here.

Setting Up TextPress

With the new packages I’ve setup, setting up a TextPress instance is quite easy (but please be warned that this is pre-alpha software not yet released by the maintainers).

Here are the steps to get started assuming a unix shell like bash.

  1. Start by creating an isolated environment using something like virtualenv. I won’t bore you with instructions on setting up virtualenv here.

    $ python virtualenv.py textpress-app
    $ source textpress-app/bin/activate
    (textpress-app)$
    
  2. Next step is to install the pre-packaged TextPress provided by me.

    (textpress-app)$ easy_install -i http://dist.serverzen.com/textpress/latest/simple/ TextPress
    
  1. Python2.4 users (Python2.5 users skip this step) will additionally need to install wsgiref and pysqlite:

    (textpress-app)$ easy_install wsgiref
    (textpress-app)$ easy_install pysqlite
    
  1. Now that the software is installed, lets setup a place to hold our actual blog instances.

    (textpress-app)$ mkdir blog-instances
    (textpress-app)$ cd blog-instances
    
  2. And now lets run our first blog instance.

    (textpress-app)$ mkdir myblog
    (textpress-app)$ TEXTPRESS_INSTANCE=myblog textpress-manage runserver
    
  3. After the server has been successfully started, use the web browser to configure the first blog instance by following the step-by-step wizard.

    1. choose your language (english for purposes of this discussion), “next”
    2. specify “sqlite:///database.db” as Database to store all data in the file “database.db” relative to the myblog directory, “next”
    3. fill in first user information (username, password, email) “next”
    4. and finally “install”

Your new test blog should be up and running. Check out the TextPress wiki for more information on how to use TextPress. But those familiar with WordPress should feel at home.

Update: The fact that this is pre-alpha software means that newer updates and releases might not migrate your data properly so some manual massaging may be necessary.

Aug 6, 2008 4:16:00 PM by rocky, 4 comments

Unit, Integration, and Functional Testing with Plone

Disclaimer
This is plone testing as thought through by the author which is, as everything, just an opinion.

Types of Tests

Unit tests
Testing done in as extremely minimal fashion as possible. Having some monstrous setup done before hand is not acceptable. These tests are done in a mind set by which the developer knows what the inner code looks like so he can test that certain inner code flags/values have been set after running the test.
Integration tests
Testing done very similar to unit tests (that is they’re all code based and don’t actually go through the browser interface) but require some integration to have been performed. In the case of Plone this often means having a complete plone site installed with all the trimmings. Basically these are unit tests on steriods. 95% of plone add-on developers write their tests these ways (the fastest way to see if a test is an integration test is to see if it uses PloneTestCase or ZopeTestCase … if it does, it’s an integration test). If a test requires a working portal instance, it’s an integration test.
Functional tests
Testing from as close to the real environment as possible, in most casee this means using something like selenium or testbrowser (I tend to use testbrowser). These tests never touch actual code api’s (other than to run the mock web browser).

An Example

Let’s say we have browser.py. Anyone familiar with the newer Zope 3 style way of coding applications is familar with the browser module. In this case, as expected, browser.py is giving us view classes that ultimately will get accessed via a web browser. Here’s how we would write different types of tests for that browser module.

But before we get into the actual tests, We need to begin by defining browser.py as follows:

from Products.Five.browser import BrowserView
from Products.CMFCore import utils as cmfutils

class SimpleView(BrowserView):
    """A simple view."""

    def nextval(self):
        portal = cmfutils.getToolByName(self.context, 'portal_url') \
                 .getPortalObject()
        current = getattr(portal, '_simpleview_count', 0)
        portal._simpleview_count = current + 1
        return portal._simpleview_count

    def __call__(self):
        return 'Retrieved %i' % self.nextval()

Also need configure.zcml:

<configure
  xmlns="http://namespaces.zope.org/zope"
  xmlns:browser="http://namespaces.zope.org/browser">

<browser:page
    name="simpleview"
    for="*"
    class=".browser.SimpleView"
    permission="zope.Public"
    />

</configure>
Unit test

In general no setup will be performed for this at all. Here the developer would simply import the browser module and instantiate the view classes using python code. And then with the view instance, test each of the methods. In general when any additional functionality is needed, it’s done in the form of mock objects.

Here’s what the test harness looks like (in this example, expected to live as tests/test_unit.py):

import unittest
from zope.testing import doctest

def test_suite():
    return unittest.TestSuite(doctest.DocFileSuite
                              ('unit-example.txt',
                               package='testingexample'))

Here’s the test (in doctest-style) as is expected to live in unit-example.txt:

First some mock objects.

    >>> class Mock(object):
    ...     def __init__(self, **kwargs):
    ...         for k, v in kwargs.items(): setattr(self, k, v)

    >>> portal = Mock()
    >>> context = Mock(portal_url=Mock(getPortalObject=lambda: portal))

We don't bother with request since we know the innards of our code and
the fact that it doesn't use the request for anything.

    >>> from testingexample.browser import SimpleView
    >>> view = SimpleView(context, None)
    >>> view.nextval()
    1
    >>> portal._simpleview_count
    1

And adjusting the private var manually will work as expected.

    >>> portal._simpleview_count = 50
    >>> view.nextval()
    51
    >>> portal._simpleview_count
    51

And then testing the string output of ``__call__``.

    >>> view()
    'Retrieved 52'
    >>> portal._simpleview_count
    52

The only way to see this break is if someone corrupted the _simpleview_count
value (which we should have to account for anyhow).

    >>> portal._simpleview_count = 'foobar'
    >>> view.nextval()
    Traceback (most recent call last):
    TypeError: cannot concatenate 'str' and 'int' objects
Integration test

Use a setUp that sets up a simple plone site and installs any plone add-ons we need. Use code like view = somecontentobj.restrictedTraverse(’@@someview’) to look up a view that is being created with browser.py and interact with that view component on an api level.

Here’s what the test harness looks like (in this example, expected to live as tests/test_integration.py):

import unittest
import testingexample
from Testing import ZopeTestCase
from Testing.ZopeTestCase.zopedoctest import ZopeDocFileSuite
from Products.PloneTestCase import PloneTestCase
from Products.PloneTestCase.layer import PloneSite
from Products.Five import zcml

PloneTestCase.setupPloneSite()

class MainTestCase(PloneTestCase.PloneTestCase):
    def afterSetUp(self):
        zcml.load_config('configure.zcml', testingexample)
        self.portal._simpleview_count = 0

def test_suite():
    suite = ZopeDocFileSuite('integration-example.txt',
                             package='testingexample',
                             test_class=MainTestCase)
    suite.layer = PloneSite

    return unittest.TestSuite((suite,))

And here’s the actual tests:

In these tests we expect that the portal object has already been setup
(ala ``PloneTestCase``) and is available as simply ``portal``.

    >>> portal
    <PloneSite at /plone>

Our first integration test just checks to make sure that we can actually
lookup the view by traversing.

    >>> view = portal.restrictedTraverse('@@simpleview')
    >>> view is not None
    True

Our view instance is already expected to have a working *context* and
*request* so we can continue as expected.

    >>> view.nextval()
    1
    >>> portal._simpleview_count
    1

And adjusting the private var manually will work as expected.

    >>> portal._simpleview_count = 50
    >>> view.nextval()
    51
    >>> portal._simpleview_count
    51

And then testing the string output of ``__call__``.

    >>> view()
    'Retrieved 52'
    >>> portal._simpleview_count
    52

The only way to see this break is if someone corrupted the _simpleview_count
value (which we should have to account for anyhow).

    >>> portal._simpleview_count = 'foobar'
    >>> view.nextval()
    Traceback (most recent call last):
    TypeError: cannot concatenate 'str' and 'int' objects
Functional test

Use a setUp that sets up a simple plone site and installs any plone add-ons we need. Instantiate a test browser instance (via zope.testbrowser) and mimick browser actions to “log into” the site and access whatever views were produced by browser.py.

Here’s what the test harness looks like (in this example, expected to live as tests/test_functional.py):

import unittest
import testingexample
from Testing import ZopeTestCase
from Testing.ZopeTestCase import FunctionalDocFileSuite
from Products.PloneTestCase import PloneTestCase
from Products.PloneTestCase.layer import PloneSite
from Products.Five import zcml

PloneTestCase.setupPloneSite()

class MainTestCase(PloneTestCase.PloneTestCase):
    def afterSetUp(self):
        zcml.load_config('configure.zcml', testingexample)
        self.portal._simpleview_count = 0

def test_suite():
    suite = FunctionalDocFileSuite('functional-example.txt',
                                   package='testingexample',
                                   test_class=MainTestCase)
    suite.layer = PloneSite

    return unittest.TestSuite((suite,))

And here’s the actual tests:

These tests are all about seeing and testing what the browser sees.  We
make no assumptions on the innards of the code -- pretending we have indeed
never seen the code itself.

First we need to setup a browser instance.

    >>> from Products.Five.testbrowser import Browser
    >>> browser = Browser()

Now we can start checking things out.  Really all we can test here now
is that the output to the browser has an integer that increments each time.

    >>> browser.open(portal.absolute_url()+'/@@simpleview')
    >>> browser.contents
    'Retrieved 1'

    >>> browser.open(portal.absolute_url()+'/@@simpleview')
    >>> browser.contents
    'Retrieved 2'

Seeing It In Use

All of the example code here can be found in the svn collective as an actual python package. Read the included README.txt to figure out how to set it up in your own zope instance. The package is available at: http://svn.plone.org/svn/collective/examples/testingexample/tags/rocky-blog-post-20070919/

The tests are meant to be run with:

$ ./bin/zopectl test -s testingexample

But don’t forget that when developing code you can save yourself a ton of time by maintaining 100% unit test coverage. Then you can run the unit tests as often as you want (at a very rapid speed) and only run the integration and/or functional tests at milestone intervals. To run them separately you would do:

$ ./bin/zopectl test -m testingexample.tests.test_unit
$ ./bin/zopectl test -m testingexample.tests.test_integration
$ ./bin/zopectl test -m testingexample.tests.test_functional

Conclusion

Testing is great. I’m not particularly advocating test driven development (it works for some people, but other people it does not). But it’s important to understand the differences between the different types of tests. The author suggests maintaining 100% unit test coverage and some (client-derived) acceptable amount of functional tests. Integration tests aren’t so important when you already have good unit and functional test coverage.

Sep 19, 2007 2:17:00 PM by rocky, 0 comments

Zope 3 in Zope 2 Gotcha's: Unicode

Viewlets

A viewlet’s render() method needs to return unicode objects because the standard zope3 viewlet implementation concatenates the output of various viewlet render()‘s. If the output is a str, then implicit conversion-to-unicode will happen using the default python character encoding which is most often ascii (causing a utf-8 encoded str with non-ascii chars to blow up).

Archetypes Schema Fields

Archetypes (as present in Plone 2.5 and 3.0, and possibly older) stores all StringField fields as utf-8 encoded str’s irrelevant of what the Plone site encoding has been configured as. Safest way to handle this is to always feed StringField field mutators unicode objects instead of str’s. And upon retrieval, be prepared to decode the str’s using utf-8 (ie context.Title().decode(’utf-8′) => u’someval’). Remember, StringField accessors will always return utf-8 encoded str’s and Zope 3 often makes assumptions that the string values it’s dealing with are unicode objects.

Summing Up

Use unicode objects everywhere. If some Zope 2 / Plone API forces you to use str‘s or get str‘s convert them to unicode‘s before doing anything else.

Aug 28, 2007 9:56:00 AM by rocky, 0 comments

Keeping Client Concerns Separate

The basic skills learned in using the Zope Component Architecture to keep client concerns separate are as follows:

  1. Identify generic reusable portion of business logic required to satisfy client needs
  2. Construct components implementing generic reusable business logic that use adapters or utilities to provide functionality
  3. Provide a default set of adapters or utilities for implementing the generic reusable business logic
  4. Provide client-specific adapters and utilities that provide the client-specific non-reusable business logic in an external client-specific product

What follows is a simple example of utilizing the Zope Component Architecture to keep the required client customizations separate from the reusable portion of your application.

Feedfeeder

I recently worked on a Zope 2 product for Zest Software called feedfeeder that would use the feedparser python library to create content items within a Plone site. The basic premise was that there was a folderish content type that could be assigned URL’s to ATOM feeds. Then there was an action (a simple Zope 3 view) made available on the folder that when called would pull down the ATOM feeds and hand the content over to a Zope 3 utility. That utility would then create the appropriate content representing the ATOM entries.

This was all find and dandy for handling generic ATOM feeds. The problem is that the client had specific entry metadata they wanted to make available for each ATOM entry. Of course we didn’t want to extend the feedfeeder product to take into account the client specific data so we had to figure out an acceptable manner of extending it.

Using Microformats

While searching for a way to enhance an ATOM feed with custom metadata, Reinout Van Rees stumbled across the concept of microformats. Basically in this case a microformat would be a way to read and write metadata that fits within the boundaries of an existing format.

Lets take a simple standard ATOM entry as an example:

<entry>
  <title>Bicycle service closed 17 July-7 August</title>
  <id>http://somecustomer.com/someuid</id>
  <content type="xhtml">
    <div xmlns="http://www.w3.org/1999/xhtml">
Here lies the body of the atom entry.
    </div>
 </content>
 <updated>2006-07-18T12:00:00Z</updated>
 <author><name>Joe Schmoe</name></author>
</entry>

Now the type of content can be any valid non-multipart mime type. In the case of XHTML and a few others you can even drop the first part of the mime type name (the text/ portion). One of the most common types is XHTML. So this means for us a microformat would have to live sensibly as any valid type for the content tag.

We decided to go ahead and describe a microformat that would be valid XHTML so that in the case of a feed parser that didn’t know what to do with our microformat, it would still be decent looking XHTML. So how would we describe metadata structurally using XHTML? Easy, with definition lists.

<dl>
  <dt>somename</dt>
  <dd>somevalue</dd>
</dl>

In the case of the client’s specific metadata, we needed to send over a special date. So our new ATOM entry would look like this:

<entry>
  <title>Bicycle service closed 17 July-7 August</title>
  <id>http://somecustomer.com/someuid</id>
  <content type="xhtml">
    <div xmlns="http://www.w3.org/1999/xhtml" class="colloquia">
      <dl>
        <dt>date</dt>
        <dd>2006-03-23</dd>
      </dl>
      <div>
Here lies the body of the atom entry.
      </div>
    </div>
 </content>
 <updated>2006-07-18T12:00:00Z</updated>
 <author><name>Joe Schmoe</name></author>
</entry>

So we did three things:

  1. Introduced new DL tag for describing metadata
  2. Moved actual content body to a sub DIV tag
  3. Added a new class attribute to the toplevel DIV tag to indicate the class or type of metadata

The next step in this process is to give feedfeeder a generic way of understanding this microformat.

Extending Feedfeeder

The question we’re left to answer is:
How do we provide a replaceable mechanism in feedfeeder for handling the metadata?
And the answer is:
Abstract the content handling logic to lookup up an adapter to deal with the content.

So since we knew our microformat had to use the XHTML type, within the feedfeeder code for handling the content we would only do our thing if the type was XHTML.

  1. Check the content type, is it XHTML?
  2. If the answer is “yes”, proceed to step #4
  3. If the answer is “no”, simply go about your default business
  4. Since it’s XML (XHTML), we use the standard python library package, xml.dom.minidom, to handle the body as a DOM fragment
  5. Next we look up the class attribute on the toplevel DIV tag within the DOM fragment
  6. Do a named adapter lookup providing IFeedItemContentHandler using the class attribute as the name
  7. Did we find an adapter? If so, we call it’s handling function passing in the DOM fragment
  8. We didn’t find an adapter? Then lookup the default unnamed adapter providing IFeedItemContentHandler and pass it the DOM fragement

Extending Client Product

So now we’ve given our feedfeeder product way of extending it’s content handling without the feedfeeder ever needing to know the specific things that could be done by external specific code.

What next? We go inside the client product implementing specific client business logic and provide a named adapter that provides IFeedItemContentHandler. In this case the type of metadata we want to handle is referred to as Colloquia. Now we’ll go ahead and make the name of the adapter be “colloquia”. Here’s an example of the ZCML used to register the adapter:

<adapter
    provides="Products.feedfeeder.interfaces.contenthandler.IFeedItemContentHandler"
    for="Products.feedfeeder.interfaces.item.IFeedItem"
    name="colloquia"
    factory=".colloquia.ColloquiaContentHandler"
    />

With the actual ColloquiaContentHandler class we simply provide the appropriate handling function that will now take the DOM fragment and parse the toplevel DL tag to determine what metadata is available.

Annotations

Annotations in the Zope Component Architecture are a way of marking up arbitrary content with arbitrary metadata. In this case we will use annotations to mark up the created content items from the ATOM feed. In the case of our IFeedItemContentHandler adapter we will use annotations to apply our colloquia metadata to the content items.

Here is some code demonstrating annotation handling.

# make sure the content item implements IAttributeAnnotatable,
# otherwise we won't be able to use annotations on it
if not IAttributeAnnotatable.providedBy(self.context):
    directly = zope.interface.directlyProvidedBy(self.context)
    zope.interface.directlyProvides(self.context,
                                    directly + IAttributeAnnotatable)
annotations = IAnnotations(self.context)
metadata = annotations.get('colloquia', None)
if metadata is None:
    metadata = PersistentDict()
    annotations['colloquia'] = metadata

# and what we're left with is a simple dict referenced by the
# variable, metadata

Now that we have a dict to store our metadata in, we simply parse the DL tag definitions and populate our metadata dict.

for dl_el in contentNode.childNodes:
    if dl_el.nodeName != 'dl':
        continue

    term = None
    for el in dl_el.childNodes:
        if el.nodeName == 'dt':
            term = self._extractText(el)
        elif el.nodeName == 'dd':
            definition = self._extractText(el)
            metadata[term] = definition

And voila, we have our extra client-specific metadata being stored on the content item. Of course it’s then up to the client product to pull out that metadata in custom views and such. In fact we actually took this further by overriding the default view of the feedfeeder (in the client specific product) and displayed the metadata in a client-specific manner.

Sep 17, 2006 9:56:00 AM by rocky, 0 comments

Using zope.formlib With Plone: Part 3

The second part in this series focused on demonstrating how a search form could be implemented with minimal fuss. This third part will show how to setup a content type using a Zope 3 schema which will be used to setup formlib based view and edit forms.

Traditional Archetypes Schema’s

By now the entire Plone and Plone add-on developer communities generally use the Archetypes framework to construct their content types. The Archetypes framework introduced its own concepts of schema’s, widgets, and fields which suited most developers fine. The problem is that shortly thereafter (perhaps even simultaneously) the Zope 3 community was already building their own constructs of these same concepts. The result was zope.interface, zope.schema, and zope.app.form. Eventually came zope.formlib that would help glue more of this together in a web UI environment.

Zope 3 Schema’s

A Zope 3 schema functions very closely to an Archetypes schema from a conceptual perspective. They both share the basic principles of defining the fields of a content type. One glaring difference is that is that a Zope 3 schema field has no direct association (within the schema) with any sort of UI logic. That is, it does not know what widget will be used to display itself on a web form. This help keeps content and UI concerns separate. Content, after all, should never need to know or care what it will actually look like in a user interface. It only cares about, well, data.

Our Example

For purposes of keeping things clean, we will break up some of our previous example code into more common python modules. This means moving our ISearch interface from the browser module into a new interfaces module.

A Content Schema

In Zope 3, schema’s are defined by constructing Zope 3 interfaces, much like the interface we created in the second part. What wasn’t made clear in the second part was that we actually created a schema to represent the search fields.

Lets begin by creating a new interface in the interfaces module called IExampleContent. This interface will have various fields and should look like this:

class IExampleContent(interface.Interface):
    title = schema.TextLine(title=u'Title',
                            required=True)

    description = schema.Text(title=u'Description',
                              required=False)

    funny = schema.Bool(title=u'Am I Funny?',
                        default=False,
                        required=True)

    really_funny = schema.Bool(title=u'Am I Really and Truly Funny?',
                               default=False,
                               required=True)

You can look at the resulting interfaces module to see what this file should end up looking like.

As mentioned earlier this example demonstrates that no UI properties are made available on the schema itself. All that is described about the schema and its fields is what is required to define the type of data that it represents.

Formlib Views

Now that we have the schema that describes what fields our content type will eventually have, we can use the same basic approach that the second part used to build views for this content.

The schema we just defined sets up a couple fields as being of type Bool. The default widget for a Bool field displays true/false as the selectable options. For purposes of our example, we need these to show yes/no instead so we’ll setup our own tweaked custom widget for this.

def YesNoWidget(field, request, true=_('yes'), false=_('no')):
    vocabulary = schemavocab.SimpleVocabulary.fromItems(((true, True),
                                                         (false, False)))
    return form_browser.RadioWidget(field, vocabulary, request)

What we have created here is a type of factory that will give us the widget we need based on an existing widget, RadioWidget. RadioWidget takes a vocabulary which we setup as having yes and no as items. Next we will define our form fields based on our content schema.

example_content_fields = form.FormFields(interfaces.IExampleContent)
example_content_fields['really_funny'].custom_widget = YesNoWidget

As with our example in the second part we generate the form fields using form.FormFields(). This class takes an interface as an argument and generates the form fields (the UI complement of a schema’s fields) we will use for our views. The second line here sets up our really_funny schema field to use our own custom widget.

All that’s left now is to actually define the default view and edit form for our content type.

class ExampleContentView(formbase.DisplayForm):
    form_fields = example_content_fields

    def __init__(self, *args, **kwargs):
        formbase.DisplayForm.__init__(self, *args, **kwargs)

        # a hack to make the content tab work
        self.template.getId = lambda: 'index.html'

class ExampleContentEditForm(formbase.EditForm):
    form_fields = example_content_fields

    def __init__(self, *args, **kwargs):
        formbase.EditForm.__init__(self, *args, **kwargs)

        # a hack to make the content tab work
        self.template.getId = lambda: 'edit.html'

Pretty basic. We defined ExampleContentView to extend formbase.DisplayForm which means formlib will understand that this is a regular display view so all widgets should be displayed in view mode. The edit form, ExampleContentEditForm was defined to extend formbase.EditForm so that formlib would know to display the widgets in edit mode. But this isn’t the only thing that formlib knows to do with the edit form. By default, formbase.EditForm defines a single apply action for its form. When this form is submitted with the apply action, formlib knows that it must actually update the current object (ie context) with the submitted values. And for those adventurous types, it should also be observed that a successfully submitted apply action will fire an IObjectModifiedEvent upon saving the submitted data.

Of course once these views have been defined they need to be registered with the Zope 3 component architecture. This is done with configure.zcml.

<browser:page
    name="index.html"
    for=".interfaces.IExampleContent"
    class=".browser.ExampleContentView"
    permission="zope2.View"
    />

<browser:page
    name="edit.html"
    for=".interfaces.IExampleContent"
    class=".browser.ExampleContentEditForm"
    permission="cmf.ModifyPortalContent"
    />

This zcml snippet shows that we have given our default view the name, index.html and our edit form the name, edit.html.

You can look at the browser module and configure.zcml to see the end result.

Content Type

So now that the schema and views have all been defined for our content type, its time to build the actual content type class. As a matter of best practise, we define this class in the content module.

class FormlibExampleContent(atapi.BaseContent):
    interface.implements(interfaces.IExampleContent)

    title = fieldproperty.FieldProperty(interfaces.IExampleContent['title'])
    description = fieldproperty.FieldProperty(interfaces.IExampleContent['description'])
    funny = fieldproperty.FieldProperty(interfaces.IExampleContent['funny'])
    really_funny = fieldproperty.FieldProperty(interfaces.IExampleContent['really_funny'])

The first line after the class statement ensures that our new content class implements the IExampleContent interface which we defined in the interfaces module earlier (a schema is just an interface with zope.schema fields). The remaining lines setup python properties for each of the required fields. As an example, the funny line says to define a funny attribute that is modelled after the funny schema field in IExampleContent). This ensures some basic validation is setup on the content type itself. If someone where to have an instance of this content type with name, myobj and tried to do myobj.funny = ‘foo’ then a validation error would be raised because the value ‘foo’ is not of type Bool. Bool types expect true or false.

That’s it for the content type class itself. Take a look at the finished content module to see the end result.

Content Type Cataloguing

One neglected aspect of all of this is since we’re no longer using Archetypes auto-generated forms our content is no longer getting catalogued. In the world of Zope 3 such things would be accomplished by using events. Since formlib will fire off an IObjectModifiedEvent when a successful save has taken place, all that is left to us is to define a handler for that event.

def catalog_content(obj, event):
    obj.reindexObject()

The handler itself is quite simple, it takes as arguments the actual object and the event (in this case the IObjectModifiedEvent instance). Since we have the object in hand at this point, we merely call reindexObject().

Of course we still have to hook this up with the Zope 3 component architecture which takes us back to configure.zcml.

<subscriber
    for=".interfaces.IExampleContent
         zope.app.event.interfaces.IObjectModifiedEvent"
    handler=".content.catalog_content"
    />

This basically says we want to handle any IObjectModifiedEvent that has been fired with an instance of IExampleContent as the target object. In our case, FormlibExampleContent implements the IExampleContent interface so we know our handler will get called when it has been modified.

The only thing that remains now is to register our content type with CMF/GenericSetup.

Content Type Installation With GenericSetup

Since we’re using Plone 2.5 with these examples we will use the new GenericSetup tool to setup our new content type in a Plone site. More information specifically about GenericSetup and Plone can be read in Rob Miller’s excellent Understanding and Using GenericSetup in Plone tutorial.

Basic steps for setting up our content type with GenericSetup are:

  1. Create a profile directory structure that has profiles/default/types beneath our formlib directory.
  2. Construct a new types.xml file underneath the default directory.
  3. Create a file with the name FormlibExampleContent.xml underneath the types directory.
  4. Register a new extension profile that uses these files with GenericSetup in our main __init__.py file.

Actual construction of these files goes beyond the scope of this writing, but the contents of these files can be found starting at the base formlib directory.

Just remember to activate these content types in Plone 2.5 you would go to the portal_setup tool, select ploneexample.formlib sample content as the active site configuration, and then run all import steps.

Conclusion

Zope 3 schema’s in Plone has arrived. And thanks to zope.formlib we have auto-generated views and forms to boot. The widget and field sets to choose from in Zope 3 are a lot smaller than in the Archetypes world, but Zope 3 is quickly catching up.

The only main missing piece (from formlib’s perspective) here is using an auto-generated add form. While we can build those, they can’t easily be hooked into Plone as Plone feels it needs to create the content first before displaying any forms.

Jul 9, 2006 9:14:00 PM by rocky, 0 comments

A Call For Removal: Custom Zope Product Test Runners

We’ve had the standard zope test runner with ‘zopectl test’ for a while now. But a lot (most?) of existing third-party products distribute their own runalltests.py and related files. This means as a contributor to any of these products a person has to make sure the tests run properly with runalltests.py and zopectl test which unfortunately often doesn’t run in the same manner.

So I put it out there that everyone stop providing their own custom test runners with their products. In fact, remove custom test runners that in your source control trunks of your products. We have the standard Zope test runner, lets just use that.

If someone doesn’t like the output or some other artifact of the standard Zope test runner, they should define their own local test runner, but don’t force everyone else to use it. And still be sure your tests run properly with the standard Zope test runner.

Jun 28, 2006 9:49:00 AM by rocky, 1 comment

Using zope.formlib With Plone: Part 2

The first part in this series focused on getting a good consistent package skeleton in place. This second part will attempt to do something useful with formlib in a Plone environment.

Understanding Formlib

zope.formlib is a Zope 3 package designed to ease the development of web-based forms in your Zope applications. In its simplest form you can compare what it does with the auto-generated displays Archetypes provides you for viewing a content type (base_view) and editing a content type (base_edit). For all practical sense formlib based components are really regular Zope view components with some convenient base classes for auto-generating output based on schema’s and other configuration info.

Thankfully beginning with Zope 2.9.3 zope.formlib is now being distributed with Zope 2. Of course Five >= 1.4 is required to make use of this Zope 3 package.

Defining Our First Form

For purposes of this writing we will construct a very simple search form for searching Plone content. This form will be similar to Plone’s built in advanced search form but much simpler.

You can view the working source code of these examples at the updated collective svn browser and updated collective svn repository locations.

The Form Class

We begin by creating a new file, browser.py, which will need to live in ploneexample.formlib/ploneexample/formlib/. The browser.py file will comprise the bulk of the necessary work. Lets start by adding the necessary imports.

from zope import interface, schema
from zope.formlib import form
from Products.CMFCore import utils as cmfutils
from Products.Five.browser import pagetemplatefile
from Products.Five.formlib import formbase

Next we’ll construct our first Zope 3 interface:

class ISearch(interface.Interface):
    text = schema.TextLine(title=u'Search Text',
                           description=u'The text to search for',
                           required=False)

    description = schema.TextLine(title=u'Description',
                                  required=False)

The purpose of the interface in this case is not to describe a particular content object but instead to define the fields that formlib will use. Later on we’ll discover how tradtional interfaces used to describe actual content classes can be used in combination with formlib to autogenerate proper add and edit forms for content.

And now for the form view class itself. We will start with the first part of the class definition.

class SearchForm(formbase.PageForm):
    form_fields = form.FormFields(ISearch)
    result_template = pagetemplatefile.ZopeTwoPageTemplateFile('search-results.pt')

We use the PageForm class as our super class to inherit functionality from formlib itself. By default, PageForm knows how to generate all the HTML that will make up of our finished form. But in order to do this, formlib needs to know what fields we want. We do this by providing the form_fields attribute. FormFields is a formlib helper class that generates the appropriate field items from any Zope 3 schema (in this case, the schema interface we just defined).

The result_template attribute defines a new page template that we will use to iterate over all of the results of our search.

Next we define an action for our form:

@form.action("search")
def action_search(self, action, data):
    catalog = cmfutils.getToolByName(self.context, 'portal_catalog')

    kwargs = {}
    if data['text']:
        kwargs['SearchableText'] = data['text']
    if data['description']:
        kwargs['description'] = data['description']

    self.search_results = catalog(**kwargs)
    self.search_results_count = len(self.search_results)
    return self.result_template()

This is where the real work takes place. A formlib action is generally a handler that will somehow get invoked by submitting an HTML form. In this case we create a new action labeled search, that will be used to handle when a user hits the search button. Our formlib-based class will automatically understand how to hook in an search button on the HTML form itself. This particular action handler will return our result template as a result.

The Result Page Template

In order to display the results of our search form we need to setup a simple page template. We will name this template, search-results.pt. Most the of template is pretty uninteresting. But for purposes of this writing we will demonstrate the result printing portion.

<tal:block tal:repeat="single view/search_results">
<div class="single-result">
  <h4>
    <span tal:replace="repeat/single/number"></span>.
    <a tal:content="single/Title" tal:attributes="href single/getURL" href=""></a>
  </h4>
  <p tal:content="single/Description"></p>
</div>
</tal:block>

Since our previous formlib based class was a regular view, it gets treated that way inside the page templates. And we are able to assign simple attributes to our view that can get picked up within the template.

Tying It All Together With ZCML

Now that we’ve defined the form class and the result page template to go along with it we need to glue this all into Zope. We do this in configure.zcml.

So we need to add the appropriate ZCML snippet:

<browser:page
    name="search.html"
    for="Products.CMFPlone.Portal.PloneSite"
    class=".browser.SearchForm"
    permission="zope.Public"
    />

Again, since formlib is all based on regular Zope 3 view components, we register them the same way in the ZCML.

Our First zope.formlib Example In Summary

The example demonstrated here shows the simplest form that could be created with formlib and how to hook in a simple action. It should be obvious from this example how you could use formlib to replace simple CMFFormController based logic. Of course formlib can do many other advanced things such as provide sub-form functionality and autogenerated add and edit forms for content classes.

The bottom line is that zope.formlib is ready for use inside Plone today. And since formlib is so easy to work with, the author recommends all Plone application developers give it a try.

Jun 13, 2006 7:13:00 AM by rocky, 2 comments

Using zope.formlib With Plone: Part 1

The primary purpose of this writing is to display the use of the Zope 3 technology, zope.formlib, in a Zope 2 + Plone based environment. A side goal is to help demonstrate some new practices with building new Plone based applications.

Part 1 specfically focuses on getting the initial skeleton product in place.

Required Stack Components

The example built by this writing is designed to run on the following stack:

  • Python >= 2.4.3
  • Zope >= 2.9.3
  • Plone >= 2.5
  • Five >= 1.4

The example described in this writing may work on different versions but is untested by the author so your mileage may vary.

Some Things To Note Before Starting

We will begin by working with a product by the name of ploneexample.formlib. The finished code for this example can be browsed using the collective svn browser or checked out from Subversion using at the collective svn repository.

The first thing the keen Plone application developer will notice is the unusual naming convention of the product. Traditionally Plone products use CamelCase as the convention for naming products with no additional periods (ie PloneExampleFormlib). But as one of the goals of this writing, best practices will be demonstrated where we try to be as pythonic as possible which means using all lowercase for package names.

The Importance Of Pythonic

Many of you may be asking yourself, “but why do we really care about keeping this pythonic?” The single biggest reason for keeping things pythonic is to keep things as simple and easy to understand for people who are already familiar with Python. When developing standard Zope 2 products and placing them into the Products directory of your Zope 2 instance, Zope magically places them within the Products namespace package (ie so the full package path of CMFPlone actually becomes Products.CMFPlone). Living in PYTHONPATH like any other package and being reusable in general is A Good Thing TM.

Also, by keeping products pythonic, we can now use generic Python tools such as easy_install and setuptools (both great package management components) to work with these products.

Creating The Product

For the initial creation of the product we will use a small component of the Python Paste project. The rest of this writing will assume the reader has installed Phillip J. Eby’s setuptools and easy_install products. As well as the necessary paster component along with the ZopeSkel template package. For instructions on how to setup paster and ZopeSkel, please see Daniel Nouri’s excellent article on ZopeSkel.

So lets get started:

$ paster create -t plone_core ploneexample.formlib
Selected and implied templates:
  ZopeSkel#plone_core  A Plone Core project

Variables:
  package:  ploneexampleformlib
  project:  ploneexample.formlib
Creating template plone_core
Enter namespace_package (Namespace package) ['plone']: ploneexample
Enter package (The package contained namespace package (like i18n)) ['']: formlib
Enter pythonproducts (Are you making a productsless Zope 2 Product?) [False]: True
Enter version (Version) ['0.1']:
Enter description (One-line description of the package) ['']: A Plone product for demonstrating zope.formlib usage
Enter long_description (Multi-line description (in reST)) ['']:
Enter author (Author name) ['Plone Foundation']: Rocky Burt
Enter author_email (Author email) ['plone-developers@lists.sourceforge.net']: rocky@serverzen.com
Enter keywords (Space-separated keywords/tags) ['']:
Enter url (URL of homepage) ['http://svn.plone.org/svn/plone/plone.i18n']: http://dev.plone.org/collective/browser/examples/ploneexample.formlib
Enter license_name (License name) ['GPL']:
Enter zip_safe (True/False: if the package can be distributed as a .zip file) [False]:
  Recursing into +namespace_package+
    Creating ./ploneexample.formlib/ploneexample/
    Recursing into +package+
      Creating ./ploneexample.formlib/ploneexample/formlib/
      Copying HISTORY.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/HISTORY.txt
      Copying LICENSE.GPL to ./ploneexample.formlib/ploneexample/formlib/LICENSE.GPL
      Copying LICENSE.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/LICENSE.txt
      Copying README.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/README.txt
      Copying __init__.py_tmpl to ./ploneexample.formlib/ploneexample/formlib/__init__.py
      Copying configure.zcml to ./ploneexample.formlib/ploneexample/formlib/configure.zcml
      Copying version.txt_tmpl to ./ploneexample.formlib/ploneexample/formlib/version.txt
    Copying __init__.py_tmpl to ./ploneexample.formlib/ploneexample/__init__.py
  Copying setup.cfg to ./ploneexample.formlib/setup.cfg
  Copying setup.py_tmpl to ./ploneexample.formlib/setup.py
Running /usr/bin/python2.4 setup.py egg_info

Setting Up Development

After this has completed running you should now have a ploneexample.formlib directory in your current working directory. For purposes of development we will now use setuptools to setup the ploneexample.formlib project on the PYTHONPATH in order to make it available to Zope. So make sure you make the ploneexample.formlib directory your current directory and do the following (remember to run this with sudo if you’re in a UNIX environment and don’t have permission to write to the system python site-packages directory):

$ python2.4 setup.py develop

This will yield some output similar to the following:

[snip output...]
Installed /home/rocky/Documents/developing/projects/ploneexample.formlib
Processing dependencies for ploneexample.formlib==0.1dev

Hooking Up The New Product Into Zope 2

Now that we have a fully runnable Zope 2 product we will proceed with hooking this up to an existing Zope 2 instance. Until Zope and CMF/Plone catch up (Zope 2.10 already includes the necessary changes) we will need to use the pythonproducts product to enable Zope 2 products to exist outside of the instance Products directory.

So be sure to download and install the pythonproducts product into your Zope 2 instance by following the install instructions provided by pythonproducts. For those in a hurry, setting up pythonproducts is as simple as downloading the pythonproducts tarball, extracting the contents into some temporary directory, and running python setup.py install –home $INSTANCE_HOME.

Now you need to tell your Zope 2 instance to “activate” the new ploneexample.formlib package as a Zope 2 product. You do this by going to the etc/package-includes directory in your Zope 2 instance and create a new file there called ploneexample.formlib-configure.zcml with its only contents being:

<include package="ploneexample.formlib" />

You should now test your Zope 2 instance to confirm that the new ploneexample.formlib product is available. You can do this by starting your Zope 2 instance as you normally would and going to the Products section of the Control Panel via the ZMI. Towards the bottom of the list you should see:

ploneexample.formlib (Installed product ploneexample.formlib)

Stay tuned for the next part of this series!

Jun 8, 2006 9:15:00 PM by rocky, 2 comments

Project Management, eXtreme Style!

The 1.0 final release of eXtremeManagement (hereafter called xm in this entry) has been thrust upon us today. For those unaware, xm is a project management tool for Plone that follows eXtreme programming practises.

The Theory Behind XM

The big idea behind xm is to break up tasks into organized components that can be managed in a sane manner and then to ensure these components are worked on together by both the customer and the project team. And of course provide organization on a per-project basis.

The three types of components are:
  1. Tasks
  2. Stories
  3. Iterations

Tasks

Starting with the more fine grained component, tasks are basically todo items. The important thing is that tasks are kept relatively small and well-defined. Individual tasks get assigned to persons for completion. And it is the responsibility of the person(s) assigned to the task to estimate how long the task will take (there is a field for this).

Stories

Stories are a way of grouping tasks in a logical fashion. Stories should always include overview text of the entire “feature” or piece that needs to be built and often times when a person is working on a task included in that story they will have to refer to the story text itself.

Iterations

Iterations are what would often be referred to as phases or milestones. The customer and project manager/team decide together what stories can be reasonably completed in a certain timeframe and those stories are assigned to the given iteration. Stories that have been decided upon but not yet assigned to any particular timeframe (for example, the client has to get further budget approval) can be left outside of any iteration to be organized later.

For those of you with project management requirements, the author highly recommends (based on active experience) taking a strong look at the eXtrememManagement tool. Even if eXtreme programming techniques are not desired, using xm as a general-purpose project management tool can make projects much more manageable.

Feb 27, 2006 10:59:00 AM by rocky, 0 comments