[Checkins] SVN: Products.GenericSetup/trunk/Products/GenericSetup/ Merge r79921:HEAD from the non-eggified GenericSetup trunk

Wichert Akkerman wichert at wiggy.net
Sat Nov 10 03:59:57 EST 2007


Log message for revision 81673:
  Merge r79921:HEAD from the non-eggified GenericSetup trunk

Changed:
  U   Products.GenericSetup/trunk/Products/GenericSetup/CHANGES.txt
  A   Products.GenericSetup/trunk/Products/GenericSetup/events.py
  U   Products.GenericSetup/trunk/Products/GenericSetup/interfaces.py
  U   Products.GenericSetup/trunk/Products/GenericSetup/meta.zcml
  U   Products.GenericSetup/trunk/Products/GenericSetup/registry.py
  A   Products.GenericSetup/trunk/Products/GenericSetup/tests/test_events.py
  A   Products.GenericSetup/trunk/Products/GenericSetup/tests/test_stepzcml.py
  U   Products.GenericSetup/trunk/Products/GenericSetup/tests/test_tool.py
  U   Products.GenericSetup/trunk/Products/GenericSetup/tests/test_zcml.py
  U   Products.GenericSetup/trunk/Products/GenericSetup/tool.py
  U   Products.GenericSetup/trunk/Products/GenericSetup/utils.py
  U   Products.GenericSetup/trunk/Products/GenericSetup/www/sutExportSteps.zpt
  U   Products.GenericSetup/trunk/Products/GenericSetup/www/sutImportSteps.zpt
  U   Products.GenericSetup/trunk/Products/GenericSetup/zcml.py

-=-
Modified: Products.GenericSetup/trunk/Products/GenericSetup/CHANGES.txt
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/CHANGES.txt	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/CHANGES.txt	2007-11-10 08:59:57 UTC (rev 81673)
@@ -1,6 +1,14 @@
 GenericSetup Product Changelog
 
+  GenericSetup 1.4 (unreleased)
+  
+    - Fire events before and after importing.
 
+    - Use zcml to register import and export steps.
+
+    - tool: Fixed toolset import handler not to initialize tools again, when
+      they already exist in the site.
+
   GenericSetup 1.3.2 (unreleased)
 
     - Ignore import and export step handlers that we can not resolve.

Copied: Products.GenericSetup/trunk/Products/GenericSetup/events.py (from rev 81672, GenericSetup/trunk/events.py)
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/events.py	                        (rev 0)
+++ Products.GenericSetup/trunk/Products/GenericSetup/events.py	2007-11-10 08:59:57 UTC (rev 81673)
@@ -0,0 +1,18 @@
+from zope.interface import implements
+from Products.GenericSetup.interfaces import IBeforeProfileImportEvent
+from Products.GenericSetup.interfaces import IProfileImportedEvent
+
+class BaseProfileImportEvent(object):
+    def __init__(self, tool, profile_id, steps, full_import):
+        self.tool=tool
+        self.profile_id=profile_id
+        self.steps=steps
+        self.full_import=full_import
+
+
+class BeforeProfileImportEvent(BaseProfileImportEvent):
+    implements(IBeforeProfileImportEvent)
+
+
+class ProfileImportedEvent(BaseProfileImportEvent):
+    implements(IProfileImportedEvent)

Modified: Products.GenericSetup/trunk/Products/GenericSetup/interfaces.py
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/interfaces.py	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/interfaces.py	2007-11-10 08:59:57 UTC (rev 81673)
@@ -15,6 +15,7 @@
 $Id$
 """
 
+from zope.interface import Attribute
 from zope.interface import Interface
 from zope.schema import Text
 from zope.schema import TextLine
@@ -811,3 +812,27 @@
           method, whose headers (e.g., "Content-Type") may affect the
           processing of the body.
         """
+
+class IBeforeProfileImportEvent(Interface):
+    """ An event which is fired before (part of) a profile is imported.
+    """
+    profile_id = Attribute("id of the profile to be imported or None for non-profile imports.")
+
+    steps = Attribute("list of steps that will be imported")
+
+    full_import = Attribute("True if all steps will be imported")
+
+    tool = Attribute("The tool which is performing the import")
+
+
+class IProfileImportedEvent(Interface):
+    """ An event which is fired when (part of) a profile is imported.
+    """
+    profile_id = Attribute("id of the imported profile")
+
+    steps = Attribute("list of steps have been imported")
+
+    full_import = Attribute("True if all steps are imported")
+
+    tool = Attribute("The tool which is performing the import")
+

Modified: Products.GenericSetup/trunk/Products/GenericSetup/meta.zcml
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/meta.zcml	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/meta.zcml	2007-11-10 08:59:57 UTC (rev 81673)
@@ -11,6 +11,24 @@
         />
 
     <meta:directive
+        name="exportStep"
+        schema=".zcml.IExportStepDirective"
+        handler=".zcml.exportStep"
+        />
+    <meta:complexDirective
+        name="importStep"
+        schema=".zcml.IImportStepDirective"
+        handler=".zcml.importStep"
+        >
+
+      <meta:subdirective
+          name="depends"
+          schema=".zcml.IImportStepDependsDirective"
+          />
+
+    </meta:complexDirective>
+
+    <meta:directive
         name="upgradeStep"
         schema=".zcml.IUpgradeStepDirective"
         handler=".zcml.upgradeStep"

Modified: Products.GenericSetup/trunk/Products/GenericSetup/registry.py
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/registry.py	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/registry.py	2007-11-10 08:59:57 UTC (rev 81673)
@@ -36,6 +36,7 @@
 from utils import _getDottedName
 from utils import _resolveDottedName
 from utils import _extractDocstring
+from utils import _computeTopologicalSort
 
 
 class BaseStepRegistry( Implicit ):
@@ -109,6 +110,10 @@
 
         return _resolveDottedName( info[ 'handler' ] )
 
+    security.declarePrivate( 'unregisterStep' )
+    def unregisterStep( self, id ):
+        del self._registered[id]
+
     security.declarePrivate( 'clear' )
     def clear( self ):
 
@@ -249,50 +254,15 @@
     #
     security.declarePrivate( '_computeTopologicalSort' )
     def _computeTopologicalSort( self ):
+        return _computeTopologicalSort(self._registered.values())
 
-        result = []
 
-        graph = [ ( x[ 'id' ], x[ 'dependencies' ] )
-                    for x in self._registered.values() ]
-
-        unresolved = []
-
-        while 1:
-            for node, edges in graph:
-    
-                after = -1
-                resolved = 0
-    
-                for edge in edges:
-    
-                    if edge in result:
-                        resolved += 1
-                        after = max( after, result.index( edge ) )
-                
-                if len(edges) > resolved:
-                    unresolved.append((node, edges))
-                else:
-                    result.insert( after + 1, node )
-
-            if not unresolved:
-                break
-            if len(unresolved) == len(graph):
-                # Nothing was resolved in this loop. There must be circular or
-                # missing dependencies. Just add them to the end. We can't
-                # raise an error, because checkComplete relies on this method.
-                for node, edges in unresolved:
-                    result.append(node)
-                break
-            graph = unresolved
-            unresolved = []
-        
-        return result
-
     security.declarePrivate( '_exportTemplate' )
     _exportTemplate = PageTemplateFile( 'isrExport.xml', _xmldir )
 
 InitializeClass( ImportStepRegistry )
 
+_import_step_registry = ImportStepRegistry()
 
 class ExportStepRegistry( BaseStepRegistry ):
 
@@ -379,6 +349,9 @@
 
 InitializeClass( ExportStepRegistry )
 
+_export_step_registry = ExportStepRegistry()
+
+
 class ToolsetRegistry( Implicit ):
 
     """ Track required / forbidden tools.

Copied: Products.GenericSetup/trunk/Products/GenericSetup/tests/test_events.py (from rev 81672, GenericSetup/trunk/tests/test_events.py)
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/tests/test_events.py	                        (rev 0)
+++ Products.GenericSetup/trunk/Products/GenericSetup/tests/test_events.py	2007-11-10 08:59:57 UTC (rev 81673)
@@ -0,0 +1,43 @@
+import unittest
+from zope.interface.verify import verifyObject
+from Products.GenericSetup.events import BeforeProfileImportEvent
+from Products.GenericSetup.interfaces import IBeforeProfileImportEvent
+from Products.GenericSetup.events import ProfileImportedEvent
+from Products.GenericSetup.interfaces import IProfileImportedEvent
+
+class BaseEventTests(unittest.TestCase):
+    def testInterface(self):
+        event=self.klass("tool", "profile_id", "steps", "full_import")
+        verifyObject(self.iface, event)
+
+    def testNormalConstruction(self):
+        event=self.klass("tool", "profile_id", "steps", "full_import")
+        self.assertEqual(event.tool, "tool")
+        self.assertEqual(event.profile_id, "profile_id")
+        self.assertEqual(event.steps, "steps")
+        self.assertEqual(event.full_import, "full_import")
+
+    def testKeywordConstruction(self):
+        event=self.klass(tool="tool", profile_id="profile_id", steps="steps", full_import="full_import")
+        self.assertEqual(event.tool, "tool")
+        self.assertEqual(event.profile_id, "profile_id")
+        self.assertEqual(event.steps, "steps")
+        self.assertEqual(event.full_import, "full_import")
+
+
+class BeforeProfileImportEventTests(BaseEventTests):
+    klass = BeforeProfileImportEvent
+    iface = IBeforeProfileImportEvent
+
+
+class ProfileImportedEventTests(BaseEventTests):
+    klass = ProfileImportedEvent
+    iface = IProfileImportedEvent
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(BeforeProfileImportEventTests))
+    suite.addTest(unittest.makeSuite(ProfileImportedEventTests))
+    return suite
+

Copied: Products.GenericSetup/trunk/Products/GenericSetup/tests/test_stepzcml.py (from rev 81672, GenericSetup/trunk/tests/test_stepzcml.py)
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/tests/test_stepzcml.py	                        (rev 0)
+++ Products.GenericSetup/trunk/Products/GenericSetup/tests/test_stepzcml.py	2007-11-10 08:59:57 UTC (rev 81673)
@@ -0,0 +1,72 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Unit tests for import/export step zcml module.
+"""
+import unittest
+from Products.GenericSetup.zcml import cleanUpImportSteps
+import Products.GenericSetup
+from Products.GenericSetup.registry import _import_step_registry
+from Products.GenericSetup.testing import ExportImportZCMLLayer
+from Products.Five import zcml
+
+EMPTY_ZCML = '''<configure xmlns:genericsetup="http://namespaces.zope.org/genericsetup">
+</configure>'''
+
+ONE_STEP_ZCML = '''<configure xmlns:genericsetup="http://namespaces.zope.org/genericsetup" i18n_domain="genericsetup">
+<genericsetup:importStep
+    name="Products.GenericSetup.teststep"
+    title="step title"
+    description="step description"
+    handler="Products.GenericSetup.initialize"
+    version="1.0"
+    />
+</configure>'''
+
+class ImportStepTests(unittest.TestCase):
+    layer = ExportImportZCMLLayer
+
+    def setUp(self):
+        zcml.load_config('meta.zcml', Products.GenericSetup)
+
+    def tearDown(self):
+        cleanUpImportSteps()
+
+    def testEmptyImport(self):
+        zcml.load_string(EMPTY_ZCML)
+        self.assertEqual(_import_step_registry._registered, {})
+
+    def testOneStepImport(self):
+        zcml.load_string(ONE_STEP_ZCML)
+        self.assertEqual(_import_step_registry._registered.keys(),
+            [ u'Products.GenericSetup.teststep'  ])
+        info = _import_step_registry._registered[ u'Products.GenericSetup.teststep' ]
+        self.assertEqual( info['description'],
+                u'step description' )
+        self.assertEqual( info['title'],
+                u'step title' )
+        self.assertEqual( info['handler'],
+                'Products.GenericSetup.initialize')
+        self.assertEqual( info['version'],
+                u'1.0' )
+        self.assertEqual( info['id'],
+                u'Products.GenericSetup.teststep' )
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(ImportStepTests),
+        ))
+
+if __name__ == '__main__':
+    from Products.GenericSetup.testing import run
+    run(test_suite())

Modified: Products.GenericSetup/trunk/Products/GenericSetup/tests/test_tool.py
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/tests/test_tool.py	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/tests/test_tool.py	2007-11-10 08:59:57 UTC (rev 81673)
@@ -21,6 +21,10 @@
 
 from StringIO import StringIO
 
+from zope.component import adapter
+from zope.component import provideHandler
+from zope.component.globalregistry import base as base_registry
+
 from Acquisition import aq_base
 from OFS.Folder import Folder
 
@@ -38,7 +42,20 @@
 from common import TarballTester
 from conformance import ConformsToISetupTool
 
+from Products.GenericSetup.interfaces import IBeforeProfileImportEvent
+from Products.GenericSetup.interfaces import IProfileImportedEvent
 
+_before_import_events = []
+ at adapter(IBeforeProfileImportEvent)
+def handleBeforeProfileImportEvent(event):
+    _before_import_events.append(event)
+
+_after_import_events = []
+ at adapter(IProfileImportedEvent)
+def handleProfileImportedEvent(event):
+    _after_import_events.append(event)
+
+
 class SetupToolTests(FilesystemTestBase, TarballTester, ConformsToISetupTool):
 
     layer = ExportImportZCMLLayer
@@ -49,10 +66,18 @@
         self._profile_registry_info = profile_registry._profile_info
         self._profile_registry_ids = profile_registry._profile_ids
         profile_registry.clear()
+        global _before_import_events
+        global _after_import_events
+        _before_import_events = []
+        provideHandler(handleBeforeProfileImportEvent)
+        _after_import_events = []
+        provideHandler(handleProfileImportedEvent)
 
     def beforeTearDown(self):
         profile_registry._profile_info = self._profile_registry_info
         profile_registry._profile_ids = self._profile_registry_ids
+        base_registry.unregisterHandler(handleBeforeProfileImportEvent)
+        base_registry.unregisterHandler(handleProfileImportedEvent)
         FilesystemTestBase.beforeTearDown(self)
 
     def _getTargetClass(self):
@@ -192,6 +217,18 @@
 
         self.assertEqual( site.title, TITLE.upper() )
 
+        global _before_import_events
+        self.assertEqual( len(_before_import_events), 1)
+        self.assertEqual(_before_import_events[0].profile_id, '')
+        self.assertEqual(_before_import_events[0].steps, ['simple'])
+        self.assertEqual(_before_import_events[0].full_import, True)
+
+        global _after_import_events
+        self.assertEqual( len(_after_import_events), 1)
+        self.assertEqual(_after_import_events[0].profile_id, '')
+        self.assertEqual(_after_import_events[0].steps, ['simple'])
+        self.assertEqual(_after_import_events[0].full_import, True)
+
     def test_runImportStep_dependencies( self ):
 
         TITLE = 'original title'
@@ -217,6 +254,19 @@
                         , 'Uppercased title' )
         self.assertEqual( site.title, TITLE.replace( ' ', '_' ).upper() )
 
+        global _before_import_events
+        self.assertEqual( len(_before_import_events), 1)
+        self.assertEqual(_before_import_events[0].profile_id, '')
+        self.assertEqual(_before_import_events[0].steps, ['dependable', 'dependent'])
+        self.assertEqual(_before_import_events[0].full_import, True)
+
+        global _after_import_events
+        self.assertEqual( len(_after_import_events), 1)
+        self.assertEqual(_after_import_events[0].profile_id, '')
+        self.assertEqual(_after_import_events[0].steps, ['dependable', 'dependent'])
+        self.assertEqual(_after_import_events[0].full_import, True)
+
+
     def test_runImportStep_skip_dependencies( self ):
 
         TITLE = 'original title'
@@ -240,6 +290,18 @@
 
         self.assertEqual( site.title, TITLE.upper() )
 
+        global _before_import_events
+        self.assertEqual( len(_before_import_events), 1)
+        self.assertEqual(_before_import_events[0].profile_id, '')
+        self.assertEqual(_before_import_events[0].steps, ['dependent'])
+        self.assertEqual(_before_import_events[0].full_import, False)
+
+        global _after_import_events
+        self.assertEqual( len(_after_import_events), 1)
+        self.assertEqual(_after_import_events[0].profile_id, '')
+        self.assertEqual(_after_import_events[0].steps, ['dependent'])
+        self.assertEqual(_after_import_events[0].full_import, False)
+
     def test_runImportStep_default_purge( self ):
 
         site = self._makeSite()

Modified: Products.GenericSetup/trunk/Products/GenericSetup/tests/test_zcml.py
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/tests/test_zcml.py	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/tests/test_zcml.py	2007-11-10 08:59:57 UTC (rev 81673)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2006 Zope Corporation and Contributors. All Rights Reserved.
+# Copyright (c) 2006-2007 Zope Corporation and Contributors. All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
@@ -20,6 +20,19 @@
 from zope.testing import doctest
 from zope.testing.doctest import ELLIPSIS
 
+from Products.GenericSetup.testing import ExportImportZCMLLayer
+from Products.GenericSetup.zcml import cleanUpImportSteps
+from Products.GenericSetup.zcml import cleanUpExportSteps
+from Products.GenericSetup.registry import _import_step_registry
+from Products.GenericSetup.registry import _export_step_registry
+from Products.Five import zcml
+
+def dummy_importstep_handler(context):
+    pass
+
+def dummy_exportstep_handler(context):
+    pass
+
 def dummy_upgrade_handler(context):
     pass
 
@@ -230,10 +243,93 @@
       >>> cleanUp()
     """
 
+
+class ImportStepTests(unittest.TestCase):
+    layer = ExportImportZCMLLayer
+
+    def tearDown(self):
+        cleanUpImportSteps()
+
+    def testNoDependencies(self):
+        zcml.load_string("""<configure
+                              xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+                              i18n_domain="genericsetup">
+                             <genericsetup:importStep
+                                 name="name"
+                                 title="title"
+                                 description="description"
+                                 handler="Products.GenericSetup.tests.test_zcml.dummy_importstep_handler"
+                                 version="version">
+                             </genericsetup:importStep>
+                            </configure>""")
+        from Products.GenericSetup.zcml import _import_step_regs
+        self.assertEqual(_import_step_regs, [u'name'])
+        self.assertEqual( _import_step_registry.listSteps(), [u'name'])
+        data=_import_step_registry.getStepMetadata(u'name')
+        self.assertEqual(data["handler"],
+                'Products.GenericSetup.tests.test_zcml.dummy_importstep_handler')
+        self.assertEqual(data["description"], u"description")
+        self.assertEqual(data["version"], u"version")
+        self.assertEqual(data["title"], u"title")
+        self.assertEqual(data["dependencies"], ())
+        self.assertEqual(data["id"], u"name")
+
+
+    def testWithDependency(self):
+        zcml.load_string("""<configure
+                              xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+                              i18n_domain="genericsetup">
+                             <genericsetup:importStep
+                                 name="name"
+                                 title="title"
+                                 description="description"
+                                 handler="Products.GenericSetup.tests.test_zcml.dummy_importstep_handler"
+                                 version="version">
+                                <depends name="something.else"/>
+                             </genericsetup:importStep>
+                            </configure>""")
+        data=_import_step_registry.getStepMetadata(u'name')
+        self.assertEqual(data["dependencies"], (u"something.else",))
+
+
+
+class ExportStepTests(unittest.TestCase):
+    layer = ExportImportZCMLLayer
+
+    def tearDown(self):
+        cleanUpExportSteps()
+
+    def testRegistration(self):
+        zcml.load_string("""<configure
+                              xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+                              i18n_domain="genericsetup">
+                             <genericsetup:exportStep
+                                 name="name"
+                                 title="title"
+                                 description="description"
+                                 handler="Products.GenericSetup.tests.test_zcml.dummy_exportstep_handler"
+                                 />
+                              </configure>
+                              """)
+        from Products.GenericSetup.zcml import _export_step_regs
+        self.assertEqual(_export_step_regs, [u'name'])
+        self.assertEqual( _export_step_registry.listSteps(), [u'name'])
+        data=_export_step_registry.getStepMetadata(u'name')
+        self.assertEqual(data["handler"],
+                'Products.GenericSetup.tests.test_zcml.dummy_exportstep_handler')
+        self.assertEqual(data["description"], u"description")
+        self.assertEqual(data["title"], u"title")
+        self.assertEqual(data["id"], u"name")
+
+
 def test_suite():
-    return unittest.TestSuite((
-        doctest.DocTestSuite(optionflags=ELLIPSIS),
-        ))
+    suite = unittest.TestSuite()
+    suite.addTest(doctest.DocTestSuite(optionflags=ELLIPSIS))
+    suite.addTest(unittest.makeSuite(ImportStepTests))
+    suite.addTest(unittest.makeSuite(ExportStepTests))
+    return suite
 
+    return suite
+
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')

Modified: Products.GenericSetup/trunk/Products/GenericSetup/tool.py
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/tool.py	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/tool.py	2007-11-10 08:59:57 UTC (rev 81673)
@@ -27,14 +27,18 @@
 from OFS.Folder import Folder
 from OFS.Image import File
 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
+from ZODB.POSException import ConflictError
 from zope.interface import implements
 from zope.interface import implementedBy
+from zope import event 
 
 from interfaces import BASE
 from interfaces import EXTENSION
 from interfaces import ISetupTool
 from interfaces import SKIPPED_FILES
 from permissions import ManagePortal
+from events import BeforeProfileImportEvent
+from events import ProfileImportedEvent
 from context import DirectoryImportContext
 from context import SnapshotImportContext
 from context import TarballExportContext
@@ -45,6 +49,8 @@
 from registry import ExportStepRegistry
 from registry import ToolsetRegistry
 from registry import _profile_registry
+from registry import _import_step_registry
+from registry import _export_step_registry
 
 from upgrade import listUpgradeSteps
 from upgrade import listProfilesWithUpgrades
@@ -53,6 +59,7 @@
 from utils import _getDottedName
 from utils import _resolveDottedName
 from utils import _wwwdir
+from utils import _computeTopologicalSort
 
 IMPORT_STEPS_XML = 'import_steps.xml'
 EXPORT_STEPS_XML = 'export_steps.xml'
@@ -105,23 +112,25 @@
         tool_class = _resolveDottedName(info['class'])
 
         existing = getattr(aq_base(site), tool_id, None)
-        try:
-            new_tool = tool_class()
-        except TypeError:
-            new_tool = tool_class(tool_id)
-        else:
+        # Don't even initialize the tool again, if it already exists.
+        if existing is None:
             try:
-                new_tool._setId(tool_id)
-            except: # XXX:  ImmutableId raises result of calling MessageDialog
-                pass
+                new_tool = tool_class()
+            except TypeError:
+                new_tool = tool_class(tool_id)
+            else:
+                try:
+                    new_tool._setId(tool_id)
+                except (ConflictError, KeyboardInterrupt):
+                    raise
+                except:
+                    # XXX: ImmutableId raises result of calling MessageDialog
+                    pass
 
-        if existing is None:
             site._setObject(tool_id, new_tool)
-
         else:
             unwrapped = aq_base(existing)
             if not isinstance(unwrapped, tool_class):
-
                 site._delObject(tool_id)
                 site._setObject(tool_id, tool_class())
 
@@ -247,6 +256,47 @@
         """
         return self._export_registry
 
+
+    security.declareProtected(ManagePortal, 'getExportStep')
+    def getExportStep(self, step, default=None):
+        """Simple wrapper to query both the global and local step registry."""
+        res=_export_step_registry.getStep(step, default)
+        if res is not default:
+            return res
+        return self._export_registry.getStep(step, default)
+
+
+    security.declareProtected(ManagePortal, 'listExportSteps')
+    def listExportSteps(self):
+        steps = _export_step_registry._registered.keys() + \
+                self._export_registry._registered.keys()
+        return steps
+
+
+    security.declareProtected(ManagePortal, 'getImportStep')
+    def getImportStep(self, step, default=None):
+        """Simple wrapper to query both the global and local step registry."""
+        res=_import_step_registry.getStep(step, default)
+        if res is not default:
+            return res
+        return self._import_registry.getStep(step, default)
+
+
+    security.declareProtected(ManagePortal, 'getSortedImportSteps')
+    def getSortedImportSteps(self):
+        steps = _import_step_registry._registered.values() + \
+                self._import_registry._registered.values()
+        return _computeTopologicalSort(steps)
+    
+    security.declareProtected(ManagePortal, 'getImportStep')
+    def getImportStepMetadata(self, step, default=None):
+        """Simple wrapper to query both the global and local step registry."""
+        res=_import_step_registry.getStepMetadata(step, default)
+        if res is not default:
+            return res
+        return self._import_registry.getStepMetadata(step, default)
+
+
     security.declareProtected(ManagePortal, 'getToolsetRegistry')
     def getToolsetRegistry(self):
 
@@ -264,7 +314,7 @@
 
         self.applyContext(context)
 
-        info = self._import_registry.getStepMetadata(step_id)
+        info = self.getImportStepMetadata(step_id)
 
         if info is None:
             self._import_context_id = old_context
@@ -274,22 +324,28 @@
 
         messages = {}
         steps = []
+
         if run_dependencies:
             for dependency in dependencies:
-
                 if dependency not in steps:
-                    message = self._doRunImportStep(dependency, context)
-                    messages[dependency] = message or ''
                     steps.append(dependency)
+        steps.append (step_id)
 
-        message = self._doRunImportStep(step_id, context)
+        full_import=(set(steps)==set(self.getSortedImportSteps()))
+        event.notify(BeforeProfileImportEvent(self, profile_id, steps, full_import))
+
+        for step in steps:
+            message = self._doRunImportStep(step, context)
+            messages[step] = message or ''
+
         message_list = filter(None, [message])
         message_list.extend( ['%s: %s' % x[1:] for x in context.listNotes()] )
         messages[step_id] = '\n'.join(message_list)
-        steps.append(step_id)
 
         self._import_context_id = old_context
 
+        event.notify(ProfileImportedEvent(self, profile_id, steps, full_import))
+
         return { 'steps' : steps, 'messages' : messages }
 
     security.declareProtected(ManagePortal, 'runImportStep')
@@ -316,7 +372,7 @@
         old_context = self._import_context_id
         context = self._getImportContext(profile_id, purge_old)
 
-        result = self._runImportStepsFromContext(context, purge_old=purge_old)
+        result = self._runImportStepsFromContext(context, purge_old=purge_old, profile_id=profile_id)
         prefix = 'import-all-%s' % profile_id.replace(':', '_')
         name = self._mangleTimestampName(prefix, 'log')
         self._createReport(name, result['steps'], result['messages'])
@@ -349,7 +405,7 @@
 
         """ See ISetupTool.
         """
-        return self._doRunExportSteps(self._export_registry.listSteps())
+        return self._doRunExportSteps(self.listExportSteps())
 
     security.declareProtected(ManagePortal, 'createSnapshot')
     def createSnapshot(self, snapshot_id):
@@ -358,7 +414,7 @@
         """
         context = SnapshotExportContext(self, snapshot_id)
         messages = {}
-        steps = self._export_registry.listSteps()
+        steps = self.listExportSteps()
 
         for step_id in steps:
 
@@ -968,7 +1024,7 @@
         __traceback_info__ = step_id
         marker = object()
 
-        handler = self._import_registry.getStep(step_id)
+        handler = self.getImportStep(step_id)
 
         if handler is marker:
             raise ValueError('Invalid import step: %s' % step_id)
@@ -1012,13 +1068,14 @@
                }
 
     security.declarePrivate('_runImportStepsFromContext')
-    def _runImportStepsFromContext(self, context, steps=None, purge_old=None):
+    def _runImportStepsFromContext(self, context, steps=None, purge_old=None, profile_id=None):
         self.applyContext(context)
 
         if steps is None:
-            steps = self._import_registry.sortSteps()
+            steps = self.getSortedImportSteps()
         messages = {}
 
+        event.notify(BeforeProfileImportEvent(self, profile_id, steps, True))
         for step in steps:
             message = self._doRunImportStep(step, context)
             message_list = filter(None, [message])
@@ -1027,6 +1084,8 @@
             messages[step] = '\n'.join(message_list)
             context.clearNotes()
 
+        event.notify(ProfileImportedEvent(self, profile_id, steps, True))
+
         return { 'steps' : steps, 'messages' : messages }
 
     security.declarePrivate('_mangleTimestampName')

Modified: Products.GenericSetup/trunk/Products/GenericSetup/utils.py
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/utils.py	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/utils.py	2007-11-10 08:59:57 UTC (rev 81673)
@@ -788,3 +788,41 @@
     if getattr(obj, 'objectValues', False):
         for sub in obj.objectValues():
             importObjects(sub, path+'/', context)
+
+
+def _computeTopologicalSort( steps ):
+    result = []
+    graph = [ ( x[ 'id' ], x[ 'dependencies' ] ) for x in steps ]
+
+    unresolved = []
+
+    while 1:
+        for node, edges in graph:
+
+            after = -1
+            resolved = 0
+
+            for edge in edges:
+
+                if edge in result:
+                    resolved += 1
+                    after = max( after, result.index( edge ) )
+            
+            if len(edges) > resolved:
+                unresolved.append((node, edges))
+            else:
+                result.insert( after + 1, node )
+
+        if not unresolved:
+            break
+        if len(unresolved) == len(graph):
+            # Nothing was resolved in this loop. There must be circular or
+            # missing dependencies. Just add them to the end. We can't
+            # raise an error, because checkComplete relies on this method.
+            for node, edges in unresolved:
+                result.append(node)
+            break
+        graph = unresolved
+        unresolved = []
+    
+    return result

Modified: Products.GenericSetup/trunk/Products/GenericSetup/www/sutExportSteps.zpt
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/www/sutExportSteps.zpt	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/www/sutExportSteps.zpt	2007-11-10 08:59:57 UTC (rev 81673)
@@ -25,12 +25,11 @@
   </tr>
  </thead>
 
- <tbody tal:define="registry here/getExportStepRegistry;
-                    step_ids registry/listSteps;
+ <tbody tal:define="step_ids here/listExportSteps;
                    ">
   <tal:loop tal:repeat="step_id step_ids">
   <tr valign="top"
-      tal:define="info python: registry.getStepMetadata( step_id );"
+      tal:define="info python: here.getExportStepMetadata( step_id );"
       tal:attributes="class python:
                      repeat[ 'step_id' ].odd and 'row-normal' or 'row-hilite'" >
    <td class="list-item" width="16">

Modified: Products.GenericSetup/trunk/Products/GenericSetup/www/sutImportSteps.zpt
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/www/sutImportSteps.zpt	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/www/sutImportSteps.zpt	2007-11-10 08:59:57 UTC (rev 81673)
@@ -55,12 +55,11 @@
   </tr>
  </thead>
 
- <tbody tal:define="registry context/getImportStepRegistry;
-                    step_ids registry/sortSteps;
+ <tbody tal:define="step_ids context/getSortedImportSteps;
                    ">
   <tal:loop tal:repeat="step_id step_ids">
   <tr valign="top"
-      tal:define="info python: registry.getStepMetadata( step_id );"
+      tal:define="info python: context.getImportStepMetadata( step_id );"
       tal:attributes="class python:
                      repeat[ 'step_id' ].odd and 'row-normal' or 'row-hilite';
                      style python:info['invalid'] and 'background: red' or None" >

Modified: Products.GenericSetup/trunk/Products/GenericSetup/zcml.py
===================================================================
--- Products.GenericSetup/trunk/Products/GenericSetup/zcml.py	2007-11-10 08:47:32 UTC (rev 81672)
+++ Products.GenericSetup/trunk/Products/GenericSetup/zcml.py	2007-11-10 08:59:57 UTC (rev 81673)
@@ -1,6 +1,6 @@
-##############################################################################
+
 #
-# Copyright (c) 2006 Zope Corporation and Contributors. All Rights Reserved.
+# Copyright (c) 2006-2007 Zope Corporation and Contributors. All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
@@ -22,6 +22,8 @@
 from zope.interface import Interface
 
 from interfaces import BASE
+from registry import _import_step_registry
+from registry import _export_step_registry
 from registry import _profile_registry
 from upgrade import _upgrade_registry
 
@@ -83,6 +85,110 @@
         )
 
 
+#### genericsetup:exportStep
+
+class IExportStepDirective(Interface):
+    name = PythonIdentifier(
+        title=u'Name',
+        description=u'',
+        required=True)
+
+    title = MessageID(
+        title=u'Title',
+        description=u'',
+        required=True)
+
+    description = MessageID(
+        title=u'Description',
+        description=u'',
+        required=True)
+
+    handler = GlobalObject(
+        title=u'Handler',
+        description=u'',
+        required=True)
+
+
+_export_step_regs = []
+
+def exportStep(context, name, handler, title=None, description=None):
+    global _export_step_regs
+    _export_step_regs.append(name)
+
+    context.action(
+        discriminator = ('exportStep', name),
+        callable = _export_step_registry.registerStep,
+        args = (name, handler, title, description),
+        )
+#### genericsetup:importStep
+
+class IImportStepDirective(Interface):
+
+    """Register import steps with the global registry.
+    """
+
+    name = PythonIdentifier(
+        title=u'Name',
+        description=u'',
+        required=True)
+
+    title = MessageID(
+        title=u'Title',
+        description=u'',
+        required=True)
+
+    description = MessageID(
+        title=u'Description',
+        description=u'',
+        required=True)
+
+    handler = GlobalObject(
+        title=u'Handler',
+        description=u'',
+        required=True)
+
+    version = MessageID(
+        title=u'Version',
+        description=u'',
+        required=True)
+
+class IImportStepDependsDirective(Interface):
+    name = PythonIdentifier(
+        title=u'Name',
+        description=u'Name of another import step that has to be run first',
+        required=True)
+
+_import_step_regs = []
+
+class importStep:
+    def __init__(self, context, name, version, title, description, handler):
+        """ Add a new import step to the registry.
+        """
+        self.context=context
+        self.discriminator = ('importStep', name),
+        self.name=name
+        self.version=version
+        self.handler=handler
+        self.title=title
+        self.description=description
+        self.dependencies=()
+
+
+    def depends(self, context, name):
+        self.dependencies+=(name,)
+
+
+    def __call__(self):
+        global _import_step_regs
+        _import_step_regs.append(self.name)
+
+        self.context.action(
+            discriminator = self.discriminator,
+            callable = _import_step_registry.registerStep,
+            args = (self.name, self.version, self.handler, self.dependencies,
+                        self.title, self.description),
+            )
+
 #### genericsetup:upgradeStep
 
 import zope.schema
@@ -176,7 +282,7 @@
 
 #### cleanup
 
-def cleanUp():
+def cleanUpProfiles():
     global _profile_regs
     for profile_id in _profile_regs:
         del _profile_registry._profile_info[profile_id]
@@ -186,6 +292,28 @@
     _upgrade_registry.clear()
 
 
+def cleanUpImportSteps():
+    global _import_step_regs
+    for name in  _import_step_regs:
+        try:
+             _import_step_registry.unregisterStep( name )
+        except KeyError:
+            pass
+
+    _import_step_regs=[]
+
+def cleanUpExportSteps():
+    global _export_step_regs
+    for name in  _export_step_regs:
+        try:
+             _export_step_registry.unregisterStep( name )
+        except KeyError:
+            pass
+
+    _export_step_regs=[]
+
 from zope.testing.cleanup import addCleanUp
-addCleanUp(cleanUp)
+addCleanUp(cleanUpProfiles)
+addCleanUp(cleanUpImportSteps)
+addCleanUp(cleanUpExportSteps)
 del addCleanUp



More information about the Checkins mailing list