Upload
clayton-parker
View
6.019
Download
2
Embed Size (px)
DESCRIPTION
Transmogrifier is a migration framework that can help you easily migrate from one platform to another. It has been written in a way that allows re-use of migration code through blueprints. In this talk we will walk through the steps necessary to migrate from Drupal, a popular CMS written in PHP, into Plone. We will see how to use the various blueprints available to build a pipeline that prepares and imports the content into Plone
Citation preview
Clayton Parker, Senior Developer
Migrating From Drupal to Plone with Transmogrifier
PLONE SYMPOSIUM EAST 2011
PLONE SYMPOSIUM EAST 2011Who Am I?
• claytron
• Python dev since 2003
• Plone Core Committer
• Foundation Member
PLONE SYMPOSIUM EAST 2011What will we learn?
• Transmogrifier basics
• Migration Planning Process
• Creating a pipeline
PLONE SYMPOSIUM EAST 2011Migrations
• One off scripts
• In multiple places
• Little to no re-usability
PLONE SYMPOSIUM EAST 2011Transmogrifier
• A framework for migrations
• Re-usable parts
PLONE SYMPOSIUM EAST 2011Basics
• Pipeline
• Blueprints
• Sources
• Section
PLONE SYMPOSIUM EAST 2011Sources
• A blueprint
• First item in your pipeline
PLONE SYMPOSIUM EAST 2011
<html><body><h3>Code Sample</h3><p>Replace this text!</p></body></html>
Example Pipeline[transmogrifier]pipeline = csv_file constructor schemaupdater
[csv_file]blueprint = collective.transmogrifier.sections.csvsourcefilename = my.migration.import:my_items.csv
[constructor]blueprint = collective.transmogrifier.sections.constructor
[schemaupdater]blueprint = plone.app.transmogrifier.atschemaupdater
PLONE SYMPOSIUM EAST 2011my_items.csv
_path , _type , title , description/folder1 , Folder , First Folder , This is folder One/folder2 , Folder , Second Folder , This is folder Two/folder1/foo , Document , One Foo , A document named foo/folder2/foo , Document , Two Foo , Another doc named foo
PLONE SYMPOSIUM EAST 2011The Result
PLONE SYMPOSIUM EAST 2011Items
• Each item is a mapping
• Keys are fields
• Keys with a leading underscore are controllers
PLONE SYMPOSIUM EAST 2011Example Item{'_id': 'a-stronger-connection-to-out-customers', '_path': 'content/stronger-connection-out-customers', '_status': 1L, '_text_mimetype': 'text/html', '_transitions': 'publish', '_type': 'Document', 'allowDiscussion': False, 'creation_date': '2011/05/14 9:20:50', 'effectiveDate': '2011/05/14 9:20:50', 'modification_date': '2011/05/18 9:22:22', 'subject': 'better\ninteresting\nstronger', 'text': '<p>this is some text</p>\r\n', 'title': 'A stronger connection to out customers'}
PLONE SYMPOSIUM EAST 2011Migration Strategy
• Investigate the source
• Prepare the destination
• Find Transmogrifier blueprints
• Write the pipeline
PLONE SYMPOSIUM EAST 2011Write your own
• Missing blueprint
• Write one
• Contribute it back
PLONE SYMPOSIUM EAST 2011GenericSetup
• Make migration part of your release
• Ability to package migrations
PLONE SYMPOSIUM EAST 2011My Migration
• Drupal backed by MySQL
• transmogrify.sqlalchemy
• Plone 4.0.5
• collective.blog.star
• plone.app.discussion
PLONE SYMPOSIUM EAST 2011Package Layoutmy.migration!"" my# !"" __init__.py# %"" migration# !"" __init__.py# !"" config# # !"" articles.cfg# # !"" base.cfg# # !"" blogs.cfg# # !"" comments.cfg# # %"" pages.cfg# !"" configure.zcml# %"" profiles# %"" default# !"" metadata.xml# %"" transmogrifier.txt!"" setup.cfg%"" setup.py
PLONE SYMPOSIUM EAST 2011Registering Configs
<transmogrifier:registerConfig name="my.migration.base" title="My migration base config" description="Base settings for all transmogrifier imports" configuration="config/base.cfg" />
PLONE SYMPOSIUM EAST 2011Registering Configs
<transmogrifier:registerConfig name="my.migration.pages" title="Drupal pages" description="Import pages from Drupal into Plone" configuration="config/pages.cfg" />
PLONE SYMPOSIUM EAST 2011Registering Configs
<transmogrifier:registerConfig name="my.migration.articles" title="Drupal articles" description="Import articles from Drupal into Plone" configuration="config/articles.cfg" />
PLONE SYMPOSIUM EAST 2011Registering Configs
<transmogrifier:registerConfig name="my.migration.blogs" title="Drupal blog entries" description="Import blog entries from Drupal into Plone" configuration="config/blogs.cfg" />
PLONE SYMPOSIUM EAST 2011Registering Configs
<transmogrifier:registerConfig name="my.migration.comments" title="Drupal comments" description="Import comments from Drupal into Plone" configuration="config/comments.cfg" />
PLONE SYMPOSIUM EAST 2011transmogrifier.txt
my.migration.pagesmy.migration.articlesmy.migration.blogsmy.migration.comments
PLONE SYMPOSIUM EAST 2011Package Layoutmy.migration!"" my# !"" __init__.py# %"" migration# !"" __init__.py# !"" config# # !"" articles.cfg# # !"" base.cfg# # !"" blogs.cfg# # !"" comments.cfg# # %"" pages.cfg# !"" configure.zcml# %"" profiles# %"" default# !"" metadata.xml# %"" transmogrifier.txt!"" setup.cfg%"" setup.py
PLONE SYMPOSIUM EAST 2011base.cfg pipeline[transmogrifier]pipeline = drupal portal_type url_normalizer path publication_state text_mimetype mimetype_encapsulator folders constructor commenting comments schema_update workflow reindex_object
[settings]# Path to use if there isn’t one givenbase_path = other-content# Have to escape python string formatting for when # this gets passed into sqlalchemydate_format = %%Y/%%m/%%d %%k:%%i:%%s
PLONE SYMPOSIUM EAST 2011drupal section
[drupal]blueprint = transmogrify.sqlalchemydsn = mysql://user:password@localhost/drupal-transmog
PLONE SYMPOSIUM EAST 2011articles.cfg[transmogrifier]include = my.migration.base
[drupal]query = SELECT node.title, node.status AS status, GROUP_CONCAT(tag_data.name SEPARATOR '\n') AS subject, FROM_UNIXTIME(node.created, "${settings:date_format}") AS creation_date, FROM_UNIXTIME(node.created, "${settings:date_format}") AS effectiveDate, FROM_UNIXTIME(node.changed, "${settings:date_format}") AS modification_date, body_data.body_value AS text url_alias.alias AS _path FROM node INNER JOIN field_data_field_tags AS tag_field ON tag_field.entity_id = node.nid INNER JOIN taxonomy_term_data AS tag_data ON tag_data.tid = tag_field.field_tags_tid INNER JOIN field_data_body AS body_data ON body_data.entity_id = node.nid INNER JOIN url_alias ON url_alias.source = CONCAT("node/", node.nid) GROUP BY node.title, node.status, node.created, node.changed, body_data.body_value, url_alias.alias;
[portal_type]value = string:Document
PLONE SYMPOSIUM EAST 2011pages.cfg[transmogrifier]include = my.migration.base
[drupal]query = my.migration.base SELECT node.title, node.status AS _status, FROM_UNIXTIME(node.created, "${settings:date_format}") AS creation_date, FROM_UNIXTIME(node.created, "${settings:date_format}") AS effectiveDate, FROM_UNIXTIME(node.changed, "${settings:date_format}") AS modification_date, body_data.body_value AS text, url_alias.alias AS _path FROM node INNER JOIN field_data_body AS body_data ON body_data.entity_id = node.nid INNER JOIN url_alias ON url_alias.source = CONCAT("node/", node.nid) WHERE node.type = "page" GROUP BY node.title, node.status, node.created, node.changed, body_data.body_value, url_alias.alias;
[portal_type]value = string:Document
PLONE SYMPOSIUM EAST 2011blogs.cfg[transmogrifier]include = my.migration.base
[drupal]query = SELECT node.title, node.satus AS _status, FROM_UNIXTIME(node.created, "${settings:date_format}") AS creation_date, FROM_UNIXTIME(node.created, "${settings:date_format}") AS effectiveDate, FROM_UNIXTIME(node.created, "${settings:date_format}") AS modification_date, body_data.body_value AS text, url_alias.alias AS _path FROM node INNER JOIN field_data_body AS body_data ON body_data.entity_id = node.nid INNER JOIN url_alias ON url_alias.source = CONCAT("node/", node.nid) WHERE node.type = "blog" GROUP BY node.title, node.status, node.created, node.changed, body_data.body_value, url_alias.alias;
[portal_type]value = string:BlogEntry
[commenting]value = python:True
PLONE SYMPOSIUM EAST 2011comments.cfg[transmogrifier]include = my.migration.base
[drupal]query = my.migration.base SELECT comment.subject AS title, FROM_UNIXTIME(comment.created, "${settings:date_format}") AS published, FROM_UNIXTIME(comment.changed, "${settings:date_format}") AS updated, comment.name AS author_name, body_data.comment_body_value AS text, url_alias.alias AS _parent_path FROM comment INNER JOIN field_data_comment_body AS body_data ON body_data.entity_id = comment.cid INNER JOIN node ON node.nid = comment.nid INNER JOIN url_alias ON url_alias.source = CONCAT("node/", node.nid) GROUP BY comment.subject, comment.created, comment.changed, comment.name, body_data.comment_body_value, url_alias.alias;
[portal_type]# Override the portal type to use the "comment_type"key = string:_comment_typevalue = string:plone.app.discussion
PLONE SYMPOSIUM EAST 2011base.cfg overrides[path]blueprint = collective.transmogrifier.sections.inserter# only add a path if one does not existcondition = python:'_path' not in item and not '_parent_path' in itemkey = string:_path# Add the value in the extended configurationvalue = string:${settings:base_path}/${item/_id}
[portal_type]blueprint = collective.transmogrifier.sections.inserterkey = string:_type# We will add the value in the extended config, but we need a# default set herevalue = string:
[commenting]blueprint = collective.transmogrifier.sections.inserterkey = string:allowDiscussion# default to falsevalue = python:False
PLONE SYMPOSIUM EAST 2011Content Creation
[comments]blueprint = transmogrify.comments
[folders]blueprint = collective.transmogrifier.sections.folders
[constructor]blueprint = collective.transmogrifier.sections.constructor
[schema_update]blueprint = plone.app.transmogrifier.atschemaupdater
[workflow]blueprint = plone.app.transmogrifier.workflowupdater
[reindex_object]blueprint = plone.app.transmogrifier.reindexobject
PLONE SYMPOSIUM EAST 2011Item Modification[publication_state]blueprint = collective.transmogrifier.sections.insertercondition = python:'_status' in item and item['_status'] == 1key = string:_transitionsvalue = string:publish
[text_mimetype]# This could probably be taken from the database as wellblueprint = collective.transmogrifier.sections.inserterkey = string:_text_mimetypevalue = string:text/html
[mimetype_encapsulator]blueprint = plone.app.transmogrifier.mimeencapsulatorkey = textmimetype = python:item.get('_%s_mimetype', key)field = keycondition = mimetype
PLONE SYMPOSIUM EAST 2011
DEMO
PLONE SYMPOSIUM EAST 2011Links
• collective.transmogrifier
http://pypi.python.org/pypi/collective.transmogrifier/
• plone.app.transmogrifier
http://pypi.python.org/pypi/plone.app.transmogrifier/
PLONE SYMPOSIUM EAST 2011Useful Sources and Blueprints• plone.app.transmogrifier
• transmogrify.filesystem
• transmogrify.sqlalchemy
• transmogrify.webcrawler
• quintagroup.transmogrifier
• transmogrify.comments
Check out
sixfeetup.com/demos