Migrating schema-extended Archetypes default CT's to plone.app.contenttypes/dexterity

In the documention of plone.app.connttypes there's mention of providing a custom migrator for schema-extended archetypes contenttypes to dexterity default types using the @@atct_migrator view, but the documentation says:

To keep the data you would need to write a custom migration for your types dexterity-behaviors for the functionality provided by the schemaextenders. This is an advanced development task and beyond the scope of this documentation.

Is there any example code as documentation available that shows how to integrate this custom migration? I have an older Plone 4 site where collective'flag has extended all the default contenttypes. The @@atct_migrator view shows the field 'flaggedobject' has been added, and I'd like to copy it.

There are two cases where schema-extended fields are migrated by default: BlobNewItems (see https://github.com/plone/plone.app.blob/pull/2) and images created with collective.contentleadimage. Both migrations use the default field-migrators provided by p.a.c.
If you have custom extenders for certain types I suggest you skip migrating these types in the default-migration and instead write a custom migration for these as described in https://github.com/plone/plone.app.contenttypes#migrating-custom-types-in-your-own-code. There map all fields als the default migration would do and also map the extended fields to your new behavior.

Since it's a bit disappointing there is no sample code to be found for this problem, I have written some.
Here it is:

plone=app.Plone
from zope.component.hooks import setSite
setSite(plone)

from plone.app.contenttypes.migration.migration import migrateCustomAT

def custom_document_migration():
    fields_mapping = (
            {'AT_field_name': 'text',
             'DX_field_name': 'text',
             'DX_field_type': 'RichText',
             },
            {'AT_field_name': 'flaggedobject',
             'DX_field_name': 'flaggedobject',
             },
    )
    r = migrateCustomAT(
        fields_mapping,
        src_type='Document',
        dst_type='Document')
    return r

def custom_folder_migration():
    fields_mapping = (
            {'AT_field_name': 'flaggedobject',
             'DX_field_name': 'flaggedobject',
             },
    )
    r = migrateCustomAT(
        fields_mapping,
        src_type='Folder',
        dst_type='Folder')
    return r


def custom_file_migration():
    fields_mapping = (
            {'AT_field_name': 'file',
             'DX_field_name': 'file',
             'DX_field_type': 'NamedBlobFile',
             },
            {'AT_field_name': 'flaggedobject',
             'DX_field_name': 'flaggedobject',
             },
    )
    r = migrateCustomAT(
        fields_mapping,
        src_type='File',
        dst_type='File')
    return r

def custom_image_migration():
    fields_mapping = (
            {'AT_field_name': 'image',
             'DX_field_name': 'image',
             'DX_field_type': 'NamedBlobImage',
             },
            {'AT_field_name': 'flaggedobject',
             'DX_field_name': 'flaggedobject',
             },
    )
    r = migrateCustomAT(
        fields_mapping,
        src_type='Image',
        dst_type='Image')
    return r

def custom_news_migration():
    fields_mapping = (
            {'AT_field_name': 'text',
             'DX_field_name': 'text',
             'DX_field_type': 'RichText',
             },
            {'AT_field_name': 'image',
             'DX_field_name': 'image',
             'DX_field_type': 'NamedBlobImage',
             },
            {'AT_field_name': 'imageCaption',
             'DX_field_name': 'imageCaption',
             },
            {'AT_field_name': 'flaggedobject',
             'DX_field_name': 'flaggedobject',
             },
    )
    r = migrateCustomAT(
        fields_mapping,
        src_type='News Item',
        dst_type='News Item')
    return r


import transaction
with api.env.adopt_user(username="admin"):
    custom_document_migration()
transaction.commit()
with api.env.adopt_user(username="admin"):
    custom_folder_migration()
transaction.commit()
with api.env.adopt_user(username="admin"):
    custom_file_migration()
transaction.commit()
with api.env.adopt_user(username="admin"):
    custom_image_migration()
transaction.commit()

Caveat:

  • there is a 'dry_run' parameter to the migrateCustomAT routine do NOT try to use it it does not work
  • custom fields (here 'flaggedobject') are not created automatically - I used the Dexterity TTW editor :-/ before running the code
  • committing between each migration is needed else duplicated id errors
  • I don't know why there are catalog error messages while committing folder handling, maybe these messages are harmless, maybe not.
  • I migrated collections with @@atct_migrator (collections are a bit special and simple code such as this will not work)
1 Like

@gp54321 Thanks!

For those interested: meanwhile I've created a dexterity behavior equivalent of collective.flag, collective.behavior.flag. Bit rough still, see https://github.com/zestsoftware/collective.behavior.flag , will probably move it to the collective later when it has stabilised.

I was a bit divided on re-using/extending collective.flag or creating a new package. I've kept the field name, and index name the same, the marker interface as well allthough it in behavior named package now. Have to test some migration scenario's. The tricky part with migration as you also mention is that the new/target dx fields have to exist before you migrate.