[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} &#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} &#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