Safer GenericSetup upgrade steps

Leave a comment

18/09/2008 by Gilles Lenfant

Upgrading Plone extensions using GenericSetup upgrade steps. A great idea. Easy to develop, easy to use and document. A unified upgrade interface for every Plone extension.

Anyway, there’s actually an annoying issue on upgrade steps: upgrade steps for component “foo” are exposed in sites where the “foo” component is not installed.

Wow, managers on instances where various different Plone sites are installed have to be careful on this. Running an upgrade step of a component that is not installed in a site may be harmful. Even if upgrade scripts are supposed to be defensively programmed.

Having several Plone extensions, I made a decorator based generic safety belt. Of course this does not prevent GS showing the upgrade steps but executing such upgrade steps raises an explicit error message and does not execute the upgrade step.

Just add these lines to your “utils.py”:

from zope.component import getUtility
from Products.CMFCore.interfaces import ISiteRoot
from config import PROJECTNAME

class NotInstalledComponent(LookupError):
    def __init__(self, cpt_name):
        self.cpt_name = cpt_name
        return

    def __str__(self):
        msg = ("Component '%s' is not installed in this site."
               " You can't run its upgrade steps."
               % self.cpt_name)
        return msg

class IfInstalled(object):
    """The decorator"""
    def __init__(self, prod_name=PROJECTNAME):
        """@param prod_name: as shown in quick installer"""
        self.prod_name = prod_name

    def __call__(self, func):
        """@param func: the decorated function"""
        def wrapper(setuptool):
            portal = getUtility(ISiteRoot)
            qi = portal.portal_quickinstaller
            installed_ids = [p['id'] for p in qi.listInstalledProducts()]
            if self.prod_name not in installed_ids:
                raise NotInstalledComponent(self.prod_name)
            return func(setuptool)
        wrapper.__name__ = func.__name__
        wrapper.__dict__.update(func.__dict__)
        wrapper.__doc__ = func.__doc__
        wrapper.__module__ = func.__module__
        return wrapper

Then your upgrade scripts should be decorated like this in your “upgrades.py”

...
from utils import IfInstalled
@IfInstalled()
def someUpgradeFunction(setuptool):
 # Stuff as usual...

You’re done…

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives

%d bloggers like this: