By Ingeniweb. A Django site.
Octobre 5, 2008
» Why Plone architecture must change


I proposed a conference on this theme but it was not keeped for PloneConf2008. Here comes a short work on its, and I will hope it create a debate.

Plone is born with a goal: make Zope 2 and CMF simple. Those two are frameworks and allowed you to create a website… a so rustic website. With Plone you only needed to fill one form to have a cool site ready to use and to custom. At that time Plone was CMFPlone, an other implementation of CMFDefault.

Some new products were inserted in the bundle like GroupUserFolder in a such way that they cannot be separated from the base implementation of Plone.

The second step was the Archetypes framework: a new manner to manage object’s attributes as schema. With all informations stored in the schema,  mutators and accessors are generated on boot time and forms are generated from schema’s widgets. It was cool, but not perfect. Then the product ATcontentTypes was created to be the glue to upgrade CMFPlone as Plone: all base types from CMF are now overloaded by a type from ATContentTypes.

So came Zope 3 and Five. Five is the glue that allows Zope 2 developpers to use component architecture from Zope 3 in their code. Plone 2.5 and Plone 3 are using Five.

Another product came from Zope ‘renegades’: PluggableAuthService and its implementation in Plone: PlonePAS. It should replace the old GroupUserFolder but users and groups management templates were never refactored.

Plone 3 introduces new component in plone.app to our greatest happiness.

What part of Plone needs CMF?
Why Plone needs to know about Archetypes storage and CMFEditions strategy?
Why GroupUserFolder is always in the bundle as PlonePAS fit is API?

Now Plone is like the linux kernel: a big monolithic Plone with a lot of modules that create a base Plone 3 site. And so much glue! GroupUserFolder is always here because nobody knows and wants to work on the portal_group replacement.
If you are following Plone4Artist or PloneGov you can see that a part of these projects needs to overload Plone base configuration.

CPS 3, an other CMF-based CMS, was conceived with the thinking that components need to be independant to be reusable and maintainable:

  • CPSSchema depends only on Zope
  • CPSCore depends only on Zope
  • CPSDocument depends only on CPSSchema
  • CPSDefault depends on CPSCore and CPSSchema, and implements the CPS site.

After 2 years it was divided into platforms:

  • CPS Legacy
  • CPS Courier
  • CPS Groupware

In front of that Plone propose at single product with a lot of glue that depends on others products or components that use their own glue and so on…
There’s no plone.core and plone.default and we cannot create a plone.artistsite or a plone.govsite.
Do you think that everyone need an openid or an ldap integration in Plone ?

Plone 4 must be a reimplementation, not only a new glue with new concepts. I don’t want any new functionality in Plone 4, I want modularity and scalability.

      

Juillet 4, 2008
» Write your first Zope 2 product


‘The topic is to make understandable what is a Zope product in the python world.

What is a python module?

A Python module is a directory on your filesystem that contain at least a file called __init__.py. This file can be empty, but the name is fixed.

How Python can find such a directory?
The standard path where python search for modules is in the directory site-packages of your Python installation. By default you find it in C:Python2.4Libpython under windows or in /usr/lib/python2.4 un Unix-like systems (Linux, Mac OS, BSD, etc). You can extend this search by adding the PYTHONPATH variable in your environment.

@set PYTHONPATH=C:Zope2.9libpython

or

export PYTHONPATH=/usr/lib/python2.4:/opt/Zope2.9/lib/python

In a Python module every sub-directory that contains a file named __init__.py is considered as a sub-module.
On these points Zope doesn’t differ from Python

What is a Zope Products?

You can divide your Zope installation in three parts: the Zope framework, the Zope Server and the ZODB. A Zope product is an application for the Zope Framework. You can use it alone, without the Zope Server or the ZODB. Actually it’s the failure of Zope to not have any public software based on the Zope Framework without the Zope Server.
Zope 2 is using a specific code to load products to register them as specific applications. You can read it in your Zope installation in the file OFS/Application.py.

During the warm up the Zope Server is looking in the zope.conf file for the ‘products‘ keywork. Each time it is defined this keywork is following by a filesystem path where Zope Products are uncompressed.
This list of filesystem paths is initialized with the two paths:

  1. the lib/python/Products directory of your Zope installation
  2. the Products directory of the Zope instance

The first one contains base application you need to work like ZCatalog, PythonScripts (for ZMI). You should never add a product in there and read the documentation available in these products will help you to understand Zope Framework standards.
The second one is empty and is designed to store cusomer Zope Products.

Implement your first Zope 2 product

To create your product you create a Python module in the Products directory. You should Have something like this:

Products

> MyProduct

> __init__.py

To be recognized as a Zope Product the __init__.py file must contain a function called initialize. This function is mainly declarative. You can read a full sample on the document ” Write a toolbox for a Plone 2.5 product “.
The main goal of the initialize function is to register your new classes into the good drawer in the Zope Application. In the sample we register a new tool object but you will do the same for content types, i18n domains, etc.

» Write a toolbox for a Plone 2.5 product


For years we have had the need to embed a utility in Plone products that allows us to execute private methods in restricted Python mode. You need such a utility when using a ZMI Python script in a Page Template or in an Expression. The last one is a CMFCore class that is used in workflow transition condition or in Archetypes field condition for example. To be able to do this utility you need to create a ‘portal tool’ for your site with some public methods. Here I will show you how to build a very basic utility that I can use in the next blog entry.

What is a tool

In pure Zope 2/CMF, a tool is a unique item with a forced id that inherits from the SimpleItem class. An instance of a tool is always created on the root of your Plone site to be acquirable from everywhere in the site.

The CMFCore product provides a function that allows us to call a tool in an unique way: ‘getToolByName’.
In python modules, external methods, or python scripts, you can use following code:

from Products.CMFCore.utils import getToolByName
mytool = getToolByName(obj or context, 'tool id', default value is not found - None if not specified)

You can use it in Page Template too, using the ‘module’ call:

python:modules['Products.CMFCore.utils'].getToolByName(here, 'tool id')

Implementation

What should a tool module contain

Now back to our implementation.

First you need to have a product to write in. To create a new product please read this howto , or download someone else’s to play with.
In your product create a file called ‘tool.py,’ if there will be only one tool in your product, or ‘myfunctiontool.py’ if there will be more than one. As a best practice, I recommend to always choose the second way.

A simple tool code could be:

from OFS.SimpleItem import SimpleItemfrom Products.CMFCore.utils import UniqueObject

from AccessControl import ClassSecurityInfo

from Globals import InitializeClass

class ProductNameTool(SimpleItem, UniqueObject):

    """ Description of what your tool handle
    """

    id = 'portal_productname'

    meta_type = 'Product Name Tool'

    manage_options = SimpleItem.manage_options
    security = ClassSecurityInfo()

InitializeClass(ProductNameTool)

Description of imported items:

  • SimpleItem is a class coming from Zope that ensures us that we have persistence and base views available in ZMI.
  • UniqueObject is a utility class coming from CMF that ensures us that nobody can rename an instance of it. It’s important for us because our tool must never be renamed by error.
  • ClassSecurityInfo is the class to use in every Zope persistent class in order to define the security of each method.
  • InitializeClass is the helper function that registers our class in the Zope context.

Description of class attributes:

  • The id must be unique on your site root and should start with ‘portal_’ or end with ‘_tool’. If your tool unherit from ZCatolog the id must be suffixed by ‘_catalog’.
  • All classes must have a meta type. You can just extend your class name with spaces.
  • manage_options contains the tabs displayed in the ZMI.
  • security defines the security in your class. All classes must contain such a declaration.

Initialize our tool at Zope startup

Now that Zope knows that you have a tool, what about Plone? Plone requires an extra step in order to register our tool (full details in: “Write your first Zope 2 product“.
We have to edit the __init__.py file of our product and add following lines:
 

from Products.CMFCore.utils import ToolInit

from myfunctiontool import ProductNameTool

tools = (ProductNameTool,)

def initialize( context ):

    ToolInit( 'My Product Tools'
                , tools=tools
                , product_name='ProductName'
                , icon='tool.gif'
                ).initialize( context )

ToolInit is a class that handles tool registration for us. We can register all tools defined in our product in only one pass.
ProductNameTool is our tool class. Here we need to import all tool classes we want to register.

At last, we call the ToolInit constructor and run its ‘initialize‘ method to build our tool factories:

  • the first parameter is the name viewed in the Add dropdown menu in the ZMI
  • tools is a tuple of classes
  • product_name should contain the name of your product
  • icon is the name of the icon displayed in the ZMI (16×16) that must be on the root of your product

We can restart Zope then add the tool in the ZMI. After that, we can add methods to this tool. Most often these methods are public or protected by ‘View’ or ‘Manage portal’ permissions.

Future of Zope 2 tools

In Zope 3 tools are becoming utilities. In Plone 3 we are in limbo, with all old Zope 2 tools registered both as tools and as utilities. In the near future, however, only the utilities will be used. Use Tool for Plone 2.5 development only.

You can read more about utilities in Zope 3 and Plone 3 documentation.

Juin 6, 2008
» How to hide a column in CPS with CPSSkins


Since few weeks I went back in CPS to make maintenance on it. I was a CPS core developper, three years ago…
I’m customing a CPS theme with CPSSkins. It could be a very cool product if a documentation exists.
It was so in advanced on web2.0 that only few people really used it outside its main developper and Nuxeo team.

It’s the first article on CPS and not the last one.

a quick recipe to explain how to hide a skin in a part of your site

The goal is to hide the right column when the ‘projects_results’ template is used

  1. login with site Manager rights
  2. in the portal box cloick on ‘Edit themes’
  3. duplicate ‘Fille’ page and named it ‘SansColonne’
  4. switch to layout mode
  5. set column number to 1 in the main block
  6. go in the ZMI on the site root and edit the .cpsskins_theme script with the following code
theme_id = 'mysitetheme'

if REQUEST is None:
    REQUEST = context.REQUEST

if not context.portal_membership.isAnonymousUser():
    return '%s+Authenticated' % theme_id
elif REQUEST.get('PUBLISHED').getId() == 'index_html':
    return '%s+HomePage' % theme_id
elif REQUEST.get('URL').endswith('projects_results'):
    return '%s+SansColonne' % theme_id
else:
    return '%s+Fille' % theme_id

» Write functionnal doctests with files in forms


A short bill about testing forms with files.

First you need to setup you environment using your site policy product then your
product to test. The function setUpDefaultMembersAndFolder create the
structure with access rights positioned. After that you can focus yourself on
Formula module test.

Test Formule creation process
=============================

For the test you need to create three Nomenclatures and two Validation Process.

First, some set-up:

    >>> from Products.Five import zcml
    >>> import Products
    >>> import iw
    >>> zcml.load_config('configure.zcml', package=iw.sitepolicy)
    >>> zcml.load_config('configure.zcml', package=Products.Formula)

    >>> from iw.sitepolicy.tests import utils
    >>> utils.setUpDefaultMembersAndFolder(self)

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

Let us log all exceptions, which is useful for debugging. Also, clear portlet
slots, to make the test browser less confused by things like the recent portlet
and the navtree.

    >>> self.portal.error_log._ignored_exceptions = ()
    >>> self.portal.left_slots = self.portal.right_slots = []

Import needed for testing file in forms

    >>> import cStringIO
    >>> portal_url = self.portal.absolute_url()

We will need of cStringIO to create fake files.

Now we will logon with an user and jump directly into working folder.

    >>> browser.open('%s/logout' % portal_url)
    >>> browser.open(portal_url + '/login_form')
    >>> browser.getControl('Login Name').value = 'jdoe'
    >>> browser.getControl('Password').value = 'secret'
    >>> browser.getControl('Log in').click()
    >>> 'John Doe' in browser.contents
    True
    >>> browser.open(self.formula_folder.absolute_url())
    >>> browser.url
    '.../formula...'

Verify that an author keeps modification and creation rights

    >>> self.formula_folder.absolute_url()+'/edit' in browser.contents
    True

    >>> self.formula_folder.absolute_url()+'/createObject?type_name=BaseFormula' in browser.contents
    True

self.formula_folder is set in setUpDefaultMembersAndFolder.

Now we can fill the form and test the result. Document’s id is the filename
prefixed by ‘formula_‘ and document’s title is the first file line.

Create a new Formula for with special pink flavor

    >>> browser.getLink('Add Base Formula').click()
    >>> 'portal_factory' in browser.url
    True
    >>> browser.getControl(name='special').value = u"pink flavor"
    >>> browser.getControl(name='formula_file').add_file(cStringIO.StringIO('Pink flavor\n   etc'), 'text/plain', "32048432.txt")
    >>> browser.getControl('Save').click()
    >>> 'Changes%20saved' in browser.url
    True
    >>> browser.getLink('View').click()
    >>> '<h1>Pink flavor</h1>' in browser.contents
    True
    >>> print browser.url
    http://nohost/plone/folder1/formula_folder/formula_32048432

If you want to do the same with Plone 2.5 - Zope 2.9 - you will have an error
because the ‘add_file‘ is not implemented on FileControl. You need to use
following code:

getControl(name='formula_file').mech_control.add_file

Avril 27, 2008
» encolpe


CMFEditions is a Plone generic versionning product and a framework for more dedicataed versioning task like staging (Iterate). It was included in Plone bundle and is activated by default on new sites. It is very simple to use and well documented. A really nice poduct in a developer point of view and a very useful tool from a customer point of view. Diggig deeper you can find something dangerous from a storage point of view.

About storage

Then, what are we calling storage?

There’s the storage from Archetypes (AttributeStorage now replaced by AnnotationStorage, FileSystemStorage, SQLStorage, etc). They are the way we want to store a field value. Here we just want to know if it is inside or outside Zope… dig deeper…

There’s the storage from ZODB. They manage how each object is stored and what structure has the ZODB:

  • FileStorage: the ZODB is a single and potentialy big file called Data.fs
  • DirectoryStorage: each object in the ZODB is a folder in the filesystem and every object attribute is a file
  • RelStorage: object are stored in an external database (PostGresSQL)

These storages have now blob support for file object: it means that your file is stored outside the ZODB and this one just store the reference on it.

There’s also a CMFEditions storage to store versionning informations and data. It’s currently based on ZVC that store everything in the Data.fs.

How is created a document’s revision ?

CMFEditions doesn’t know anything about Archetypes. It’s a transversal tool that can be apadted quickly if we move from Archetypes to something else. And we want it to keep this independance.

When a new revision is created, the docuement is parse like a simple python object and each attribute is stored following different strategies define by some modifiers. An attribute doesn’t show which Archetypes storage is used for it. until know it supposes that AnnotationStorage or AttributeStorage is used the it just copie each attribute elsewhere in the ZODB. In the FileSystemStorage case the whole file is copied from the filesystem into the ZODB for each revision… here size does matter and smaller is better.

Do get storage information we need to use Archetypes API. Then we need an extra modifier for each Archetypes storage. But now, what to do with this information? You have to modify you specific Archetypes storage to be able to store revision informations and to restore them if needed. CMFEditions communicate a very few informations during these operations.

Novell Plone team submit a patch on FileSystemStorage to do such work and next releases would include it.

What to do to simplify storage inclosing

This approach doesn’t allow to define advanced strategies in Archetypes storages above versionning. But why do we need to defin advanced strategies in Archetypes storages?

The only real storages should be the ZODB storages. But they doesn’t allow advanced strategy creation to manage objects on different ways following some rules.

What is the advantage to have strategy implemented directly in the ZODB?

CMFEditions would not need to know anything about field storage: everything is managed by the ZODB. You don’t need to configure storage strategy in several configuration files and the developper doesn’t need to know about this. blob is one of this strategy. An Archetypes field would just indicate if it use standard or blob.

» encolpe


Enfold Desktop is a very cool product for Windows users. Linux and Mac users already have builtin WebDav tools that do the trick. Enfold provides both server side products and client side user interface.

All this come from my only experience on Enfold Desktop 4 and may be there’s some errors. There no documentation for all this on Enfold Site

Client side

The client user interface needs to be installed on every computer that you want they use webdav access. It’s a plugin for the standard windows file explorer. Once you configured your access with the public url of your plone site you browse it like a local folder with a little time penalty but faster than in your web browser.

cooler than cool

hum, it’s not a water cooler advertising, but this product is really cool: in a Plone 3 site you can through your file explorer create new content types, modify and delete existing ones. You can doing a workflow transition. And for my customer site… we will see that point later.

Server side component

Enfold provide also an archive with several product that integrates your customer plone site for Enfold Desktop. It comes especially with the ShelExServer product that you have to install within the plone control panel. This product extend several tools to be more WebDav aware: content_type_registry and portal_workflow for example. It also modify permission for roles in the zope instance root.

Where things get more complicated

The last point is very useful in a plone site without any customization but can be very painful with a site managed with a policy registered in GenericSetup. Couple this with some bug in the last release candidate of Enfold Desktop 4 and you have a big mess.

What do I need to check after installing server side product

First: Don’t apply your customer profile before having update it at the end of this process. You will loose all Enfold Desktop settings.

Roles:

webdav access are correctly set in the zope instance root, but not in the plone instance root with Plone 3. Go in the Security tabs and fix webdav permissions for the three new roles: Editor, Contributor and Reader. Webdeav permissions have to be the same than on ‘View’ and ‘Access portal content’ permisions.

Workflows:

For each state webdav permissions are clone from ‘View’ and ‘Access portal content’… but the ‘acquire’ setting isn’t cloned during the operation. You have to fix that manually on each state of each workflow.

Content type registry:

Enfold Desktop create new predicates on base ATCT types, and only on them. If you need to manipulate your own types you have to confifure them manually.

Now you can export your profile and merge the modification in your customer profile.

More and more

It is impossible to modify which content type are created during a copy/paste: In one folder I want to create a document from a text file, in another I want to create a newsletter.

For HTML files created via webdav, the code is displayed as it until the document is edited and submit once.

It may be cool if Enfold Desktop can access to other web framework through webdav… May Sidney and Alan can answer on this.

Février 7, 2008