From 665b0f4c26f5b21eeefce7380571664b70acd77f Mon Sep 17 00:00:00 2001 From: Martin Aspeli Date: Wed, 30 Dec 2009 14:53:08 +0000 Subject: [PATCH] Add UID updater section. --- docs/HISTORY.txt | 3 + setup.py | 1 + src/plone/app/transmogrifier/configure.zcml | 6 ++ src/plone/app/transmogrifier/tests.py | 63 +++++++++++++++- src/plone/app/transmogrifier/uidupdater.py | 57 +++++++++++++++ src/plone/app/transmogrifier/uidupdater.txt | 79 +++++++++++++++++++++ 6 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/plone/app/transmogrifier/uidupdater.py create mode 100644 src/plone/app/transmogrifier/uidupdater.txt diff --git a/docs/HISTORY.txt b/docs/HISTORY.txt index 59b98c2..d280863 100644 --- a/docs/HISTORY.txt +++ b/docs/HISTORY.txt @@ -6,6 +6,9 @@ Change History 1.1 (unreleased) ================ +- Added UID updated section. See uidupdater.txt. + [optilude] + - Fixed tests for Plone 4, in the same way that they were fixed in collective.transmogrifier. [optilude] diff --git a/setup.py b/setup.py index 92d396e..429d1ed 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ def read(*rnames): 'Detailed Documentation', '**********************', '', read('src', 'plone', 'app', 'transmogrifier', 'atschemaupdater.txt'), '', + read('src', 'plone', 'app', 'transmogrifier', 'uidupdater.txt'), '', read('src', 'plone', 'app', 'transmogrifier', 'workflowupdater.txt'), '', read('src', 'plone', 'app', 'transmogrifier', 'browserdefault.txt'), '', read('src', 'plone', 'app', 'transmogrifier', 'criteria.txt'), '', diff --git a/src/plone/app/transmogrifier/configure.zcml b/src/plone/app/transmogrifier/configure.zcml index f8f19df..8624554 100644 --- a/src/plone/app/transmogrifier/configure.zcml +++ b/src/plone/app/transmogrifier/configure.zcml @@ -37,4 +37,10 @@ name="plone.app.transmogrifier.mimeencapsulator" /> + + + diff --git a/src/plone/app/transmogrifier/tests.py b/src/plone/app/transmogrifier/tests.py index afbeb67..58e4e1d 100644 --- a/src/plone/app/transmogrifier/tests.py +++ b/src/plone/app/transmogrifier/tests.py @@ -319,6 +319,64 @@ def __iter__(self): provideUtility(OFSFilePrinter, name=u'plone.app.transmogrifier.tests.ofsfileprinter') +def uidSetUp(test): + sectionsSetUp(test) + + from Products.Archetypes.interfaces import IReferenceable + + class MockReferenceableObject(object): + implements(IReferenceable) + + def __init__(self, path, portal): + self.path = path + self.portal = portal + + _at_uid = 'xyz' + def UID(self): + return self._at_uid + + def _setUID(self, uid): + self.portal.uids_set.append((self.path, uid)) + self._at_uid = uid + + class MockPortal(object): + implements(IReferenceable) + + _last_path = None + def unrestrictedTraverse(self, path, default): + if path[0] == '/': + return default # path is absolute + if isinstance(path, unicode): + return default + if path == 'not/existing/bar': + return default + if path.endswith('/notatcontent'): + return object() + return MockReferenceableObject(path, self) + + uids_set = [] + + test.globs['plone'] = MockPortal() + test.globs['transmogrifier'].context = test.globs['plone'] + + class UIDSource(SampleSource): + classProvides(ISectionBlueprint) + implements(ISection) + + def __init__(self, *args, **kw): + super(UIDSource, self).__init__(*args, **kw) + self.sample = ( + dict(_path='/spam/eggs/foo', _uid='abc',), # will be set + dict(_path='/spam/eggs/bar', _uid='xyz',), # same as default + dict(_path='not/existing/bar', _uid='def',), # not found + dict( _uid='geh',), # no path + dict(_path='/spam/eggs/baz', ), # no uid + dict(_path='/spam/notatcontent', _uid='ijk',), # not referenceable + ) + provideUtility(UIDSource, + name=u'plone.app.transmogrifier.tests.uidsource') + + def test_suite(): return unittest.TestSuite(( doctest.DocFileSuite( @@ -327,6 +385,9 @@ def test_suite(): doctest.DocFileSuite( 'atschemaupdater.txt', setUp=aTSchemaUpdaterSetUp, tearDown=tearDown), + doctest.DocFileSuite( + 'uidupdater.txt', + setUp=uidSetUp, tearDown=tearDown), doctest.DocFileSuite( 'workflowupdater.txt', setUp=workflowUpdaterSetUp, tearDown=tearDown), @@ -340,5 +401,5 @@ def test_suite(): 'criteria.txt', setUp=criteriaSetUp, tearDown=tearDown), doctest.DocFileSuite( 'mimeencapsulator.txt', - setUp=mimeencapsulatorSetUp, tearDown=tearDown), + setUp=mimeencapsulatorSetUp, tearDown=tearDown), )) diff --git a/src/plone/app/transmogrifier/uidupdater.py b/src/plone/app/transmogrifier/uidupdater.py new file mode 100644 index 0000000..91d8f86 --- /dev/null +++ b/src/plone/app/transmogrifier/uidupdater.py @@ -0,0 +1,57 @@ +from zope.interface import classProvides, implements + +from collective.transmogrifier.interfaces import ISectionBlueprint +from collective.transmogrifier.interfaces import ISection +from collective.transmogrifier.utils import Matcher +from collective.transmogrifier.utils import defaultKeys + +from Products.Archetypes.interfaces import IReferenceable +from Products.Archetypes.config import UUID_ATTR + +class UIDUpdaterSection(object): + classProvides(ISectionBlueprint) + implements(ISection) + + def __init__(self, transmogrifier, name, options, previous): + self.previous = previous + self.context = transmogrifier.context + + if 'path-key' in options: + pathkeys = options['path-key'].splitlines() + else: + pathkeys = defaultKeys(options['blueprint'], name, 'path') + self.pathkey = Matcher(*pathkeys) + + if 'uid-key' in options: + uidkeys = options['uid-key'].splitlines() + else: + uidkeys = defaultKeys(options['blueprint'], name, 'uid') + self.uidkey = Matcher(*uidkeys) + + + def __iter__(self): + + for item in self.previous: + + pathkey = self.pathkey(*item.keys())[0] + uidkey = self.uidkey(*item.keys())[0] + + if not pathkey or not uidkey: # not enough info + yield item; continue + + path = item[pathkey] + uid = item[uidkey] + + obj = self.context.unrestrictedTraverse(path.lstrip('/'), None) + if obj is None: # path doesn't exist + yield item; continue + + if IReferenceable.providedBy(obj): + oldUID = obj.UID() + if oldUID != uid: + if not oldUID: + setattr(obj, UUID_ATTR, uid) + else: + obj._setUID(uid) + + yield item diff --git a/src/plone/app/transmogrifier/uidupdater.txt b/src/plone/app/transmogrifier/uidupdater.txt new file mode 100644 index 0000000..f20beab --- /dev/null +++ b/src/plone/app/transmogrifier/uidupdater.txt @@ -0,0 +1,79 @@ +UID updater section +=================== + +If an Archetypes content object is created in a pipeline, e.g. by the standard +content constructor section, it will get a new UID. If you are importing +content from another Plone site, and you have references (or links embedded +in content using Plone's link-by-UID feature) to existing content, you may +want to retain UIDs. The UID updater section allows you to set the UID on an +existing object for this purpose. + +The UID updater blueprint name is ``plone.app.transmogrifier.uidupdater``. + +UID updating requires two pieces of information: the path to the object +to update, and the new UID to set. + +To determine the path, the UID updater section inspects each item and looks +for a path key, as described below. Any item missing this key will be skipped. +Similarly, items with a path that doesn't exist or are not referenceable +(Archetypes) objects will be skipped. + +The object path will be found under the first key found among the following: + +* ``_plone.app.transmogrifier.atschemaupdater_[sectionname]_path`` +* ``_plone.app.transmogrifier.atschemaupdater_path`` +* ``_[sectionname]_path`` +* ``_path`` + +where ``[sectionname]`` is replaced with the name given to the current +section. This allows you to target the right section precisely if +needed. + +Alternatively, you can specify what key to use for the path by specifying the +``path-key`` option, which should be a list of keys to try (one key per line; +use a ``re:`` or ``regexp:`` prefix to specify regular expressions). + +Paths to objects are always interpreted as relative to the context. + +Similarly, the UID to set must be a string under a given key. You can set the +key with the ``uid-key`` option, which behaves much like ``path-key``. The +default is to look under: + +* ``_plone.app.transmogrifier.atschemaupdater_[sectionname]_uid`` +* ``_plone.app.transmogrifier.atschemaupdater_uid`` +* ``_[sectionname]_uid`` +* ``_uid`` + +If the UID key is missing, the item will be skipped. + +Below is an example of a standard updater. The test uid source produces +items with two keys: a path under ``_path`` and a UID string under ``_uid``. + + >>> import pprint + >>> atschema = """ + ... [transmogrifier] + ... pipeline = + ... schemasource + ... schemaupdater + ... printer + ... + ... [schemasource] + ... blueprint = plone.app.transmogrifier.tests.uidsource + ... + ... [schemaupdater] + ... blueprint = plone.app.transmogrifier.uidupdater + ... + ... [printer] + ... blueprint = collective.transmogrifier.sections.tests.pprinter + ... """ + >>> registerConfig(u'plone.app.transmogrifier.tests.uid', atschema) + >>> transmogrifier(u'plone.app.transmogrifier.tests.uid') + [('_path', '/spam/eggs/foo'), ('_uid', 'abc')] + [('_path', '/spam/eggs/bar'), ('_uid', 'xyz')] + [('_path', 'not/existing/bar'), ('_uid', 'def')] + [('_uid', 'geh')] + [('_path', '/spam/eggs/baz')] + [('_path', '/spam/notatcontent'), ('_uid', 'ijk')] + + >>> pprint.pprint(plone.uids_set) + [('spam/eggs/foo', 'abc')]