[Checkins] SVN: GenericSetup/branches/tseaver-bbq_sprint/ * now supports nested upgradeSteps and upgradeStep directives

Rob Miller ra at burningman.com
Thu Apr 12 21:42:28 EDT 2007


Log message for revision 74114:
  * now supports nested upgradeSteps and upgradeStep directives
  * upgrade system now tracks separate upgrade steps and version numbers for
    each profile
  * upgrade registry is a registry object (not just a dictionary)
  * ZMI changes to support profile selection 
  

Changed:
  U   GenericSetup/branches/tseaver-bbq_sprint/CHANGES.txt
  U   GenericSetup/branches/tseaver-bbq_sprint/meta.zcml
  U   GenericSetup/branches/tseaver-bbq_sprint/registry.py
  U   GenericSetup/branches/tseaver-bbq_sprint/tests/test_zcml.py
  U   GenericSetup/branches/tseaver-bbq_sprint/tool.py
  U   GenericSetup/branches/tseaver-bbq_sprint/upgrade.py
  U   GenericSetup/branches/tseaver-bbq_sprint/www/setup_upgrades.zpt
  A   GenericSetup/branches/tseaver-bbq_sprint/www/upgradeStep.zpt
  U   GenericSetup/branches/tseaver-bbq_sprint/zcml.py

-=-
Modified: GenericSetup/branches/tseaver-bbq_sprint/CHANGES.txt
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/CHANGES.txt	2007-04-12 20:02:42 UTC (rev 74113)
+++ GenericSetup/branches/tseaver-bbq_sprint/CHANGES.txt	2007-04-13 01:42:27 UTC (rev 74114)
@@ -2,6 +2,12 @@
 
   GenericSetup 1.3-beta (unreleased)
 
+    - Profiles now support version numbers; setup tool tracks profile
+      versions during upgrades.
+
+    - Added support for nested 'upgradeStep' directives; expanded upgrade
+      step registry into a real registry object and not just a dictionary.
+
     - Added support for 'metadata.xml' in the profile (read during
       profile registration) to register profile description, version,
       and dependencies.

Modified: GenericSetup/branches/tseaver-bbq_sprint/meta.zcml
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/meta.zcml	2007-04-12 20:02:42 UTC (rev 74113)
+++ GenericSetup/branches/tseaver-bbq_sprint/meta.zcml	2007-04-13 01:42:27 UTC (rev 74114)
@@ -5,17 +5,31 @@
   <meta:directives namespace="http://namespaces.zope.org/genericsetup">
 
     <meta:directive
-       name="registerProfile"
-       schema=".zcml.IRegisterProfileDirective"
-       handler=".zcml.registerProfile"
-       />
+        name="registerProfile"
+        schema=".zcml.IRegisterProfileDirective"
+        handler=".zcml.registerProfile"
+        />
 
     <meta:directive
-       name="upgradeStep"
-       schema=".zcml.IUpgradeStepDirective"
-       handler=".zcml.upgradeStep"
-       />
+        name="upgradeStep"
+        schema=".zcml.IUpgradeStepDirective"
+        handler=".zcml.upgradeStep"
+        />
 
+    <meta:complexDirective
+        name="upgradeSteps"
+        schema=".zcml.IUpgradeStepsDirective"
+        handler=".zcml.upgradeSteps"
+        >
+       
+      <meta:subdirective
+          name="upgradeStep"
+          schema=".zcml.IUpgradeStepsStepSubDirective"
+          />
+
+    </meta:complexDirective>
+        
+
   </meta:directives>
 
 </configure>

Modified: GenericSetup/branches/tseaver-bbq_sprint/registry.py
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/registry.py	2007-04-12 20:02:42 UTC (rev 74113)
+++ GenericSetup/branches/tseaver-bbq_sprint/registry.py	2007-04-13 01:42:27 UTC (rev 74114)
@@ -552,7 +552,7 @@
 
         self.clear()
 
-    security.declareProtected( ManagePortal, '' )
+    security.declareProtected( ManagePortal, 'getProfileInfo' )
     def getProfileInfo( self, profile_id, for_=None ):
 
         """ See IProfileRegistry.
@@ -616,7 +616,8 @@
 
         version = metadata.get( 'version', None )
         if version is None and product is not None:
-            prod_module = getattr(App.Product.Products, product, None)
+            prod_name = product.split('.')[-1]
+            prod_module = getattr(App.Product.Products, prod_name, None)
             if prod_module is not None:
                 prod_path = prod_module.__path__[0]
 

Modified: GenericSetup/branches/tseaver-bbq_sprint/tests/test_zcml.py
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/tests/test_zcml.py	2007-04-12 20:02:42 UTC (rev 74113)
+++ GenericSetup/branches/tseaver-bbq_sprint/tests/test_zcml.py	2007-04-13 01:42:27 UTC (rev 74114)
@@ -18,8 +18,17 @@
 import unittest
 import Testing
 from zope.testing import doctest
+from zope.testing.doctest import ELLIPSIS
 
+def dummy_upgrade_handler(context):
+    pass
 
+def b_dummy_upgrade_handler(context):
+    pass
+
+def c_dummy_upgrade_handler(context):
+    pass
+
 def test_registerProfile():
     """
     Use the genericsetup:registerProfile directive::
@@ -73,10 +82,155 @@
       False
     """
 
+def test_registerUpgradeStep(self):
+    """
+    Use the genericsetup:upgradeStep directive::
 
+      >>> import Products.GenericSetup
+      >>> from Products.Five import zcml
+      >>> configure_zcml = '''
+      ... <configure
+      ...     xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+      ...     i18n_domain="foo">
+      ...   <genericsetup:upgradeStep
+      ...       title="Upgrade Foo Product"
+      ...       description="Upgrades Foo from 1.0 to 1.1."
+      ...       source="1.0"
+      ...       destination="1.1"
+      ...       handler="Products.GenericSetup.tests.test_zcml.dummy_upgrade_handler"
+      ...       sortkey="1"
+      ...       profile="default"
+      ...       />
+      ... </configure>'''
+      >>> zcml.load_config('meta.zcml', Products.GenericSetup)
+      >>> zcml.load_string(configure_zcml)
+
+    Make sure the upgrade step is registered correctly::
+
+      >>> from Products.GenericSetup.upgrade import _upgrade_registry
+      >>> profile_steps = _upgrade_registry.getUpgradeStepsForProfile('default')
+      >>> keys = profile_steps.keys()
+      >>> len(keys)
+      1
+      >>> step = profile_steps[keys[0]]
+      >>> step.source
+      ('1', '0')
+      >>> step.dest
+      ('1', '1')
+      >>> step.handler
+      <function dummy_upgrade_handler at ...>
+
+    Clean up and make sure the cleanup works::
+
+      >>> from zope.testing.cleanup import cleanUp
+      >>> cleanUp()
+    """
+
+
+def test_registerUpgradeSteps(self):
+    """
+    Use the nested genericsetup:upgradeSteps directive::
+
+      >>> import Products.GenericSetup
+      >>> from Products.Five import zcml
+      >>> configure_zcml = '''
+      ... <configure
+      ...     xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+      ...     i18n_domain="foo">
+      ...   <genericsetup:upgradeSteps
+      ...       profile="default"
+      ...       source="1.0"
+      ...       destination="1.1"
+      ...       sortkey="2"
+      ...       >
+      ...       <genericsetup:upgradeStep
+      ...           title="Foo Upgrade Step 1"
+      ...           description="Does some Foo upgrade thing."
+      ...           handler="Products.GenericSetup.tests.test_zcml.b_dummy_upgrade_handler"
+      ...           />
+      ...       <genericsetup:upgradeStep
+      ...           title="Foo Upgrade Step 2"
+      ...           description="Does another Foo upgrade thing."
+      ...           handler="Products.GenericSetup.tests.test_zcml.c_dummy_upgrade_handler"
+      ...           />
+      ...   </genericsetup:upgradeSteps>
+      ...   <genericsetup:upgradeSteps
+      ...       profile="default"
+      ...       source="1.0"
+      ...       destination="1.1"
+      ...       sortkey="1"
+      ...       >
+      ...       <genericsetup:upgradeStep
+      ...           title="Bar Upgrade Step 1"
+      ...           description="Does some Foo upgrade thing."
+      ...           handler="Products.GenericSetup.tests.test_zcml.b_dummy_upgrade_handler"
+      ...           />
+      ...       <genericsetup:upgradeStep
+      ...           title="Bar Upgrade Step 2"
+      ...           description="Does another Foo upgrade thing."
+      ...           handler="Products.GenericSetup.tests.test_zcml.c_dummy_upgrade_handler"
+      ...           />
+      ...   </genericsetup:upgradeSteps>
+      ... </configure>'''
+      >>> zcml.load_config('meta.zcml', Products.GenericSetup)
+      >>> zcml.load_string(configure_zcml)
+
+    Make sure the upgrade steps are registered correctly::
+
+      >>> from Products.GenericSetup.upgrade import _upgrade_registry
+      >>> profile_steps = _upgrade_registry.getUpgradeStepsForProfile('default')
+      >>> keys = profile_steps.keys()
+      >>> len(keys)
+      2
+      >>> steps = profile_steps[keys[0]]
+      >>> type(steps)
+      <type 'list'>
+      >>> len(steps)
+      2
+      >>> (step1_id, step1), (step2_id, step2) = steps
+      >>> step1.source == step2.source == ('1', '0')
+      True
+      >>> step1.dest == step2.dest == ('1', '1')
+      True
+      >>> step1.handler
+      <function b_dummy_upgrade_handler at ...>
+      >>> step1.title
+      u'Bar Upgrade Step 1'
+      >>> step2.handler
+      <function c_dummy_upgrade_handler at ...>
+      >>> step2.title
+      u'Bar Upgrade Step 2'
+
+    First one listed should be second in the registry due to sortkey:
+
+      >>> steps = profile_steps[keys[1]]
+      >>> type(steps)
+      <type 'list'>
+      >>> len(steps)
+      2
+      >>> (step1_id, step1), (step2_id, step2) = steps
+      >>> step1.source == step2.source == ('1', '0')
+      True
+      >>> step1.dest == step2.dest == ('1', '1')
+      True
+      >>> step1.handler
+      <function b_dummy_upgrade_handler at ...>
+      >>> step1.title
+      u'Foo Upgrade Step 1'
+      >>> step2.handler
+      <function c_dummy_upgrade_handler at ...>
+      >>> step2.title
+      u'Foo Upgrade Step 2'
+
+    Clean up and make sure the cleanup works::
+
+      >>> from zope.testing.cleanup import cleanUp
+      >>> cleanUp()
+    """
+
 def test_suite():
     return unittest.TestSuite((
-        doctest.DocTestSuite(),
+        doctest.DocTestSuite(optionflags=ELLIPSIS),
         ))
 
 if __name__ == '__main__':

Modified: GenericSetup/branches/tseaver-bbq_sprint/tool.py
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/tool.py	2007-04-12 20:02:42 UTC (rev 74113)
+++ GenericSetup/branches/tseaver-bbq_sprint/tool.py	2007-04-13 01:42:27 UTC (rev 74114)
@@ -17,6 +17,7 @@
 
 import os
 import time
+import logging
 from warnings import warn
 from cgi import escape
 
@@ -29,8 +30,6 @@
 from zope.interface import implements
 from zope.interface import implementedBy
 
-from Products.CMFCore.utils import getToolByName
-
 from interfaces import BASE
 from interfaces import EXTENSION
 from interfaces import ISetupTool
@@ -48,6 +47,8 @@
 from registry import _profile_registry
 
 from upgrade import listUpgradeSteps
+from upgrade import listProfilesWithUpgrades
+from upgrade import _upgrade_registry
 
 from utils import _resolveDottedName
 from utils import _wwwdir
@@ -152,6 +153,8 @@
     # BBB _import_context_id is a vestige of a stateful import context
     _import_context_id = ''
 
+    _profile_upgrade_versions = {}
+
     security = ClassSecurityInfo()
 
     def __init__(self, id):
@@ -582,6 +585,9 @@
     security.declareProtected(ManagePortal, 'manage_upgrades')
     manage_upgrades = PageTemplateFile('setup_upgrades', _wwwdir)
 
+    security.declareProtected(ManagePortal, 'upgradeStepMacro')
+    upgradeStepMacro = PageTemplateFile('upgradeStep', _wwwdir)
+
     security.declareProtected(ManagePortal, 'manage_snapshots')
     manage_snapshots = PageTemplateFile('sutSnapshots', _wwwdir)
 
@@ -639,7 +645,6 @@
         ext.sort(lambda x, y: cmp(x['id'], y['id']))
         return base + ext
 
-
     security.declareProtected(ManagePortal, 'listContextInfos')
     def listContextInfos(self):
 
@@ -744,31 +749,92 @@
     #
     # Upgrades management
     #
-    security.declarePrivate('_getCurrentVersion')
-    def _getCurrentVersion(self):
-        # XXX this should return the current version of the
-        # appropriate profile.. need to define what this means
-        return None
+    security.declareProtected(ManagePortal, 'getLastVersionForProfile')
+    def getLastVersionForProfile(self, profile_id):
+        """Return the last upgraded version for the specified profile.
+        """
+        version = self._profile_upgrade_versions.get(profile_id, 'unknown')
+        return version
 
+    security.declareProtected(ManagePortal, 'setLastVersionForProfile')
+    def setLastVersionForProfile(self, profile_id, version):
+        """Set the last upgraded version for the specified profile.
+        """
+        if isinstance(version, basestring):
+            version = tuple(version.split('.'))
+        prof_versions = self._profile_upgrade_versions.copy()
+        prof_versions[profile_id] = version
+        self._profile_upgrade_versions = prof_versions
+
+    security.declareProtected(ManagePortal, 'getVersionForProfile')
+    def getVersionForProfile(self, profile_id):
+        """Return the registered filesystem version for the specified
+        profile.
+        """
+        info = _profile_registry.getProfileInfo(profile_id)
+        return info.get('version', 'unknown')
+
+    security.declareProtected(ManagePortal, 'listProfilesWithUpgrades')
+    def listProfilesWithUpgrades(self):
+        return listProfilesWithUpgrades()
+
+    security.declarePrivate('_massageUpgradeInfo')
+    def _massageUpgradeInfo(self, info):
+        """Add a couple of data points to the upgrade info dictionary.
+        """
+        info = info.copy()
+        info['haspath'] = info['source'] and info['dest']
+        info['ssource'] = '.'.join(info['source'] or ('all',))
+        info['sdest'] = '.'.join(info['dest'] or ('all',))
+        return info
+
     security.declareProtected(ManagePortal, 'listUpgrades')
-    def listUpgrades(self, show_old=False):
+    def listUpgrades(self, profile_id, show_old=False):
         """Get the list of available upgrades.
         """
-        portal = getToolByName(self, 'portal_url').getPortalObject()
         if show_old:
             source = None
         else:
-            source = self._getCurrentVersion()
-        upgrades = listUpgradeSteps(portal, source)
+            source = self.getLastVersionForProfile(profile_id)
+        upgrades = listUpgradeSteps(self, profile_id, source)
         res = []
         for info in upgrades:
-            info = info.copy()
-            info['haspath'] = info['source'] and info['dest']
-            info['ssource'] = '.'.join(info['source'] or ('all',))
-            info['sdest'] = '.'.join(info['dest'] or ('all',))
-            res.append(info)
+            if type(info) == list:
+                subset = []
+                for subinfo in info:
+                    subset.append(self._massageUpgradeInfo(subinfo))
+                res.append(subset)
+            else:
+                res.append(self._massageUpgradeInfo(info))
         return res
 
+    security.declareProtected(ManagePortal, 'manage_doUpgrades')
+    def manage_doUpgrades(self, request=None):
+        """Perform all selected upgrade steps.
+        """
+        if request is None:
+            request = self.REQUEST
+        logger = logging.getLogger('GenericSetup')
+        steps_to_run = request.form.get('upgrades', [])
+        profile_id = request.get('profile_id', '')
+        for step_id in steps_to_run:
+            step = _upgrade_registry.getUpgradeStep(profile_id, step_id)
+            if step is not None:
+                step.doStep(self)
+                msg = "Ran upgrade step %s for profile %s" % (step.title,
+                                                              profile_id)
+                logger.log(logging.INFO, msg)
+
+        # XXX should be a bit smarter about deciding when to up the
+        #     profile version
+        profile_info = _profile_registry.getProfileInfo(profile_id)
+        version = profile_info.get('version', None)
+        if version is not None:
+            self.setLastVersionForProfile(profile_id, version)
+
+        url = self.absolute_url()
+        request.RESPONSE.redirect("%s/manage_upgrades?saved=%s" % (url, profile_id))
+
     #
     #   Helper methods
     #

Modified: GenericSetup/branches/tseaver-bbq_sprint/upgrade.py
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/upgrade.py	2007-04-12 20:02:42 UTC (rev 74113)
+++ GenericSetup/branches/tseaver-bbq_sprint/upgrade.py	2007-04-13 01:42:27 UTC (rev 74114)
@@ -11,12 +11,67 @@
 #
 ##############################################################################
 
-_upgrade_registry = {} # id -> step
+from BTrees.OOBTree import OOBTree
 
+from registry import _profile_registry
+
+class UpgradeRegistry(object):
+    """Registry of upgrade steps, by profile.
+    
+    Registry keys are profile ids.
+
+    Each registry value is a nested mapping:
+      - id -> step for single steps
+      - id -> [ (id1, step1), (id2, step2) ] for nested steps
+    """
+    def __init__(self):
+        self._registry = OOBTree()
+
+    def __getitem__(self, key):
+        return self._registry.get(key)
+
+    def keys(self):
+        return self._registry.keys()
+
+    def clear(self):
+        self._registry.clear()
+
+    def getUpgradeStepsForProfile(self, profile_id):
+        """Return the upgrade steps mapping for a given profile, or
+        None if there are no steps registered for a profile matching
+        that id.
+        """
+        profile_steps = self._registry.get(profile_id, None)
+        if profile_steps is None:
+            self._registry[profile_id] = OOBTree()
+            profile_steps = self._registry.get(profile_id)
+        return profile_steps
+
+    def getUpgradeStep(self, profile_id, step_id):
+        """Returns the specified upgrade step for the specified
+        profile, or None if it doesn't exist.
+        """
+        profile_steps = self._registry.get(profile_id, None)
+        if profile_steps is not None:
+            step = profile_steps.get(step_id, None)
+            if step is None:
+                for key in profile_steps.keys():
+                    if type(profile_steps[key]) == list:
+                        subs = dict(profile_steps[key])
+                        step = subs.get(step_id, None)
+                        if step is not None:
+                            break
+            elif type(step) == list:
+                subs = dict(step)
+                step = subs.get(step_id, None)
+            return step
+
+_upgrade_registry = UpgradeRegistry()
+
 class UpgradeStep(object):
     """A step to upgrade a component.
     """
-    def __init__(self, title, profile, source, dest, handler,
+    def __init__(self, title, profile, source, dest, desc, handler,
                  checker=None, sortkey=0):
         self.id = str(abs(hash('%s%s%s%s' % (title, source, dest, sortkey))))
         self.title = title
@@ -30,17 +85,18 @@
         elif isinstance(dest, basestring):
             dest = tuple(dest.split('.'))
         self.dest = dest
+        self.description = desc
         self.handler = handler
         self.checker = checker
         self.sortkey = sortkey
         self.profile = profile
 
-    def versionMatch(self, portal, source):
+    def versionMatch(self, source):
         return (source is None or
                 self.source is None or
                 source <= self.source)
 
-    def isProposed(self, portal, source):
+    def isProposed(self, tool, source):
         """Check if a step can be applied.
 
         False means already applied or does not apply.
@@ -48,35 +104,73 @@
         """
         checker = self.checker
         if checker is None:
-            return self.versionMatch(portal, source)
+            return self.versionMatch(source)
         else:
-            return checker(portal)
+            return checker(tool)
 
-    def doStep(self, portal):
-        self.handler(portal)
+    def doStep(self, tool):
+        self.handler(tool)
 
 def _registerUpgradeStep(step):
-    _upgrade_registry[step.id] = step
+    profile_id = step.profile
+    profile_steps = _upgrade_registry.getUpgradeStepsForProfile(profile_id)
+    profile_steps[step.id] = step
 
-def listUpgradeSteps(portal, source):
-    """Lists upgrade steps available from a given version.
+def _registerNestedUpgradeStep(step, outer_id):
+    profile_id = step.profile
+    profile_steps = _upgrade_registry.getUpgradeStepsForProfile(profile_id)
+    nested_steps = profile_steps.get(outer_id, [])
+    nested_steps.append((step.id, step))
+    profile_steps[outer_id] = nested_steps
+
+def _extractStepInfo(tool, id, step, source):
+    """Returns the info data structure for a given step.
     """
+    proposed = step.isProposed(tool, source)
+    if (not proposed
+        and source is not None
+        and (step.source is None or source > step.source)):
+        return
+    info = {
+        'id': id,
+        'step': step,
+        'title': step.title,
+        'source': step.source,
+        'dest': step.dest,
+        'description': step.description,
+        'proposed': proposed,
+        'sortkey': step.sortkey,
+        }
+    return info
+
+def listProfilesWithUpgrades():
+    return _upgrade_registry.keys()
+
+def listUpgradeSteps(tool, profile_id, source):
+    """Lists upgrade steps available from a given version, for a given
+    profile id.
+    """
     res = []
-    for id, step in _upgrade_registry.items():
-        proposed = step.isProposed(portal, source)
-        if (not proposed
-            and source is not None
-            and (step.source is None or source > step.source)):
-            continue
-        info = {
-            'id': id,
-            'step': step,
-            'title': step.title,
-            'source': step.source,
-            'dest': step.dest,
-            'proposed': proposed,
-            }
-        res.append(((step.source or '', step.sortkey, proposed), info))
+    profile_steps = _upgrade_registry.getUpgradeStepsForProfile(profile_id)
+    for id, step in profile_steps.items():
+        if type(step) == UpgradeStep:
+            info = _extractStepInfo(tool, id, step, source)
+            if info is None:
+                continue
+            res.append(((step.source or '', step.sortkey, info['proposed']), info))
+        else: # nested steps
+            nested = []
+            outer_proposed = False
+            for inner_id, inner_step in step:
+                info = _extractStepInfo(tool, inner_id, inner_step, source)
+                if info is None:
+                    continue
+                nested.append(info)
+                outer_proposed = outer_proposed or info['proposed']
+            if nested:
+                src = nested[0]['source']
+                sortkey = nested[0]['sortkey']
+                res.append(((src or '', sortkey, outer_proposed), nested))
     res.sort()
     res = [i[1] for i in res]
     return res

Modified: GenericSetup/branches/tseaver-bbq_sprint/www/setup_upgrades.zpt
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/www/setup_upgrades.zpt	2007-04-12 20:02:42 UTC (rev 74113)
+++ GenericSetup/branches/tseaver-bbq_sprint/www/setup_upgrades.zpt	2007-04-13 01:42:27 UTC (rev 74114)
@@ -1,19 +1,49 @@
+<html tal:define="profile_id request/saved | request/profile_id | nothing;
+                  prof_w_upgrades context/listProfilesWithUpgrades">
+
 <h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1>
 <h2 tal:replace="structure context/manage_tabs">TABS</h2>
 
+<strong tal:condition="python:request.form.has_key('saved')">
+  <span tal:replace="request/saved" /> profile saved.
+</strong>
+
 <h3>Upgrades</h3>
 
+<tal:choose-profile condition="prof_w_upgrades">
+  <form method="post" action="manage_upgrades">
+    <select name="profile_id">
+      <option tal:repeat="prof_id context/listProfilesWithUpgrades"
+              tal:content="prof_id"
+              tal:attributes="selected python:prof_id == profile_id"/>
+    </select>
+    <input type="submit" value="Choose Profile" />
+  </form>
+</tal:choose-profile>
+
+<strong tal:condition="not: prof_w_upgrades">
+  No profiles with registered upgrade steps.
+</strong>
+
+<tal:profile-specified condition="profile_id">
+
 <p class="form-help">
-  The portal is currently upgraded to version
-  <strong tal:define="portal context/portal_url/getPortalObject"
-          tal:content="python:portal.getProperty('last_upgraded_version')
-                              or 'unknown'">
-    VERSION
+  The profile "<span tal:replace="profile_id" />" is currently upgraded to version
+  <strong tal:define="version python:context.getLastVersionForProfile(profile_id)"
+          tal:content="python:test(same_type(version, tuple()), '.'.join(version), version)">
+    LAST UPGRADED VERSION
   </strong>.
 </p>
 
+<p class="form-help">
+  The filesystem version for the "<span tal:replace="profile_id" />" profile is currently
+  <strong tal:content="python:context.getVersionForProfile(profile_id)">
+    CURRENT FILESYSTEM VERSION
+  </strong>.
+</p>
+
 <tal:block define="show_old request/show_old | python:0;
-                   upgrades python:context.listUpgrades(show_old=show_old)">
+                   upgrades python:context.listUpgrades(profile_id, show_old=show_old)">
 
 <form method="post" action="manage_doUpgrades" tal:condition="upgrades">
 <p class="form-help">
@@ -21,26 +51,27 @@
 </p>
 <input type="hidden" name="show_old:int" value="VALUE"
        tal:attributes="value show_old" />
+<input type="hidden" name="profile_id" value="VALUE"
+       tal:attributes="value profile_id" />
 <table>
-  <tr valign="top" tal:repeat="info upgrades">
-    <td>
-      <input type="checkbox" name="upgrades:list"
-             value="VALUE" checked="CHECKED"
-             tal:attributes="value info/id;
-                             checked python:info['proposed'] and not show_old;
-                             "/>
-    </td>
-    <td>
-      <div tal:replace="info/title">INFO</div>
-    </td>
-    <td class="form-help">
-      <div tal:condition="info/haspath"
-           tal:content="structure string:(${info/ssource} &amp;#8594; ${info/sdest})">PATH</div>
-    </td>
-    <td class="form-help">
-      <div tal:condition="not:info/proposed"
-           tal:replace="default">(done)</div>
-    </td>
+  <tr valign="top" tal:repeat="upgrade_info upgrades">
+
+    <tal:single condition="python:not same_type(upgrade_info, [])"
+                define="info upgrade_info">
+      <metal:insert-step use-macro="context/upgradeStepMacro/macros/upgrade-step" />
+    </tal:single>
+
+    <tal:multiple condition="python: same_type(upgrade_info, [])">
+      <table>
+        <tr>
+          <td colspan="5">Upgrade Step Group</td>
+        </tr>
+        <tr tal:repeat="info upgrade_info">
+          <td>-></td>
+          <metal:insert-step use-macro="context/upgradeStepMacro/macros/upgrade-step" />
+        </tr>
+      </table>
+    </tal:multiple>
   </tr>
 
   <tr valign="top">
@@ -60,10 +91,15 @@
   Show old upgrades:
   <input type="submit" value="Show" />
   <input type="hidden" name="show_old:int" value="1" />
+  <input type="hidden" name="profile_id" value="VALUE"
+         tal:attributes="value profile_id" />
 </p>
 </form>
 
-
 </tal:block>
 
+</tal:profile-specified>
+
 <h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1>
+
+</html>

Added: GenericSetup/branches/tseaver-bbq_sprint/www/upgradeStep.zpt
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/www/upgradeStep.zpt	2007-04-12 20:02:42 UTC (rev 74113)
+++ GenericSetup/branches/tseaver-bbq_sprint/www/upgradeStep.zpt	2007-04-13 01:42:27 UTC (rev 74114)
@@ -0,0 +1,24 @@
+<html>
+
+  <metal:upgrade-step define-macro="upgrade-step">
+    <td>
+      <input type="checkbox" name="upgrades:list"
+             value="VALUE" checked="CHECKED"
+             tal:attributes="value info/id;
+                             checked python:info['proposed'] and not show_old;
+                             "/>
+    </td>
+    <td>
+      <div tal:replace="info/title">INFO</div>
+    </td>
+    <td class="form-help">
+      <div tal:condition="info/haspath"
+           tal:content="structure string:(${info/ssource} &amp;#8594; ${info/sdest})">PATH</div>
+    </td>
+    <td class="form-help">
+      <div tal:condition="not:info/proposed"
+           tal:replace="default">(done)</div>
+    </td>
+  </metal:upgrade-step>
+
+</html>


Property changes on: GenericSetup/branches/tseaver-bbq_sprint/www/upgradeStep.zpt
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: GenericSetup/branches/tseaver-bbq_sprint/zcml.py
===================================================================
--- GenericSetup/branches/tseaver-bbq_sprint/zcml.py	2007-04-12 20:02:42 UTC (rev 74113)
+++ GenericSetup/branches/tseaver-bbq_sprint/zcml.py	2007-04-13 01:42:27 UTC (rev 74114)
@@ -23,8 +23,8 @@
 
 from interfaces import BASE
 from registry import _profile_registry
+from upgrade import _upgrade_registry
 
-
 #### genericsetup:registerProfile
 
 class IRegisterProfileDirective(Interface):
@@ -88,14 +88,12 @@
 import zope.schema
 from upgrade import UpgradeStep
 from upgrade import _registerUpgradeStep
+from upgrade import _registerNestedUpgradeStep
 
-class IUpgradeStepDirective(Interface):
-    """Register an upgrade setup.
+class IUpgradeStepsDirective(Interface):
     """
-    title = zope.schema.TextLine(
-        title=u"Title",
-        required=True)
-
+    Define multiple upgrade steps without repeating all of the parameters
+    """
     source = zope.schema.ASCII(
         title=u"Source version",
         required=False)
@@ -112,6 +110,18 @@
         title=u"GenericSetup profile id",
         required=True)
 
+class IUpgradeStepsStepSubDirective(Interface):
+    """
+    Subdirective to IUpgradeStepsDirective
+    """
+    title = zope.schema.TextLine(
+        title=u"Title",
+        required=True)
+
+    description = zope.schema.TextLine(
+        title=u"Upgrade step description",
+        required=True)
+
     handler = GlobalObject(
         title=u"Upgrade handler",
         required=True)
@@ -120,17 +130,50 @@
         title=u"Upgrade checker",
         required=False)
 
-def upgradeStep(_context, title, profile, handler, source='*', destination='*',
-                sortkey=0, checker=None):
-    step = UpgradeStep(title, profile, source, destination, handler, checker,
-                       sortkey)
+class IUpgradeStepDirective(IUpgradeStepsDirective, IUpgradeStepsStepSubDirective):
+    """
+    Define multiple upgrade steps without repeating all of the parameters
+    """
+
+
+def upgradeStep(_context, title, profile, handler, description=None, source='*',
+                destination='*', sortkey=0, checker=None):
+    step = UpgradeStep(title, profile, source, destination, description, handler,
+                       checker, sortkey)
     _context.action(
         discriminator = ('upgradeStep', source, destination, handler, sortkey),
         callable = _registerUpgradeStep,
         args = (step,),
         )
 
+class upgradeSteps(object):
+    """
+    Allows nested upgrade steps.
+    """
+    def __init__(self, _context, profile, source='*', destination='*', sortkey=0):
+        self.profile = profile
+        self.source = source
+        self.dest = destination
+        self.sortkey = sortkey
+        self.id = None
 
+    def upgradeStep(self, _context, title, description, handler, checker=None):
+        step = UpgradeStep(title, self.profile, self.source, self.dest, description,
+                           handler, checker, self.sortkey)
+        if self.id is None:
+            self.id = str(abs(hash('%s%s%s%s' % (title, self.source, self.dest,
+                                                 self.sortkey))))
+        _context.action(
+            discriminator = ('upgradeStep', self.source, self.dest, handler,
+                             self.sortkey),
+            callable = _registerNestedUpgradeStep,
+            args = (step, self.id),
+            )
+
+    def __call__(self):
+        return ()
+
+
 #### cleanup
 
 def cleanUp():
@@ -140,6 +183,9 @@
         _profile_registry._profile_ids.remove(profile_id)
     _profile_regs = []
 
+    _upgrade_registry.clear()
+
+
 from zope.testing.cleanup import addCleanUp
 addCleanUp(cleanUp)
 del addCleanUp



More information about the Checkins mailing list