Posted by: encolpe | 2008 July 4

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.

Posted by: encolpe | 2008 July 4

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.

Posted by: encolpe | 2008 June 21

Use google document to create the Plone Documentation

Today I was writing a little documentation about Plone 2.5 in reaction to a first draft on workflow management by groups. My goal is not to write a document alone that will replace this draft but to imply its author. A this point a tool was missing until I remember we can use Google Docs application to share our ideas and write this document together.

We have shortly try out Google Docs and our first impressions are positive: each other can write in the same document and modification are reported in live on the other screen. Add a GTalk window or an IRC session to discuss and you have a very powerful tool.

The next step is to finish this document and to copy/paste it in Plone Documentation. I hope to do this tomorrow night.

Hey Limi, when will we embed this in Plone ;^)

Posted by: encolpe | 2008 June 17

Integration of buildout in your favorite IDE

As Open Source developer I’m concived that we are developping on the best tools ever created (early friday) and I’m using a proprietary IDE… but writen in Python: Wing IDE.
 
With this IDE you need only two operations to make it understand a new buildout:

  • add your bin/instance (bin/zopeinstance or bin/zeoclient) in your project via the project Panel
  • once it is done select it and right click on it to activate it as ‘main debug file’

 
Then wait that wing update the python path for you and enable the autocompletion for your whole project. That’s magical, I love it!

Thanks to Lennart to ask the good question on wing-users mailing list and to Martijn Pieters to give us this excellent recipe.

It would be cool is there’s such recipe fir IDE in the new documentation section on plone.org.

Posted by: encolpe | 2008 June 13

Plone Paris Sprint ambiance

Some short videos taken during Plone Paris Sprint 2008 that can show you the ambiance of a sprint with big and small teams.

On Saturday morning

On Saturday afternoon

Posted by: encolpe | 2008 June 6

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
Posted by: encolpe | 2008 June 6

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

Today I loose my day on a specific IE7 bug:
it doesn’t take in account fixed height and overflow in table structure.

The concept is to use the thead tag (the tfoot tag if needed) for the fixed part and the tbody tag for the scrolling part:

<table>
  <thead>
  </thead>
  <tbody>
  </tbody>
</table>

With firefox 2 you just need this CSS code:

table>tbody {
  overflow: auto;
  height: 280px;
  overflow-x: hidden;
}

Simplicty, efficacity.

With IE7 you need to cheat and to add an extra div tag which support the overfllow rule. The div tag height must large enough to contain the table: thead height + tbody forced height.

<div class="tableContainer">
  <table>
    <thead>
    </thead>
    <tbody>
    </tbody>
  </table>
</div>

The CSS rules are now more complex:

div.tableContainer {
  width: 90%;       /* table width will be 99% of this*/
  height: 320px;    /* must be greater than tbody*/
  overflow: auto;
  margin: 0 auto;
}

table {
  width: 97%;  /*100% of container produces horiz. scroll in Mozilla*/
  border: none;
  border-spacing: 0px;
  background-color: transparent;
}

table>tbody {
  overflow: auto;
  height: 280px;
  overflow-x: hidden;
}

With that code every tr tag height is set to 280px on IE7, we need an extra rule:

table>tbody tr {
  height: auto;
}

But the thead is always glue with the tbody. We need to make it relative:

table>thead tr {
  position:relative;
  top: 0px;/*expression(offsetParent.scrollTop); IE5+ only*/
}

This is the fifth implementation of the day to fix this bug. I don’t know how to thanks Microsoft to make me earn money on customers. With Internet Explorer I don’t need to buy a Wii the improve my mind.

Posted by: encolpe | 2008 April 28

Manage your internationalization with i18ndude

Yet another post to manage automatically you po files…

We are using i18ndude to manage translation for the Plone bundle and helpers script are in PloneTranslations/utils. If we can do this for the whole bundle you can do it too for your products.

Here come an example of a script that manage translations for a Plone 2.5 product:


#!/bin/sh

##
## First we work on the 'plone' domain and then on the local domain to define below.
##

PLONEPRODUCTS="/path/to/plone/bundle/Products"
LOCALDOMAIN="product_name"
POPREFIX="product_name_in_lowercase"

##
## Domain: 'plone'
##

##
## Search all translations in 'plone' domain and rebiuld a new catalog
i18ndude rebuild-pot --exclude build --pot "./i18n/i18ndude-plone.pot" \
                     --create plone "./"

##
## Filter out all msgids that are already translated in PloneTranslations
i18ndude filter "./i18n/i18ndude-plone.pot" \
                "${PLONEPRODUCTS}/PloneTranslations/i18n/plone.pot" > "./i18n/filtered-plone.pot"

##
## Merge generated file with manual maintained pot file then with the current catalog
i18ndude merge --pot "./i18n/${POPREFIX}-plone.pot" \
               --merge "./i18n/filtered-plone.pot" \
               --merge2 "./i18n/manual-plone.pot"

##
## Cleaning
rm "./i18n/filtered-plone.pot" "./i18n/i18ndude-plone.pot"

##
## Refresh po files for 'plone' domain
i18ndude sync --pot "./i18n/${POPREFIX}-plone.pot" \
                    "./i18n/${POPREFIX}-plone-fr.po" "./i18n/${POPREFIX}-plone-en.po"

##
## Domain: LOCALDOMAIN
##

##
## Search all translations in ${LOCALDOMAIN} domain and rebiuld a new catalog
i18ndude rebuild-pot --exclude build --pot "./i18n/i18ndude.pot" \
                     --create ${LOCALDOMAIN} "./"
##
## generated.pot is given by ArchGenXML during product generation
i18ndude merge --pot "./i18n/${POPREFIX}.pot" \
               --merge "./i18n/i18ndude.pot" \
               --merge2 "./i18n/generated.pot"
##
## Cleaning
rm "./i18n/i18ndude.pot"

##
## Refresh po files for LOCALDOMAIN
i18ndude sync --pot "./i18n/${POPREFIX}.pot" \
                    "./i18n/${POPREFIX}-fr.po" "./i18n/${POPREFIX}-en.po"

##
## Check for missing translations in all page templates
echo
echo "#########################################################"
echo "##"
echo "## untranslated messages summary report"
echo
i18ndude find-untranslated -s `find skins -name "*.*p?"`

echo "To display the full report use:"
echo "i18ndude find-untranslated \`find skins -name \"*.*p?\"\`"

exit 0

You can repeat this for every domain you are using in your products.
You still have to manage a manual-domain.pot by hand to be able to i18n selectboxes or Archetype name for a content type for example, but your work is really easier to maintain you translations.

Thanks to Hanno for maintaining this tool.

Posted by: encolpe | 2008 April 27

Theming and skining

Since Plone 3 is out, integrators are affected by the new way to do themes and to manage page layout. Through The Web customization is reduce to the strict minimum and some of them just leave Plone to another thing more easier to integrate. Before the sprint there were three proof of concept products trying to modify this.

The only one that can be used is CSSManager developed by Weblion. It only allow the site manager to modify the base_properties through the PMI without having to type anything: have ave a picker color widget and dropdown menu for other properties. It works only if your theme use base_properties. For example NuPlone does not.

In Paris Sprint, Jean-Mathieu Grimaldi (macadames) create a new product to ameliorate plone skinning collective.phantasy that you can found here: http://svn.plone.org/svn/collective/collective.phantasy/trunk/

The main goal of this product is to be able to have theme associated with folders in the same way that you can see on Quintagroup skins site.

This is far from online theming that you can have here on wordpress or in CPSSkins.

What do dummies wnat to their site? Inline edting is cool, but they want to be able in few clicks to appropriate their brand new site adding a new logo and getting round corners everywhere. They want to get a theme and modify it quickly as we done with base_properties. They don’t want to see the ZMI or to learn python and Zope 3 interfaces.

Such a tool should be a component of the Plone social sprint and must be available with or within the next major version of Plone.

I’m leaving Plone Paris sprint with a lot to think. It was great to met you again guys and girl from the community.

Older Posts »

Categories