Personal tools
You are here: Home
Document Actions

News About the Server

Wed Sep 19, 2007

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.

Tue Aug 28, 2007

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.

Sun Sep 17, 2006

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.

Sun Jul 09, 2006

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.

Wed Jun 28, 2006

A Call For Removal: Custom Zope Product Test Runners

Filed Under:

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.

Sat Jun 10, 2006

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.

Thu Jun 08, 2006

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!

Mon Feb 27, 2006

Project Management, eXtreme Style!

Filed Under:

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.

Fri Feb 03, 2006

Plone And Zope 3

Filed Under:

For a little while now Plone development has been moving towards Zope 3 development styles, conventions, and techniques via the Five product on Zope 2. There is much documentation available on the internet today for following this path (at least from the perspective of Zope 3). One of the largest benefits of this is being able to use the Zope 3 Component Architecture (CA) which promises to provide us with more manageable and reusable components.

The repercussions of such a system is that developers have a more robust framework for developing applications and when components are developed by one developer, there is a higher chance that this component can be reused by another developer in another project (and of course by the same developer in other projects).

Business Impact

Benefits

Of course further development, refactoring, and refining the base architecture generally provides a more robust solution to such an extent that businesses have to spend less time and effort managing and supporting deployed solutions. Having reusable components means less time spent developing new components and more time spent plugging in existing components which proves to take less time and thus less expense.

Risks

Migration of existing sites can get quite complicated. Also having to deal with switching frameworks such as making it difficult to maintain backwards compatibility with third-party products. Of course this gets easier as more and more third-party products adopt the same Zope 3 develop techniques which means education of this process moving forward is vitally important.

Random Non-Plone Impacts

From the perspective of a traditional Zope 2 developer deciding whether a new project should be developed and deployed on Zope 2 VS Zope 3 there are two different types of people:

  1. Zope 2 developer who is accustomed and familiar with Zope 2 development techniques with no interest to move outside of the Zope 2 development model.
  2. Zope 2 developer who is interested in Zope 3 development techniques but due to missing functionality (Zope 2 has obviously been around a lot longer than Zope 3) in Zope 3 has to develop and deploy on Zope 2.

Obviously the move to Zope 3 development techniques for #1 Zope developer doesn't make any sense. But developer #2 would be very interested in getting equivalent (in capability) functionality from Zope 2 into Zope 3. And once enough of the required Zope 2 functionality has found a capable equivalent in Zope 3, #2 developers will have a path to fully migrate to developing and deploying on Zope 3.

In order for a full set of Plone components to deploy and run on Zope 3 a lot of this traditional Zope 2 functionality will have to have equivalents on Zope 3. So in the process of providing this functionality for Plone on Zope 3, Zope 2 developers will have much of their wanted/needed functionality migrated as well. This means non-Plone Zope 2 developers will have a higher likelihood of being able to deploy on Zope 3.

Conclusion

It is the recommendation of this author that moving to Zope 3 development techniques is a required manner of evolution for Plone and the Plone software stack (which is in agreement with the Goldegg effort of course). And related to this, the development process and attitude should be to build Zope 3 components on Zope 3 and build them so they (for the some finite time period) can be deployed on Zope 2 until the convergence of Zope 2 and Zope 3 is complete.

Sat Jan 07, 2006

CMF With Zope 3 on Zope 2

Zope 3 encompasses a lot of functionality in its core. In fact, Zope 3 tries to include the required pieces of what in Zope 2 is called CMF. So in order to realize certain Zope 3 style functionality in Zope 2 (via Five) CMF needs to be used.

The Zope 2 product created to enable Zope 3 functionality using Zope 2 + CMF is called CMFonFive. According to the CMFonFive website, CMFonFive is:

... a product that provides integration between CMF and Five, making it possible to write Five products that run on CMF.

CMFonFive Functionality

It used to be that CMFonFive provided a fair amount of functionality when building Zope 3 / Five applications with CMF. These days most of that functionality has been integrated directly into CMF. As of CMF 1.5.2, CMF includes both the interface work and standard macros pieces that CMFonFive used to provide. At this point this means the only functionality that is left specific to CMFonFive is the bridge between the Zope 3 menu and CMF actions facilities.

Zope 3 Menu Facilities

A menu in Zope 3 is analogous to menu's in standard desktop applications. Such menu's are generally built to provide categorized actions available to the running application and are often context sensitive (ie the menu will change shape, deactivate items, etc when the application changes what data is being viewed).

In Zope 3, menu's are registered as utilities. There are a few specific core Zope 3 menu's that are registered on startup including (but not limited to) zmi_views and zmi_actions. These specific menu's are used to display actions available in the Zope 3 ZMI [*]. Its easy enough to add your own actions to these menu's by defining a browser menu item declaration such as:

<browser:menuItem
    for="some.interface.IHere"
    menu="zmi_views"
    title="Preview"
    action="preview.html"
    permission="zope.ManageContent"
    />

CMF and Menu's

Since Zope 2 (by default) doesn't provide any real notion of categorized actions its left up to CMF to pick up the slack. CMF has the portal_actions tool and related API which brings a concept of categorized actions to Zope 2. Some of the categories that CMF provides by default are object, user, and folder. The standard way to add new actions to those categories is to either go to the ZMI and add new actions to one of the tools there (filling in the appropriate 'Category' field) or by (in code) creating a new CMF tool that provides its own set of actions.

CMFonFive Bridges CMF Actions and Zope 3 Menu's

CMFonFive has its own CMF tool that implements the CMF actions interfaces and translates Zope 3 defined menu's and menu items into CMF action categories and actions. So, to get a new "user" action to show up in the CMF UI, you would do the following in ZCML:

<browser:menu
  title="user"
  id="user"
  />
  
<browser:menuItem
  for="*"
  menu="user"
  title="Manage Hours"
  action="${portal_url}/multiplehours.html"
  permission="zope2.View"
  />

This ends up being a lot less work than the standard way of defining CMF actions in a standard CMF based application. No CMF tools required; no manual ZMI configuration required.

Note: Since Plone is based on CMF, all of the previous text applies to Plone equally.

[*]The ZMI (Zope Management Interface) is the standard adminstration screens available in both Zope 2 and Zope 3 (although used and implemented quite differently between Zope 2 and Zope 3).

Mon Jan 02, 2006

Boilerplate Zope Projects

In the early days of J2EE/EJB it was decided that there was nothing wrong with boilerplate code and templates. Sun decided that it should be up to the tool vendors to auto-generate and in most cases hide the boilerplate text that was generated.

After having endured many poorly built tools and even decent tools that did very bad jobs of maintaining the boilerplate code, the EJB committee (I don't really know if such a committee exists, but for arguments sake we'll say it did) decided that enough was enough. The usefulness of boilerplate-less code was greatly demonstrated with such popular Java-based ORM (Object-Relational Mapping) as like Hibernate.

So with EJB 3.0 one of the underlying goals was to remove as much boilerplate code as possible. This has resulted in many things, one of those things being the removal of all the extra ejb home and ejb implementation pieces.

Skeletor

In a recent posting to the plone-users mailing list Alan Runyan says the following:

so while I agree with this helpful suggestion - I dont think its something that most people on plone.users will understand or benefit from tremendously. What we really need is something like Skeletor that will pre-generate some code harness. I would see it something like this:

python skeletor.py -id "MyProject"

Personally I agree with the need for something like this and think Skeletor is on the right path. But, there shouldn't be as much of a need for a product like Skeletor as there is currently. The problem is that there is too much boilerplate code right now. It shouldn't be 10 near identical steps to create a new content type. Also there needs to be less configuration by python code and more configuration by external means (in Zope 3 this would be done by ZCML).

Zope 3 minimizes boilerplate code by quite a bit (this may be due to the fact there are currently less layers requiring their own boilerplates compared to the common Plone/CMF/Archetypes/Zope2 combo). But there is certainly less work (and more importantly, less code) involved in setting up a new content type.

So lets minimize the work needed to be done with all of these frameworks to make an application work, shall we?

Thu Dec 15, 2005

Zope3 Techniques in Zope2, Today

Filed Under:

The Zope3 development model has appealed to me for quite some time now but mostly from a stand-offish point of of view. The problem being that I'm a consultant (ServerZen Software) who's primary income is through Plone site/product development. Granted I could spend significant time in searching for Zope3 projects but the reality is I don't even know where to begin at this point.

Zope3 Is In Zope2, Huh?

I've heard on many occasions that today the latest Zope2 releases contain Zope3 but it's taken a while to truly realize what that means. A quick inspection of SOFTWARE_HOME/lib/python shows the usual python packages we're used to dealing with in Zope2. OFS, App, zLOG, all of these get used quite often. At the very end of this list (if you're folder listing sorts alphabetically) is a little unused package called "zope". Hmm... as a Zope2 developer, I've never used this package.

One of the nice things about Zope3 is that it's more or less just a packaging of Python packages that basically all start from the base "zope" package. Click (that was the sound of a light bulb coming on, btw). That "zope" package contained in my Zope2 SOFTWARE_HOME/lib/python was in fact the toplevel package for all Zope3 source code. As of Zope 2.8, this means Zope 2.8 contains Zope X3 3.0 in its entirety. Beginning with Zope 2.9, Zope 3.2 will be included.

So, now that we have Zope3 at our finger tips in Zope2-world, how do we use it?

Enter Five

Zope2 and Zope3 developers talked and talked (I'm imagining) about what would be possible regarding using Zope3 technologies in Zope2 given that their api's aren't exactly compatible. And with a little bit of nudging and a little bit of funding, Martijn Faassen took upon himself the task of making these zope.* packages usable in Zope2. (Of course this isn't the full story but its enough for my story-telling purposes) These days Martijn Faassen and Philipp von Weitershausen consume many daily cycles in an effort to maintain Five and bring into Five even more pieces for working with the Zope3 techniques in Zope2.

Zope3-Based Development Using Five

Probably one of the most often used development terms used Plone development is the term, "Five views". This is a bit of a misnomer as the views are actually a Zope3 concept. Its just that Five enables use to use Zope3 views in Zope2.

Views
Views are an improved way of separating view logic from page templates. This could be described as a MVC (Model View Controller) approach with the model being the zodb, view being the page template, and the controller being a separate class called the view class.
Interfaces
Interfaces are one common object-oriented aspect that Python lacks natively and so has been provided by Zope. Zope2 had interfaces in a simple (ugly?) manner but Zope3 really shines with its interfaces implementation.
Adapters
Need one interface to fit (api-wise) against another interface? Use an adapter.
Utilities
Often uses as a CMF tool replacement mechanism. Provides a nice way to access useful functionality without having to stuff content classes with inappropriate logic.

Whats Left?

There are various common z2 techniques that are quite useful that are still not compatible with z3/Five.

Form Controller Integration
There have been discussions regarding how to integrate Form Controller with z3/Five.
TTW Customization
The current CMF skins mechanism doesn't run on z3/Five at all and thus skin item customization isn't possible.

Hopefully this will whet everyone's appetites for moving to z3 development techniques today on z2.

Wed Dec 07, 2005

Zope Products as Eggs

Filed Under:

"Eggs are to Pythons as Jars are to Java...", or so says the PEAK project at least.  The fact of the matter is that a python egg is a great way of distributing python code and python-related resource data.  Egg's are simply zipfiles that contain all the code needed for a python app or library.  It can contain multiple toplevel packages, miscellaneous package data, etc.

Recent development has gone into a project called Basket to provide this distribution format to the world of Zope (or Zope2 at least).  Initial development on this project enabled regular Zope2 products to be distributed in egg form.  So rather than extract the contents of a traditional product in the INSTANCE_HOME/Products directory, the egg just needs to be placed on the python path some place, most often in INSTANCE_HOME/lib/python.

No More Products Package

As of late Basket has been extended to allow CMF and more specifically Plone-based products to be distributed as eggs.  Even more recent Basket code has enabled CMF and Plone based products to live outside the Products package namespace.  What does this mean?  This means that your product can exist in a toplevel python package called myproduct1 and your code inside myproduct1 can do things like:

 from myproduct1 import config

Where normally code had to be written to do things like:

from Products.myproduct1 import config

What does this gain us?  Well, at a minimum we're removing some of the cruft that makes our Zope products different from regular python projects meaning there is less Zope-specific coding taking place and more reusable python coding happening.

Of course removing the requirement of the Products package namespace is only one of the great features of Basket.

Perhaps the best way to demonstrate the usefulness of plone-products-as-eggs is by example.  Lets take the recent CalPlone work from the calendaring sprint as that example.

The CalPlone Example

CalPlone requires the following projects:

  • CalCore
  • CalZope
  • icalendar
  • And of course, CalPlone

Normally the distribution policy for the CalPlone suite would be to create a tarball consisting of those 4 projects becoming the CalPlone bundle.  One problem here though is that icalendar is not a zope product, its a simple python package/library.  So there would have to be information inside the README.txt or some such file instructing the installer to use python to install the icalendar package beforehand.  So this means extracting the contents of the CalPlone bundle into INSTANCE_HOME/Products directory is not enough.  There are extra steps for installing third-party python packages.

Enter Eggs

What could have been done rather than the old-style tarball creation is to produce one egg file that contains all four of these projects.  The nature of an egg is that all of the packages contained with the egg automatically gets added to PYTHONPATH.  So once the egg has been deployed in the INSTANCE_HOME/lib/python directory, the icalendar (and all of the products contained within) would be automatically accessible to the Zope environment.  So there, all done.  No instructions required on how to install the icalendar package manually because by virtue of the egg format, its already been installed.

Now this means on the release page for CalPlone there would be one lonesome egg file ready and waiting for the masses to download and deploy.

The Future of Basket

One of the issues of using Basket today in Zope 2.8 (and probably Zope 2.9) is that Zope2 at this point makes the assumption that all products live in the toplevel Products package so the fact that Basket provides a means for them to live elsewhere isn't enough.  Basket has to monkey patch the Zope code making this assumption.  This means the assuming code has been replaced with code that is aware of Basket and the packages/products that Basket provides.

Obviously monkey patches should never be a long term solution.  Following that ideal, the plan is to fold Basket functionality directly into Zope2 (perhaps with Zope 2.10 ?).  This means Basket's existance as a standalone product will be short lived.  But this is fine as Basket was designed as a stop gap measure until Zope2 has this functionality.  It was designed to let us use the exceptional egg distribution format today without having to wait for the rest of Zope2 to catch up.

What Now?

As Basket stabilizes there should be a movement to distribute all our favourite Zope2 products as eggs.  One way of seeing how to get this setup is to visit  Chris McDonough's excellent Basket setup page. Although getting Basket directly from CVS might be worthwhile to all those early adopters.  Two concrete examples of products-as-eggs can be found here:

So lets get those eggs cracking.  Plone, anyone?



Wed Jul 27, 2005

PloneIrcChat 0.1 Released

Filed Under:

The first release of PloneIrcChat is now available.  You can download PloneIrcChat at http://dev.serverzen.com/site/projects/ploneircchat.

PloneIrcChat is a java applet based chat product designed to work with Internet Explorer 5+, Firefox 1.0+, and Safari.

Fri Jul 15, 2005

Eclipse 3.1 and Python