Quantcast
Channel: Planet Plone - Where Developers And Integrators Write
Viewing all articles
Browse latest Browse all 3535

Asko Soukka: Creating Plone content with Transmogrifier on Python 3

$
0
0

TL;DR; This blog post ends with minimal example of creating Plone 5.2 content with Python 3 compatible Transmogrifier pipeline with command line execution.

Years ago, I forked the famous Plone content migration tool Transmogrifier into a Plone independent and Python 3 compatible version, but never released the fork to avoid maintenance burden. Unfortunately, I was informed that my old examples of using my transmogrifier fork with Plone no longer worked, so I had to review the situation.

The resolution: I found that I had changed some of the built-in reusable blueprints after the post, I updated the old post, fixed a compatibility issue related to updates in Zope Component Architecture dependencies, and tested the results with the latest Plone 5.2 on Python 3.

Transmogrifying RSS into Plone

So, here goes a minimal example for creating Plone 5.2 content with Python 3 Transmogrifier pipeline using my fork:

At first ./buildout.cfg for the Plone instance:

[buildout]extends = http://dist.plone.org/release/5-latest/versions.cfgparts = instance plonesiteversions = versionsextensions = mr.developersources = sourcesauto-checkout = *[sources]transmogrifier = git https://github.com/collective/transmogrifier[instance]recipe = plone.recipe.zope2instanceeggs =    Plone    transmogrifieruser = admin:admin[plonesite]recipe = collective.recipe.plonesitesite-id = Ploneinstance = instance

Then buildout must be run to create the instance with a Plone site:

$ buildout

Next the transmogrifier ./pipeline.cfg must be created to define the pipeline:

[transmogrifier]pipeline =    from_rss    prepare    create    patch    commit[from_rss]blueprint = transmogrifier.frommodules = feedparserexpression = python:modules['feedparser'].parse(options['url']).get('entries', [])url = http://rss.slashdot.org/Slashdot/slashdot[prepare]blueprint = transmogrifier.setportal_type = string:Documentid = python:Nonetext = path:item/summary_container = python:context.get('slashdot') or modules['plone.api'].content.create(container=context, type='Folder', id='slashdot')[create]blueprint = transmogrifier.setmodules = plone.apiobject = python:modules['plone.api'].content.create(container=item.pop('_container'), type='Document', **item)[patch]blueprint = transmogrifier.transformmodules = plone.app.textfieldpatch = python:setattr(item['object'], 'text', modules['plone.app.textfield'].value.RichTextValue(item['object'].text, 'text/html', 'text/x-html-safe'))[commit]blueprint = transmogrifier.finallymodules = transactioncommit = modules['transaction'].commit()

Finally, the execution of transmogrifier with Plone site as its context (remember that this version of transmogrifier also works outside Plone ecosystem, but for a convenience transmogrify-script also supports calling with instance run):

$ bin/instance -OPlone run bin/transmogrify pipeline.cfg --context=zope.component.hooks.getSite

This example should result with the latest Slashdot posts in a Plone site. And, because this example is not perfect, running this again would create duplicates.

Transmogrifying JSON files into Plone

There’s never enough simple tutorials on how to build your own Transmogrifier pipelines from scratch. Especially now, when many old pipeline packages have not been ported to Python 3 yet.

In this example we configure a buildout with local custom Transmogrifier blueprints in python and use them to do minimal import from a JSON export generated using collective.jsonify, which is a one of many legacy ways to generate intermediate export. (That said, it might be good to know, that nowadays trivial migrations could be done with just Plone REST API and a little shell scripting.)

At first, we will define a ./buildout.cfg that expects a local directory ./local to contain a Python module ./local/custom and include ZCML configuration from ./local/custom/configure.zcml:

[buildout]extends = http://dist.plone.org/release/5-latest/versions.cfgparts = instance plonesiteversions = versionsextensions = mr.developersources = sourcesauto-checkout = *[sources]transmogrifier = git https://github.com/collective/transmogrifier[instance]recipe = plone.recipe.zope2instanceeggs =    Plone    transmogrifier    plone.restapiuser = admin:adminextra-paths = localzcml = custom[plonesite]recipe = collective.recipe.plonesitesite-id = Ploneinstance = instance

Before running buildout we ensure a proper local Python module structure with:

$ mkdir-p local/custom$ touch local/custom/__init__.py$ echo'<configure xmlns="http://namespaces.zope.org/zope" />'> local/custom/__init__.py

Only then we run buildout as usually:

$ buildout

Now, let’s populate our custom module with a Python module ./local/custom/blueprints.py defining a couple of custom blueprints:

# -*- coding: utf-8 -*-fromtransmogrifier.blueprintsimportBlueprintimportjsonimportpathlibclassGlob(Blueprint):"""Produce JSON items from files matching globbing from option `glob`."""def__iter__(self):foriteminself.previous:yielditemforpinpathlib.Path(".").glob(self.options["glob"]):withopen(p, encoding="utf-8") asfp:yieldjson.load(fp)classFolders(Blueprint):"""Minimal Folder item producer to ensure that items have containers."""def__iter__(self):context=self.transmogrifier.contextforiteminself.previous:parts= (item.get('_path') or'').strip('/').split('/')[:-1]path=''forpartinparts:path+='/'+parttry:context.restrictedTraverse(path)exceptKeyError:yield {"_path": path,"_type": "Folder","id": part                    }yielditem

And complete ZCML configuration at ./local/custom/configure.zcml with matching blueprint registrations:

<configurexmlns="http://namespaces.zope.org/zope"xmlns:transmogrifier="http://namespaces.plone.org/transmogrifier"><includepackage="transmogrifier"file="meta.zcml"/><transmogrifier:blueprintcomponent=".blueprints.Glob"name="custom.glob"/><transmogrifier:blueprintcomponent=".blueprints.Folders"name="custom.folders"/></configure>

Now, by using these two new blueprints and minimal content creating pipeline parts based on built-in expression blueprints, it is possible to:

  • generate new pipeline items from exported JSON files
  • inject folder items into pipeline to ensure that containers are created before items (because we cannot quarentee any order from the export)
  • create minimal Folder and Document objects with plone.api.
[transmogrifier]pipeline =    generate_from_json    generate_containers    set_container    create_folder    create_document    commit[generate_from_json]blueprint = custom.globglob = data/**/*.json[generate_containers]blueprint = custom.folders[set_container]blueprint = transmogrifier.set_container = python:context.restrictedTraverse(item["_path"].rsplit("/", 1)[0])[create_folder]blueprint = transmogrifier.setcondition = python:item.get("_type") =="Folder"modules = plone.api_object = python:modules["plone.api"].content.get(item["_path"]) or modules["plone.api"].content.create(container=item["_container"], type="Folder", id=item["id"])[create_document]blueprint = transmogrifier.setcondition = python:item.get("_type") =="Document"modules =  plone.api  plone.app.textfield_object = python:modules["plone.api"].content.get(item["_path"]) or modules["plone.api"].content.create(container=item["_container"], type="Document", id=item["id"], title=item["title"], text=modules['plone.app.textfield'].value.RichTextValue(item["text"], 'text/html', 'text/x-html-safe'))[commit]blueprint = transmogrifier.finallymodules = transactioncommit = modules['transaction'].commit()

Finally, the pipeline can be run and content imported with:

$ bin/instance -OPlone run bin/transmogrify pipeline.cfg --context=zope.component.hooks.getSite

Obviously, in a real migration, the pipeline parts [create_folder] and [create_document] should be implemented in Python to properly populate all metadata fields, handle possible exceptions, etc, but consider that as homework.


If this post raised more questions than gave answers, please, feel free to ask more at: https://github.com/collective/transmogrifier/issues.


Viewing all articles
Browse latest Browse all 3535

Trending Articles