Migrate Archetypes content types to Dexterity

In this post I will explain how I migrated Plone 4 Archetypes base content types to Dexterity (plone.app.contenttypes)

By this migartion, I had 2 goals:

  • make my first step for migration to Plone 5
  • use multiligual site with Plone 4 and plone.app.multilingual
  • use plone.app.event (for recurence, no end event, ...).

I also don't want to make big visual changes for my clients. So I decided to not use plone.app.widgets at this moment. I prefere use it with Plone 5. So I will pin plone.app.event 1.1.x. Indeed newer versions of plone.app.event use plone.app.widgets.

For this migration I have to done 2 'steps'. I made a 'custom migration' and I had a profile with an upgrade step. I will explain that below.

For migration, I first have install and pin some packages in my buildout:

  • Add plone.app.contenttypes to my buildout (with 1.1.x version/branch)
  • Pin plone.app.event to 1.1.x version/branch
  • Pin plone.outputfilters 2.1.2 for this problem

Creation of a new profile

I choose to add new profile called 'migratetodx' for making migration. I prefere use a new profile instead of a upgrade step but all migration can be used into a upgrade step.

So I started with creating new profile like this :

<genericsetup:registerProfile
        name="migratetodx"
        title="cpskin.migration: migrate at to dx"
        directory="profiles/migratetodx"
        description="Updates CPSkin to dexterity"
        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
        provides="Products.GenericSetup.interfaces.EXTENSION"
        />

Created folder profiles/migratetodx and added a metadata.xml file like this :

<?xml version="1.0"?>
<metadata>
  <version>1</version>
  <dependencies>
    <dependency>profile-plone.app.contenttypes:default</dependency>
  </dependencies>
</metadata>

 

Add behaviors

Lead image

For lead image, all job is already done in plone.app.contenttypes. I just had to add behavior for types you would like to migrate.

So in my profile, I added files like profiles/migrate/types/Folder.xml

<?xml version="1.0"?>
<object name="Folder">
  <property name="behaviors" purge="false">
    <element value="plone.app.contenttypes.behaviors.leadimage.ILeadImage"/>
  </property>
</object>

Other collective packages

I added others packages from collective: Collective.geo.*, collective.plonetruegallery and eea.facetednavigation.

So I added other behaviors for my folder type into Folder.xml:

<?xml version="1.0"?>
<object name="Folder">
<property name="behaviors" purge="false">
<element value="plone.app.contenttypes.behaviors.leadimage.ILeadImage"/>
<element value="collective.geo.behaviour.interfaces.ICoordinates" />
<element value="eea.facetednavigation.subtypes.interfaces.IPossibleFacetedNavigable"/>
<element value="collective.plonetruegallery.interfaces.IGallery"/>
</property>
</object>

Add import step

I created a profiles/migratetodx/import_steps.xml

<?xml version="1.0"?>
<import-steps>
  <import-step id="cpskin.migration.migratetodx"
               handler="cpskin.migration.migrate.migratetodx"
               title="cpskin.migration: import step for migration">
    <dependency step="typeinfo" />
  </import-step>
</import-steps>

And I use migrate.py for preparing migration, starting migration (with migration view) and fixing image scales.

Problems with memoize

We use caching for our sites and applications. And during migration, I saw than I had some problems with the cache and with plone.memoize. We decide to use an empty plone.memoize cache and keep this cache empty with this code into import step.

In my migrate.py file, I used this code:

from plone import api
from zope.annotation.interfaces import IAnnotations


def migratetodx(context):
    if context.readDataFile('cpskin.migration-migratetodx.txt') is None:
        return
portal = api.portal.get()
request = getattr(portal, 'REQUEST', None)
class EmptyMemoize(dict):

    def __setitem__(self, key, value):
        pass

annotations = IAnnotations(request)
annotations['plone.memoize'] = EmptyMemoize()

Fix image scale

We also have to fix new way to get image scale, I was inspared by  this code. It get all richtext content and check if it needs to change image_[preview] to @@images/image/[preview].

I also use this code for getting all portlets static and update it.

from zope.component import getMultiAdapter
from zope.component import getUtility
from plone.portlets.interfaces import IPortletManager
from plone.portlets.interfaces import IPortletAssignmentMapping


def image_scale_fixer(text):
    if text:
        for old, new in IMAGE_SCALE_MAP.items():
            # replace plone.app.imaging old scale names with new ones
            text = text.replace(
                '@@images/image/{0}'.format(old),
                '@@images/image/{0}'.format(new)
            )
            # replace AT traversing scales
            text = text.replace(
                'image_{0}'.format(old),
                '@@images/image/{0}'.format(new)
            )
    return text


def fix_portlets_image_scales(obj):
    managers = [u'plone.leftcolumn', u'plone.rightcolumn']
    for manager in managers:
        column = getUtility(IPortletManager, manager)
        mappings = getMultiAdapter((obj, column), IPortletAssignmentMapping)
        for key, assignment in mappings.items():
            # skip possibly broken portlets here
            if not hasattr(assignment, '__Broken_state__'):
                if getattr(assignment, 'text', None):
                    clean_text = image_scale_fixer(assignment.text)
                    assignment.text = clean_text
            else:
                logger.warn(u'skipping broken portlet assignment {0} '
                            'for manager {1}'.format(key, manager))

 


Custom migration

Migrate extended archetypes field

I migrated an extend archetype filed named 'hiddentags'. For that is use ICustomMigrator adapter. I added this line on my configure.zcml :

<adapter name="mymigrator" factory=".migrate.MyMigrator" />

And I created a class MyMigrator with a "migrate"method in my file migrate.py

from plone.app.contenttypes.migration.migration import ICustomMigrator
from zope.component import adapter
from zope.interface import implementer
from zope.interface import Interface


@implementer(ICustomMigrator)
@adapter(Interface)
class MyMigrator(object):

    def __init__(self, context):
        self.context = context

    def migrate(self, old, new):
        # hiddenTags
        if getattr(old, 'hiddenTags', None):
            new.hiddenTags = old.hiddenTags

Migrate marker interfaces

I had some marker interfaces on our content, in this snippet, I will show how I migrated eea.facetednavigation marker :

from eea.facetednavigation.settings.interfaces import IDisableSmartFacets
from eea.facetednavigation.settings.interfaces import IHidePloneLeftColumn
from eea.facetednavigation.settings.interfaces import IHidePloneRightColumn
from eea.facetednavigation.subtypes.interfaces import IFacetedNavigable
from eea.facetednavigation.subtypes.interfaces import IFacetedWrapper
 
interfaces = [
  IFacetedNavigable,
    IDisableSmartFacets,
    IHidePloneLeftColumn,
    IHidePloneRightColumn,
IFacetedWrapper,
]
for interface in interfaces:
    if interface.providedBy(old):
        alsoProvides(new, interface)

Migrate faceted criteria

In our site, we use faceted navigation. and we have to migrate criteria for all our faceted view. I have done into custom migration with that code :

if IFacetedNavigable.providedBy(old):
criteria = Criteria(new)
    criteria._update(ICriteria(old).criteria)

Migrate object with coordinates

Again in migrate method, I had this snippet

from collective.geo.behaviour.behaviour import Coordinates

old_coord = Coordinates(old).coordinates
new_coord = Coordinates(new)
new_coord.coordinates = old_coord

Setting time-zone to new event

When I wrote this code, there is still a bug into plone.app.event 1.1.0 and plone.app.contenttypes 1.1.0. Time-zone is not set on each event during migration, I forced id:

from plone.app.event.dx.interfaces import IDXEvent

if IDXEvent.providedBy(new):
new.timezone = timezone

Conclusion

I learnt a lot of Plone migration all along my work.Each migration depend on plugins you use,

And this is link for my 'import step' script. I hope you can use some piece of code of it.

Add comment

You can add a comment by filling out the form below. Plain text formatting.