December 6th, 2010

The plone sub-site product Lineage migration script from version 0.1 to 0.6 involves migrating objects that use its obsolete Child Folder custom content type to standard Plone folders marked as child folders using p4a.subtyper.  I’ve been trying to enhance the migration script so that any portlets attached to a Child Folder items are copied over to the new Folder item as well.  I didn’t find any examples of this anywhere so I’m posting here in case anyone should needs to do something similar.

Here’s the code I used.  A few things that I learnt along the way:

  • contextual portlets are managed by Zope local utilities
  • you have a IPortletManager local utility for each portlet area defined.  Plone comes with two of these ‘plone.leftcolumn’ and ‘plone.rightcolumn’ but you can add more
  • An IPortletAssignmentMapping adaptor adapts your context and its portlet manager to provide storage for your portlet assignments for that context
  • A ILocalPortletAssignmentManager adaptor adapts your context and its portlet manager to provide storage for your blocking settings for that context
from logging import getLogger
from plone.portlets.interfaces import ILocalPortletAssignable
from plone.portlets.interfaces import ILocalPortletAssignmentManager
from plone.portlets.interfaces import IPortletAssignmentMapping
from plone.portlets.interfaces import IPortletManager
from plone.portlets.constants import CONTEXT_CATEGORY, GROUP_CATEGORY, CONTENT_TYPE_CATEGORY

from zope.interface import alsoProvides
from zope.component import getMultiAdapter
from zope.component import getUtility
from zope.component import getUtilitiesFor
from zope.component import queryUtility
from zope.component import provideUtility

logger = getLogger('scarba05.utils')

def copy_portlet_assignments_and_settings(src, target):
    """Copy portlet assignments from src to target"""
    if not ILocalPortletAssignable.providedBy(src):
        alsoProvides(src, ILocalPortletAssignable)

    for manager_name, src_manager in getUtilitiesFor(IPortletManager, context=src):
        src_manager_assignments = getMultiAdapter((src, src_manager), IPortletAssignmentMapping)
        target_manager = queryUtility(IPortletManager, name=manager_name, context=target)
        if target_manager is None:
            logger.warning('New folder %s does not have portlet manager %s' %
                           (target.getId(), target_manager))
        else:
            target_manager_assignments = getMultiAdapter((target, target_manager),
                                                IPortletAssignmentMapping)
            for id, assignment in src_manager_assignments.items():
                target_manager_assignments[id] = assignment
            src_assignment_manager = getMultiAdapter((src, src_manager),
                                                 ILocalPortletAssignmentManager)
            target_assignment_manager = getMultiAdapter((target, target_manager),
                                                 ILocalPortletAssignmentManager)
            for category in (CONTEXT_CATEGORY, GROUP_CATEGORY, CONTENT_TYPE_CATEGORY):
                target_assignment_manager.setBlacklistStatus(category,
                                      src_assignment_manager.getBlacklistStatus(category))

I ended up tweaking this code a little for the actual migration step as in Lineage 0.1 Child Folders didn’t inherit their parents portlets, no matter what the parent portlet block settings were.