[Checkins] SVN: z3c.wizard/trunk/ implemented new z3c.form based wizard
Roger Ineichen
roger at projekt01.ch
Mon Aug 25 11:37:05 EDT 2008
Log message for revision 90223:
implemented new z3c.form based wizard
Changed:
A z3c.wizard/trunk/CHANGES.txt
A z3c.wizard/trunk/README.txt
A z3c.wizard/trunk/bootstrap.py
A z3c.wizard/trunk/buildout.cfg
A z3c.wizard/trunk/setup.py
A z3c.wizard/trunk/src/
A z3c.wizard/trunk/src/z3c/
A z3c.wizard/trunk/src/z3c/__init__.py
A z3c.wizard/trunk/src/z3c/wizard/
A z3c.wizard/trunk/src/z3c/wizard/README.txt
A z3c.wizard/trunk/src/z3c/wizard/__init__.py
A z3c.wizard/trunk/src/z3c/wizard/browser.zcml
A z3c.wizard/trunk/src/z3c/wizard/button.py
A z3c.wizard/trunk/src/z3c/wizard/configure.zcml
A z3c.wizard/trunk/src/z3c/wizard/interfaces.py
A z3c.wizard/trunk/src/z3c/wizard/meta.zcml
A z3c.wizard/trunk/src/z3c/wizard/step.pt
A z3c.wizard/trunk/src/z3c/wizard/step.py
A z3c.wizard/trunk/src/z3c/wizard/testing.py
A z3c.wizard/trunk/src/z3c/wizard/tests.py
A z3c.wizard/trunk/src/z3c/wizard/wizard.pt
A z3c.wizard/trunk/src/z3c/wizard/wizard.py
A z3c.wizard/trunk/src/z3c/wizard/zcml.py
A z3c.wizard/trunk/src/z3c/wizard/zcml.txt
-=-
Added: z3c.wizard/trunk/CHANGES.txt
===================================================================
--- z3c.wizard/trunk/CHANGES.txt (rev 0)
+++ z3c.wizard/trunk/CHANGES.txt 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,9 @@
+=======
+CHANGES
+=======
+
+
+Version 0.5.0dev (unreleased)
+-----------------------------
+
+- Initial Release
Property changes on: z3c.wizard/trunk/CHANGES.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/README.txt
===================================================================
--- z3c.wizard/trunk/README.txt (rev 0)
+++ z3c.wizard/trunk/README.txt 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1 @@
+This package provides a form wizard concept based on z3c.form for Zope3.
\ No newline at end of file
Property changes on: z3c.wizard/trunk/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/bootstrap.py
===================================================================
--- z3c.wizard/trunk/bootstrap.py (rev 0)
+++ z3c.wizard/trunk/bootstrap.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 75940 2007-05-24 14:45:00Z srichter $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+ ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+ cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+ os.P_WAIT, sys.executable, sys.executable,
+ '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+ dict(os.environ,
+ PYTHONPATH=
+ ws.find(pkg_resources.Requirement.parse('setuptools')).location
+ ),
+ ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)
Property changes on: z3c.wizard/trunk/bootstrap.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/buildout.cfg
===================================================================
--- z3c.wizard/trunk/buildout.cfg (rev 0)
+++ z3c.wizard/trunk/buildout.cfg 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,26 @@
+[buildout]
+develop = .
+parts = test checker coverage-test coverage-report
+
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.wizard [test]
+
+
+[checker]
+recipe = lovely.recipe:importchecker
+path = src/z3c/wizard
+
+
+[coverage-test]
+recipe = zc.recipe.testrunner
+eggs = z3c.wizard [test]
+defaults = ['--coverage', '../../coverage']
+
+
+[coverage-report]
+recipe = zc.recipe.egg
+eggs = z3c.coverage
+scripts = coverage=coverage-report
+arguments = ('coverage', 'coverage/report')
Added: z3c.wizard/trunk/setup.py
===================================================================
--- z3c.wizard/trunk/setup.py (rev 0)
+++ z3c.wizard/trunk/setup.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,82 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation 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.
+#
+##############################################################################
+"""Setup
+
+$Id:$
+"""
+import os
+from setuptools import setup, find_packages
+
+def read(*rnames):
+ return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup (
+ name='z3c.wizard',
+ version='0.5.0dev',
+ author = "Roger Ineichen and the Zope Community",
+ author_email = "zope3-dev at zope.org",
+ description = "Wizard based on z3c.form for for Zope3",
+ long_description=(
+ read('README.txt')
+ + '\n\n' +
+ read('CHANGES.txt')
+ ),
+ license = "ZPL 2.1",
+ keywords = "zope3 z3c form wizard",
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Web Environment',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Zope Public License',
+ 'Programming Language :: Python',
+ 'Natural Language :: English',
+ 'Operating System :: OS Independent',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Framework :: Zope3'],
+ url = 'http://cheeseshop.python.org/pypi/z3c.wizard',
+ packages = find_packages('src'),
+ include_package_data = True,
+ package_dir = {'':'src'},
+ namespace_packages = ['z3c'],
+ extras_require = dict(
+ test = [
+ 'z3c.macro',
+ 'z3c.testing',
+ 'zope.app.pagetemplate',
+ 'zope.app.testing',
+ 'zope.publisher',
+ 'zope.testing',
+ ],
+ ),
+ install_requires = [
+ 'setuptools',
+ 'z3c.form',
+ 'z3c.formui',
+ 'z3c.i18n',
+ 'z3c.pagelet',
+ 'zope.app.component',
+ 'zope.app.publisher',
+ 'zope.component',
+ 'zope.configuration',
+ 'zope.contentprovider',
+ 'zope.event',
+ 'zope.interface',
+ 'zope.lifecycleevent',
+ 'zope.publisher',
+ 'zope.schema',
+ 'zope.security',
+ 'zope.traversing',
+ ],
+ zip_safe = False,
+)
Property changes on: z3c.wizard/trunk/setup.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/__init__.py
===================================================================
--- z3c.wizard/trunk/src/z3c/__init__.py (rev 0)
+++ z3c.wizard/trunk/src/z3c/__init__.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,7 @@
+try:
+ # Declare this a namespace package if pkg_resources is available.
+ import pkg_resources
+ pkg_resources.declare_namespace('z3c')
+except ImportError:
+ pass
+
Property changes on: z3c.wizard/trunk/src/z3c/__init__.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/README.txt
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/README.txt (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/README.txt 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,373 @@
+======
+Wizard
+======
+
+The goal of this package is to offer a form wizard. This implementation doesn't
+use a session. It just offers the wizard logic, the data which the wizard will
+changes or add is not a part of this implementation. If you liek to implement
+some add wizard logic you probably need to use a session anf collect the values
+in the different wizard steps and create and add an object in the wizard
+doComplete or doFinish or the step doComplete method.
+
+All steps are available by it's own url. This allows us to cache each step if
+needed. Each step url is only available if we are allowed to access a step. If
+a step is accessible depends on the conditions of each step.
+
+Since steps are adapters, we can register steps for already existing wizards
+or we can also ovreride existing steps by register a UnavailableStep step which
+always will return False for the ``available`` argument.
+
+If the wizard is completed we get redirected to the confirmation page. If we
+access a completed wizard again, we will get redirected to the confirmation
+page again.
+
+Now let's show how this works and setup our tests.
+
+
+Form support
+------------
+
+We need to setup the form defaults first:
+
+ >>> from z3c.form.testing import setupFormDefaults
+ >>> setupFormDefaults()
+
+And load the formui confguration, which will make sure that all macros get
+registered correctly.
+
+ >>> from zope.configuration import xmlconfig
+ >>> import zope.component
+ >>> import zope.viewlet
+ >>> import zope.app.component
+ >>> import zope.app.publisher.browser
+ >>> import z3c.macro
+ >>> import z3c.template
+ >>> import z3c.formui
+ >>> xmlconfig.XMLConfig('meta.zcml', zope.component)()
+ >>> xmlconfig.XMLConfig('meta.zcml', zope.viewlet)()
+ >>> xmlconfig.XMLConfig('meta.zcml', zope.app.component)()
+ >>> xmlconfig.XMLConfig('meta.zcml', zope.app.publisher.browser)()
+ >>> xmlconfig.XMLConfig('meta.zcml', z3c.macro)()
+ >>> xmlconfig.XMLConfig('meta.zcml', z3c.template)()
+ >>> xmlconfig.XMLConfig('configure.zcml', z3c.formui)()
+
+And load the z3c.wizard macro configuration:
+
+ >>> import z3c.wizard
+ >>> xmlconfig.XMLConfig('configure.zcml', z3c.wizard)()
+
+
+Sample data setup
+-----------------
+
+Let's define a sample content class:
+
+ >>> import zope.interface
+ >>> import zope.schema
+ >>> from zope.schema.fieldproperty import FieldProperty
+ >>> class IPerson(zope.interface.Interface):
+ ... """Person interface."""
+ ...
+ ... firstName = zope.schema.TextLine(title=u'First Name')
+ ... lastName = zope.schema.TextLine(title=u'Last Name')
+ ... street = zope.schema.TextLine(title=u'Street')
+ ... city = zope.schema.TextLine(title=u'City')
+
+ >>> class Person(object):
+ ... """Person content."""
+ ... zope.interface.implements(IPerson)
+ ...
+ ... firstName = FieldProperty(IPerson['firstName'])
+ ... lastName = FieldProperty(IPerson['lastName'])
+ ... street = FieldProperty(IPerson['street'])
+ ... city = FieldProperty(IPerson['city'])
+
+Setup a person for our wizard:
+
+ >>> person = Person()
+ >>> root['person'] = person
+ >>> person.__parent__ = root
+ >>> person.__name__ = u'person'
+
+
+Step
+----
+
+Let's define some steps. First use a step which knows how to store the name
+of a person.
+
+ >>> from z3c.form import form
+ >>> from z3c.form import field
+ >>> from z3c.wizard import step
+
+ >>> class PersonStep(step.EditStep):
+ ... label = u'Person'
+ ... fields = field.Fields(IPerson).select('firstName', 'lastName')
+
+And another step for collect some address data:
+
+ >>> class AddressStep(step.EditStep):
+ ... label = u'Address'
+ ... fields = field.Fields(IPerson).select('street', 'city')
+
+
+Wizard
+------
+
+Now we can define our ``Wizard`` including our steps. Steps are named
+adapters. Let's use the global method addStep for doing the step setup:
+
+ >>> from z3c.wizard import wizard
+ >>> class IPersonWizard(z3c.wizard.interfaces.IWizard):
+ ... """Person wizard marker."""
+
+ >>> class PersonWizard(wizard.Wizard):
+ ...
+ ... zope.interface.implements(IPersonWizard)
+ ...
+ ... label = u'Person Wizard'
+ ...
+ ... def setUpSteps(self):
+ ... return [
+ ... step.addStep(self, 'person', weight=1),
+ ... step.addStep(self, 'address', weight=2),
+ ... ]
+
+As next, we need to register our steps as named IStep adapters. This can be
+done by the z3c:wizardStep directive. Let's define our adapters with the
+provideAdapter method for now:
+
+ >>> import zope.interface
+ >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+ >>> from zope.publisher.interfaces.browser import IBrowserRequest
+ >>> import z3c.wizard.interfaces
+ >>> zope.component.provideAdapter(
+ ... PersonStep, (None, IBrowserRequest, None),
+ ... z3c.wizard.interfaces.IStep, name='person')
+
+ >>> zope.component.provideAdapter(
+ ... AddressStep, (None, IBrowserRequest, None),
+ ... z3c.wizard.interfaces.IStep, name='address')
+
+We need to support the div form layer for our request. This is needed for the
+form part we usein our steps. Because our steps are forms:
+
+ >>> from z3c.formui.interfaces import IDivFormLayer
+ >>> from zope.interface import alsoProvides
+ >>> from z3c.form.testing import TestRequest
+ >>> request = TestRequest()
+ >>> alsoProvides(request, IDivFormLayer)
+
+Now we can use our wizard. Our wizard will allways force to traverse to the
+current active step. This means the wizard provides a browserDefault which
+returns the default step instead of render the wizard as view. This allows us
+to use the step as an adapter discriminator for viewlets and other adapters
+like the menu implementation uses. The wizard acts like a dispatcher to the
+right step and not as a view itself.
+
+ >>> personWizard = PersonWizard(person, request)
+ >>> personWizard.__parent__ = person
+ >>> personWizard.__name__ = u'wizard'
+
+Now get the default view (step) arguments from the wizard:
+
+ >>> obj, names = personWizard.browserDefault(request)
+ >>> obj
+ <PersonWizard u'wizard'>
+
+ >>> names
+ ('person',)
+
+Now traverse to the step, update and render them:
+
+ >>> personStep = obj.publishTraverse(request, names[0])
+ >>> personStep.update()
+ >>> print personStep.render()
+ <div class="wizard">
+ <div class="header">Person Wizard</div>
+ <div class="wizardMenu">
+ <span class="selected">
+ <span>Person</span>
+ </span>
+ <span>
+ <a href="http://127.0.0.1/person/wizard/address">Address</a>
+ </span>
+ </div>
+ <form action="http://127.0.0.1" method="post"
+ enctype="multipart/form-data" class="edit-form"
+ id="form">
+ <div class="viewspace">
+ <div class="label">Person</div>
+ <div class="required-info">
+ <span class="required">*</span>
+ – required
+ </div>
+ <div class="step">
+ <div id="form-widgets-firstName-row" class="row">
+ <div class="label">
+ <label for="form-widgets-firstName">
+ <span>First Name</span>
+ <span class="required">*</span>
+ </label>
+ </div>
+ <div class="widget"><input type="text" id="form-widgets-firstName"
+ name="form.widgets.firstName"
+ class="text-widget required textline-field" value="" />
+ </div>
+ </div>
+ <div id="form-widgets-lastName-row" class="row">
+ <div class="label">
+ <label for="form-widgets-lastName">
+ <span>Last Name</span>
+ <span class="required">*</span>
+ </label>
+ </div>
+ <div class="widget"><input type="text" id="form-widgets-lastName"
+ name="form.widgets.lastName"
+ class="text-widget required textline-field" value="" />
+ </div>
+ </div>
+ </div>
+ <div>
+ <div class="buttons">
+ <span class="back">
+ </span>
+ <span class="step">
+ <input type="submit" id="form-buttons-apply"
+ name="form.buttons.apply"
+ class="submit-widget button-field" value="Apply" />
+ </span>
+ <span class="forward">
+ <input type="submit" id="form-buttons-next"
+ name="form.buttons.next"
+ class="submit-widget button-field" value="Next" />
+ </span>
+ </div>
+ </div>
+ </div>
+ </form>
+ </div>
+
+
+We can't go to the next step if we not complete the first step:
+
+ >>> request = TestRequest(form={'form.buttons.next': 'Next'})
+ >>> alsoProvides(request, IDivFormLayer)
+ >>> personWizard = PersonWizard(person, request)
+ >>> personWizard.__parent__ = person
+ >>> personWizard.__name__ = u'wizard'
+ >>> personStep = personWizard.publishTraverse(request, names[0])
+ >>> personStep.update()
+ >>> print personStep.render()
+ <div class="wizard">
+ ...
+ <div class="summary">There were some errors.</div>
+ ...
+ <div class="error">Required input is missing.</div>
+ ...
+ <div class="error">Required input is missing.</div>
+ ...
+
+
+We can complete this step if we fill in the required values and click next:
+
+ >>> request = TestRequest(form={'form.widgets.firstName': u'Roger',
+ ... 'form.widgets.lastName': u'Ineichen',
+ ... 'form.buttons.next': 'Next'})
+ >>> alsoProvides(request, IDivFormLayer)
+ >>> personWizard = PersonWizard(person, request)
+ >>> personWizard.__parent__ = person
+ >>> personWizard.__name__ = u'wizard'
+ >>> personStep = personWizard.publishTraverse(request, names[0])
+ >>> personStep.update()
+ >>> print personStep.render()
+
+As you can see the step get processed and the wizard will redirect to the next
+step using the response redirect concept:
+
+ >>> personWizard.nextURL
+ 'http://127.0.0.1/person/wizard/address'
+
+Let's access the next step using the traverser. This will setup the next step
+and tehm.
+
+ >>> request = TestRequest()
+ >>> alsoProvides(request, IDivFormLayer)
+ >>> personWizard = PersonWizard(person, request)
+ >>> personWizard.__parent__ = person
+ >>> personWizard.__name__ = u'wizard'
+
+As you can see we see our next step is the address step:
+
+ >>> addressStep = personWizard.publishTraverse(request, 'address')
+ >>> addressStep
+ <AddressStep 'address'>
+
+Update and render them:
+
+ >>> addressStep.update()
+ >>> print addressStep.render()
+ <div class="wizard">
+ <div class="header">Person Wizard</div>
+ <div class="wizardMenu">
+ <span>
+ <a href="http://127.0.0.1/person/wizard/person">Person</a>
+ </span>
+ <span class="selected">
+ <span>Address</span>
+ </span>
+ </div>
+ <form action="http://127.0.0.1" method="post"
+ enctype="multipart/form-data" class="edit-form"
+ id="form">
+ <div class="viewspace">
+ <div class="label">Address</div>
+ <div class="required-info">
+ <span class="required">*</span>
+ – required
+ </div>
+ <div class="step">
+ <div id="form-widgets-street-row" class="row">
+ <div class="label">
+ <label for="form-widgets-street">
+ <span>Street</span>
+ <span class="required">*</span>
+ </label>
+ </div>
+ <div class="widget"><input type="text" id="form-widgets-street"
+ name="form.widgets.street"
+ class="text-widget required textline-field" value="" />
+ </div>
+ </div>
+ <div id="form-widgets-city-row" class="row">
+ <div class="label">
+ <label for="form-widgets-city">
+ <span>City</span>
+ <span class="required">*</span>
+ </label>
+ </div>
+ <div class="widget"><input type="text" id="form-widgets-city"
+ name="form.widgets.city"
+ class="text-widget required textline-field" value="" />
+ </div>
+ </div>
+ </div>
+ <div>
+ <div class="buttons">
+ <span class="back">
+ <input type="submit" id="form-buttons-back"
+ name="form.buttons.back"
+ class="submit-widget button-field" value="Back" />
+ </span>
+ <span class="step">
+ <input type="submit" id="form-buttons-apply"
+ name="form.buttons.apply"
+ class="submit-widget button-field" value="Apply" />
+ </span>
+ <span class="forward">
+ </span>
+ </div>
+ </div>
+ </div>
+ </form>
+ </div>
Property changes on: z3c.wizard/trunk/src/z3c/wizard/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/__init__.py
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/__init__.py (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/__init__.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1 @@
+# make a package
Property changes on: z3c.wizard/trunk/src/z3c/wizard/__init__.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/browser.zcml
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/browser.zcml (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/browser.zcml 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,72 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:z3c="http://namespaces.zope.org/z3c"
+ i18n_domain="z3c">
+
+ <z3c:macro
+ name="wizard"
+ template="wizard.pt"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+ <z3c:macro
+ name="wizard-header"
+ template="wizard.pt"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+ <z3c:macro
+ name="wizard-menu"
+ template="wizard.pt"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+ <z3c:macro
+ name="wizard-form"
+ template="wizard.pt"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+ <z3c:macro
+ name="wizard-subform"
+ template="wizard.pt"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+ <z3c:macro
+ name="wizard-label"
+ template="wizard.pt"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+ <z3c:macro
+ name="wizard-required-info"
+ template="wizard.pt"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+ <z3c:macro
+ name="wizard-widgets"
+ template="wizard.pt"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+ <z3c:macro
+ name="wizard-errors"
+ template="wizard.pt"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+ <z3c:macro
+ name="wizard-buttons"
+ template="wizard.pt"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+ <z3c:template
+ template="step.pt"
+ for=".interfaces.IStep"
+ layer="z3c.form.interfaces.IFormLayer"
+ />
+
+</configure>
Property changes on: z3c.wizard/trunk/src/z3c/wizard/browser.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/button.py
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/button.py (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/button.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,34 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""Wizard button actions implementation
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+from z3c.form import button
+from z3c.wizard import interfaces
+
+
+class WizardButtonActions(button.ButtonActions):
+ """Wizard Button Actions."""
+
+ @property
+ def backActions(self):
+ return [action for action in self.values()
+ if interfaces.IBackButton.providedBy(action.field)]
+
+ @property
+ def forwardActions(self):
+ return [action for action in self.values()
+ if interfaces.INextButton.providedBy(action.field)]
Property changes on: z3c.wizard/trunk/src/z3c/wizard/button.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/configure.zcml
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/configure.zcml (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/configure.zcml 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,8 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:z3c="http://namespaces.zope.org/z3c"
+ i18n_domain="z3c">
+
+ <include file="browser.zcml" />
+
+</configure>
Property changes on: z3c.wizard/trunk/src/z3c/wizard/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/interfaces.py
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/interfaces.py (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/interfaces.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,285 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""Wizard button actions implementation
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.schema
+import zope.location.interfaces
+from zope.contentprovider.interfaces import IContentProvider
+
+from z3c.i18n import MessageFactory as _
+from z3c.form import interfaces
+from z3c.form import button
+from z3c.pagelet.interfaces import IPagelet
+
+
+class IBackButton(interfaces.IButton):
+ """A button that redirects to the previous step."""
+
+
+class INextButton(interfaces.IButton):
+ """A button that redirects to the next step."""
+
+
+class IWizardButtons(zope.interface.Interface):
+ """Wizard button interfaces."""
+
+ back = button.Button(
+ title=_('Back'),
+ condition=lambda form: form.showBackButton)
+ zope.interface.alsoProvides(back, (IBackButton,))
+
+ next = button.Button(
+ title=_('Next'),
+ condition=lambda form: form.showNextButton)
+ zope.interface.alsoProvides(next, (INextButton,))
+
+ complete = button.Button(
+ title=_('Complete'),
+ condition=lambda form: form.showCompleteButton)
+ zope.interface.alsoProvides(complete, (INextButton,))
+
+
+class IStep(interfaces.IForm, IPagelet):
+ """An interface marking a step sub-form."""
+
+ available = zope.schema.Bool(
+ title=u'Available',
+ description=u'Marker for available step',
+ default=True,
+ required=False)
+
+ visible = zope.schema.Bool(
+ title=u'Show step in wizard step menu',
+ description=u'Show step in wizard step menu',
+ default=True,
+ required=False)
+
+ showRequired = zope.schema.Bool(
+ title=u'Show required label',
+ description=u'Show required label',
+ default=True,
+ required=False)
+
+ weight = zope.schema.Int(
+ title=u'Step weight in wizard',
+ description=u'Step weight in wizard',
+ default=0,
+ required=False)
+
+ completed = zope.schema.Bool(
+ title=u'Completed',
+ description=u'Marker for completed step',
+ default=False,
+ required=False)
+
+ handleApplyOnBack = zope.schema.Bool(
+ title=u'Handle apply changes on back',
+ description=u'Handle apply changes on back will force validation',
+ default=False,
+ required=False)
+
+ handleApplyOnNext = zope.schema.Bool(
+ title=u'Handle apply changes on next',
+ description=u'Handle apply changes on next will force validation',
+ default=True,
+ required=False)
+
+ handleApplyOnComplete = zope.schema.Bool(
+ title=u'Handle apply changes on complete',
+ description=u'Handle apply changes on complete will force validation',
+ default=True,
+ required=False)
+
+ showSaveButton = zope.schema.Bool(
+ title=u'Show save button',
+ description=u'Show save button',
+ default=True,
+ required=False)
+
+ showBackButton = zope.schema.Bool(
+ title=u'Show back button',
+ description=u'Back button condition',
+ default=True,
+ required=False)
+
+ showNextButton = zope.schema.Bool(
+ title=u'Show next button',
+ description=u'Next button condition',
+ default=True,
+ required=False)
+
+ showCompleteButton = zope.schema.Bool(
+ title=u'Show complete button',
+ description=u'Complete button condition',
+ default=True,
+ required=False)
+
+ def goToStep(stepName):
+ """Redirect to step by name."""
+
+ def goToNext():
+ """Redirect to next step."""
+
+ def goToBack():
+ """Redirect to back step."""
+
+ def applyChanges(data):
+ """Generic form save method taken from z3c.form.form.EditForm."""
+
+ def doHandleApply(action):
+ """Extract data and calls applyChanges."""
+
+ def doBack(action):
+ """Process back action and return True on sucess."""
+
+ def doNext(action):
+ """Process next action and return True on sucess."""
+
+ def doComplete(action):
+ """Process complete action and return True on sucess."""
+
+ def update():
+ """Update the step."""
+
+ def render():
+ """Render the step content w/o wrapped layout."""
+
+ def __call__():
+ """Compute a response body including the layout"""
+
+
+class IWizard(zope.location.interfaces.ILocation):
+ """An interface marking the controlling wizard form."""
+
+ firstStepAsDefault = zope.schema.Bool(
+ title=u'Show first step as default',
+ description=u'Show first step or first not completed step as default',
+ default=True,
+ required=True)
+
+ adjustStep = zope.schema.Bool(
+ title=u'Adjust step',
+ description=u'Force fallback (redirect) to last incomplete step',
+ default=True,
+ required=False)
+
+ confirmationPageName = zope.schema.ASCIILine(
+ title=u'Confirmation page name',
+ description=u'The confirmation page name shown after completed',
+ default=None,
+ required=False)
+
+ cssActive = zope.schema.ASCIILine(
+ title=u'Active step menu CSS class',
+ description=u'The active step menu CSS class',
+ default='selected',
+ required=False)
+
+ cssInActive = zope.schema.ASCIILine(
+ title=u'In-Active step menu item CSS class',
+ description=u'The in-active step menu item CSS class',
+ default=None,
+ required=False)
+
+ stepInterface = zope.interface.Attribute('Step lookup interface.')
+
+ steps = zope.interface.Attribute(
+ """List of one or more IStep (can be lazy).""")
+
+ stepMenu = zope.interface.Attribute("""Step menu info.""")
+
+ step = zope.schema.Object(
+ title=u'Current step',
+ description=u'Current step',
+ schema=IStep)
+
+ completed = zope.schema.Bool(
+ title=u'Completed',
+ description=u'Marker for completed step',
+ default=False,
+ required=False)
+
+ isFirstStep = zope.schema.Bool(
+ title=u'Is first step',
+ description=u'Is first step',
+ default=False,
+ required=False)
+
+ isLastStep = zope.schema.Bool(
+ title=u'Is last step',
+ description=u'Is last step',
+ default=False,
+ required=False)
+
+ previousStepName = zope.schema.TextLine(
+ title=u'Previous step name',
+ description=u'Previous step name',
+ default=None,
+ required=False)
+
+ nextStepName = zope.schema.TextLine(
+ title=u'NExt step name',
+ description=u'Next step name',
+ default=None,
+ required=False)
+
+ def doAdjustStep():
+ """Ensure that we can't traverse to more then the first not completed
+ step.
+ """
+
+ def getDefaultStep():
+ """Can return the first or first not completed step as default."""
+
+ def doAdjustStep():
+ """Make sure all previous steps got completed. If not, redirect to the
+ last uncomplete step.
+ """
+
+ def updateActions():
+ """Update wizard actions."""
+
+ def publishTraverse(request, name):
+ """Traverse to step by it's name."""
+
+ def browserDefault(request):
+ """The default step is our browserDefault traversal setp."""
+
+ def goToStep(stepName):
+ """Redirect to the step by name."""
+
+ def goToBack():
+ """Redirect to next step if previous get sucessfuly processed."""
+
+ def goToNext():
+ """Redirect to next step if previous get sucessfuly processed."""
+
+ def doBack(action):
+ """Process something if back action get exceuted."""
+
+ def doNext(action):
+ """Process something if next action get exceuted."""
+
+ def doComplete(action):
+ """Process something if complete action get exceuted."""
+
+ def doFinish():
+ """Process something on complete wizard."""
+
+ def update():
+ """Adjust step and update actions."""
Property changes on: z3c.wizard/trunk/src/z3c/wizard/interfaces.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/meta.zcml
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/meta.zcml (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/meta.zcml 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,22 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:meta="http://namespaces.zope.org/meta">
+
+ <meta:directives namespace="http://namespaces.zope.org/z3c">
+
+ <meta:directive
+ name="wizard"
+ schema=".zcml.IWizardDirective"
+ handler=".zcml.wizardDirective"
+ />
+
+ <meta:directive
+ name="wizardStep"
+ schema=".zcml.IWizardStepDirective"
+ handler=".zcml.wizardStepDirective"
+ />
+
+ </meta:directives>
+
+</configure>
+
Property changes on: z3c.wizard/trunk/src/z3c/wizard/meta.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/step.pt
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/step.pt (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/step.pt 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1 @@
+<div metal:use-macro="macro:wizard"></div>
Property changes on: z3c.wizard/trunk/src/z3c/wizard/step.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/step.py
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/step.py (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/step.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,221 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""Wizard button actions implementation
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.component
+import zope.interface
+import zope.event
+import zope.lifecycleevent
+
+from z3c.i18n import MessageFactory as _
+from z3c.form.interfaces import IDataManager
+from z3c.form.interfaces import IActionHandler
+from z3c.form import button
+from z3c.form import field
+from z3c.form import subform
+from z3c.formui import form
+from z3c.formui import layout
+from z3c.wizard import interfaces
+
+
+def addStep(self, name, label=None, weight=None, available=None, **kws):
+ step = zope.component.getMultiAdapter((self.context, self.request, self),
+ interfaces.IStep, name=name)
+ step.__name__ = name
+ if label is not None:
+ step.label = label
+ if weight is not None:
+ step.weight = weight
+ if available is not None:
+ step.available = available
+ for name, value in kws.items():
+ setattr(step, name, value)
+ return step
+
+
+class Step(form.Form):
+ """Wizard base step implementation.
+
+ The step offers hooks for action handlers for all wizard actions. The step
+ can also provide own actions and handlers. This actions get rendered as step
+ actions. Between the bak an next wizard actions.
+
+ A step can access the context or any object which you return by the
+ getContent method. See z3c.form for more info about that. If you need a
+ complexer wizard setup, you probably have to use a session and store
+ temporary collected values in the session and store it if the wizard will
+ call doComplete on the last step or in the wizard itself. Such a session
+ is not a part of this implementation. This wizard implementation works
+ on any context like other z3c.form IForm implementations. For more infos
+ see z3c.form which this wizard is based on.
+ """
+
+ zope.interface.implements(interfaces.IStep)
+
+ name = None
+ label = None
+ available = True
+ visible = True
+ weight = 0
+ showRequired = True
+
+ handleApplyOnBack = False
+ handleApplyOnNext = True
+ handleApplyOnComplete = True
+
+ # button condition
+ showSaveButton = True
+
+ formErrorsMessage = _('There were some errors.')
+ successMessage = _('Data successfully updated.')
+ noChangesMessage = _('No changes were applied.')
+
+ def __init__(self, context, request, wizard):
+ self.context = context
+ self.request = request
+ self.wizard = self.__parent__ = wizard
+
+ @property
+ def showBackButton(self):
+ """Back button condition."""
+ return not self.wizard.isFirstStep
+
+ @property
+ def showNextButton(self):
+ """Next button condition."""
+ return not self.wizard.isLastStep
+
+ @property
+ def showCompleteButton(self):
+ """Complete button condition."""
+ return self.wizard.isLastStep and self.wizard.completed
+
+ @property
+ def nextURL(self):
+ """Next step url known by wizard."""
+ return self.wizard.nextURL
+
+ @property
+ def completed(self):
+ """Simple default check for find out if a step is complete.
+
+ This method will ensure that we store at least all required form values
+ and that this values are valid. You can implement any other or
+ additional condition in your custom step implementation.
+ """
+ content = self.getContent()
+ for field in self.fields.values():
+ if not field.field.required:
+ continue
+ dm = zope.component.getMultiAdapter(
+ (content, field.field), IDataManager)
+ if dm.get() is field.field.missing_value:
+ return False
+ return True
+
+ def goToStep(self, stepName):
+ self.wizard.goToStep(stepName)
+
+ def goToNext(self):
+ self.wizard.goToNext()
+
+ def goToBack(self):
+ self.wizard.goToBack()
+
+ def applyChanges(self, data):
+ """Generic form save method taken from z3c.form.form.EditForm."""
+ content = self.getContent()
+ changes = form.applyChanges(self, content, data)
+ # ``changes`` is a dictionary; if empty, there were no changes
+ if changes:
+ # Construct change-descriptions for the object-modified event
+ descriptions = []
+ for interface, names in changes.items():
+ descriptions.append(
+ zope.lifecycleevent.Attributes(interface, *names))
+ # Send out a detailed object-modified event
+ zope.event.notify(
+ zope.lifecycleevent.ObjectModifiedEvent(content, *descriptions))
+ return changes
+
+ def doHandleApply(self, action):
+ """Extract data and calls applyChanges."""
+ data, errors = self.extractData()
+ if errors:
+ self.status = self.formErrorsMessage
+ return False
+ changes = self.applyChanges(data)
+ if changes:
+ self.status = self.successMessage
+ else:
+ self.status = self.noChangesMessage
+ return True
+
+ def doBack(self, action):
+ """Process back action and return True on sucess."""
+ if self.handleApplyOnBack:
+ return self.doHandleApply(action)
+ return True
+
+ def doNext(self, action):
+ """Process next action and return True on sucess."""
+ if self.handleApplyOnNext:
+ return self.doHandleApply(action)
+ return True
+
+ def doComplete(self, action):
+ """Process complete action and return True on sucess."""
+ if self.handleApplyOnComplete:
+ return self.doHandleApply(action)
+ return True
+
+ def update(self):
+ # setup wizard actions
+ self.wizard.update()
+ if self.nextURL is not None:
+ # abort and do redirect in render method
+ return
+ # update and excecute step actions
+ super(Step, self).update()
+ # excecute wizard actions
+ self.wizard.actions.execute()
+
+ def render(self):
+ # render content template
+ if self.nextURL is not None:
+ self.request.response.redirect(self.nextURL)
+ return u''
+ return super(Step, self).render()
+
+ def __repr__(self):
+ return '<%s %r>' % (self.__class__.__name__, self.__name__)
+
+
+class EditStep(Step):
+ """Step with default save button action."""
+
+ @button.buttonAndHandler(_('Apply'), name='apply')
+ def handleApply(self, action):
+ self.doHandleApply(action)
+
+
+class UnavailableStep(Step):
+ """A step that is not available.
+
+ This class is particularly useful for turning off an adapter steps.
+ """
+ available = False
Property changes on: z3c.wizard/trunk/src/z3c/wizard/step.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/testing.py
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/testing.py (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/testing.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""Wizard button actions implementation
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+from zope.app.pagetemplate import metaconfigure
+from zope.app.testing import setup
+
+import z3c.macro.tales
+
+
+###############################################################################
+#
+# testing setup
+#
+###############################################################################
+
+def setUp(test):
+ test.globs = {'root': setup.placefulSetUp(True)}
+
+ metaconfigure.registerType('macro', z3c.macro.tales.MacroExpression)
+
+
+def tearDown(test):
+ setup.placefulTearDown()
Property changes on: z3c.wizard/trunk/src/z3c/wizard/testing.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/tests.py
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/tests.py (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/tests.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,116 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""Wizard button actions implementation
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import unittest
+import zope.interface
+from zope.testing import doctest
+from zope.publisher.browser import TestRequest
+
+import z3c.testing
+from z3c.wizard import interfaces
+from z3c.wizard import wizard
+from z3c.wizard import step
+from z3c.wizard import testing
+
+
+class IContentStub(zope.interface.Interface):
+ """Content stub marker."""
+
+
+class ContentStub(object):
+ """Content stub."""
+
+ zope.interface.implements(IContentStub)
+
+ def values(self):
+ pass
+
+
+class StepTestClass(step.Step):
+ """Simple test step."""
+
+
+class WizardTestClass(wizard.Wizard):
+ """Wizard Test class providing a step."""
+
+ baseURL = '#'
+
+ def __init__(self, context, request):
+ super(WizardTestClass, self).__init__(context, request)
+ self.step = StepTestClass(context, request, self)
+ self.step.__name__ = 'first'
+ self.step.__parent__ = self
+
+
+def setStubs():
+ zope.component.provideAdapter(StepTestClass,
+ (IContentStub, None, None), provides=interfaces.IStep, name='first')
+ zope.component.provideAdapter(StepTestClass,
+ (IContentStub, None, None), provides=interfaces.IStep, name='last')
+
+
+class TestStep(z3c.testing.InterfaceBaseTest):
+
+ def setUp(self):
+ setStubs()
+
+ def getTestInterface(self):
+ return interfaces.IStep
+
+ def getTestClass(self):
+ return step.Step
+
+ def getTestPos(self):
+ content = ContentStub()
+ request = TestRequest()
+ wiz = wizard.Wizard(content, request)
+ return (content, request, wiz)
+
+
+class TestWizard(z3c.testing.InterfaceBaseTest):
+
+ def setUp(self):
+ setStubs()
+
+ def getTestInterface(self):
+ return interfaces.IWizard
+
+ def getTestClass(self):
+ return WizardTestClass
+
+ def getTestPos(self):
+ return (ContentStub(), TestRequest())
+
+
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocFileSuite('README.txt',
+ setUp=testing.setUp, tearDown=testing.tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ doctest.DocFileSuite('zcml.txt',
+ setUp=testing.setUp, tearDown=testing.tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,),
+ unittest.makeSuite(TestStep),
+ unittest.makeSuite(TestWizard),
+ ))
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Property changes on: z3c.wizard/trunk/src/z3c/wizard/tests.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/wizard.pt
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/wizard.pt (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/wizard.pt 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,101 @@
+<div class="wizard"
+ metal:define-macro="wizard">
+ <metal:block define-slot="header">
+ <div class="header" metal:define-macro="wizard-header"
+ tal:content="view/wizard/label">label</div>
+ </metal:block>
+ <metal:block define-slot="menu">
+ <div class="wizardMenu" metal:define-macro="wizard-menu">
+ <span tal:repeat="item view/wizard/stepMenu"
+ tal:attributes="class item/class">
+ <span
+ tal:condition="item/selected"
+ tal:content="item/title" />
+ <a href=""
+ tal:condition="not:item/selected"
+ tal:attributes="href item/url"
+ tal:content="item/title" />
+ </span>
+ </div>
+ </metal:block>
+ <form action="." method="post" enctype="multipart/form-data" class="edit-form"
+ metal:define-macro="wizard-form"
+ tal:attributes="method view/method;
+ enctype view/enctype;
+ acceptCharset view/acceptCharset;
+ accept view/accept;
+ action view/action;
+ name view/name;
+ id view/id">
+ <metal:block define-macro="wizard-subform">
+ <div class="viewspace" metal:define-slot="viewspace">
+ <metal:block define-slot="label">
+ <div class="label" metal:define-macro="wizard-label"
+ tal:condition="view/label|nothing"
+ tal:content="view/label">
+ Form Label
+ </div>
+ </metal:block>
+ <metal:block define-slot="info">
+ <div class="required-info"
+ metal:define-macro="wizard-required-info"
+ tal:condition="view/showRequired">
+ <span class="required">*</span>
+ – required
+ </div>
+ </metal:block>
+ <metal:block define-slot="header">
+ <div class="status" tal:condition="view/status">
+ <div class="summary"
+ i18n:translate=""
+ tal:content="view/status">
+ Form status summary
+ </div>
+ <ul class="errors"
+ tal:condition="view/widgets/errors"
+ metal:define-macro="wizard-errors">
+ <li tal:repeat="error view/widgets/errors">
+ <tal:block condition="error/widget">
+ <span tal:replace="error/widget/label" />:
+ </tal:block>
+ <span tal:replace="structure error/render">Error Type</span>
+ </li>
+ </ul>
+ </div>
+ </metal:block>
+ <div metal:define-slot="extra-info" tal:replace="nothing">
+ </div>
+ <div class="step" metal:define-slot="widget-rows">
+ <div metal:use-macro="macro:widget-rows" />
+ </div>
+ <metal:block define-slot="above-buttons">
+ </metal:block>
+ <metal:block define-slot="buttons">
+ <div metal:define-macro="wizard-buttons">
+ <div class="buttons">
+ <span class="back">
+ <input tal:repeat="action view/wizard/actions/backActions"
+ tal:replace="structure action/render"
+ />
+ </span>
+ <span class="step">
+ <input tal:repeat="action view/actions/values"
+ tal:replace="structure action/render"
+ />
+ </span>
+ <span class="forward">
+ <input tal:repeat="action view/wizard/actions/forwardActions"
+ tal:replace="structure action/render"
+ />
+ </span>
+ </div>
+ </div>
+ </metal:block>
+ <metal:block define-slot="bottom">
+ </metal:block>
+ </div>
+ </metal:block>
+ </form>
+</div>
+
+
Property changes on: z3c.wizard/trunk/src/z3c/wizard/wizard.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/wizard.py
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/wizard.py (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/wizard.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,285 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""Wizard button actions implementation
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.component
+import zope.interface
+from zope.cachedescriptors.property import Lazy
+from zope.publisher.interfaces import NotFound
+from zope.traversing.browser import absoluteURL
+
+from z3c.form import button
+from z3c.form import field
+from z3c.form import subform
+from z3c.formui import form
+from z3c.formui import layout
+from z3c.wizard import interfaces
+from z3c.wizard.button import WizardButtonActions
+
+
+def nameStep(step, name):
+ """Give a step a __name__."""
+ step.__name__ = name
+ return step
+
+
+class Wizard(form.Form):
+ """Wizard form.
+
+ The wizard is responsible for manage the steps and offers the wizard menu
+ navigation and knows the step order. The wizard can check the conditions
+ given from the steps. The wizard is also responsible for delegate the
+ back, next and complete actions to the steps.
+
+ This IWizard object is modeled as a Controller known from the MVC
+ (Model, view, controller) patter version 2.0 and the step is implemented as
+ a view.
+ """
+
+ zope.interface.implements(interfaces.IWizard)
+
+ buttons = button.Buttons(interfaces.IWizardButtons)
+
+ # custmize this part if needed
+ stepInterface = interfaces.IStep
+
+ firstStepAsDefault = True
+ adjustStep = True
+ confirmationPageName = None
+ nextURL = None
+
+ cssActive = 'selected'
+ cssInActive = None # None will skip class attribute in DOM element
+
+ # for internal use
+ __name__ = None
+ steps = None
+ step = None
+
+ @property
+ def baseURL(self):
+ return absoluteURL(self, self.request)
+
+ def setUpSteps(self):
+ """Return a list of steps. This implementation uses IStep adapters.
+
+ Take a look at the addStep method defined in step.py. This method
+ allows you to setup steps directly in the method and offers an API for
+ customized step setup.
+ """
+ steps = list(zope.component.getAdapters((self.context, self.request,
+ self), self.stepInterface))
+ return [nameStep(step, name) for name, step in steps]
+
+ def filterSteps(self, steps):
+ """Make sure to only select available steps and we give a name."""
+ return [step for step in steps if step.available]
+
+ def orderSteps(self, steps):
+ # order steps by it's weight
+ return sorted(steps, key=lambda step: step.weight)
+
+ @property
+ def steps(self):
+ steps = self.setUpSteps()
+ steps = self.filterSteps(steps)
+ return self.orderSteps(steps)
+
+ @property
+ def completed(self):
+ for step in self.steps:
+ if not step.completed:
+ return False
+ return True
+
+ @property
+ def isFirstStep(self):
+ """See interfaces.IWizard"""
+ return self.step and self.step.__name__ == self.steps[0].__name__
+
+ @property
+ def isLastStep(self):
+ """See interfaces.IWizard"""
+ return self.step and self.step.__name__ == self.steps[-1].__name__
+
+ @property
+ def showBackButton(self):
+ """Ask the step."""
+ return self.step and self.step.showBackButton
+
+ @property
+ def showNextButton(self):
+ """Ask the step."""
+ return self.step and self.step.showNextButton
+
+ @property
+ def showCompleteButton(self):
+ """Ask the step."""
+ return self.step.showCompleteButton
+
+ @property
+ def previousStepName(self):
+ if self.step is None:
+ return
+ stepNames = [step.__name__ for step in self.steps]
+ idx = stepNames.index(self.step.__name__)
+ if idx == 0:
+ return
+ return stepNames[idx-1]
+
+ @property
+ def nextStepName(self):
+ if self.step is None:
+ return
+ stepNames = [step.__name__ for step in self.steps]
+ idx = stepNames.index(self.step.__name__)
+ if idx == len(stepNames)-1:
+ return
+ return stepNames[idx+1]
+
+ @property
+ def stepMenu(self):
+ items = []
+ append = items.append
+ lenght = len(self.steps)-1
+ for idx, step in enumerate(self.steps):
+ firstStep = False
+ lastStep = False
+ if step.visible:
+ isSelected = self.step and self.step.__name__ == step.__name__
+ cssClass = isSelected and self.cssActive or self.cssInActive
+ if idx == 0:
+ firstStep = True
+ if idx == lenght:
+ lastStep = True
+ append({
+ 'name': step.__name__,
+ 'title': step.label,
+ 'number': str(idx+1),
+ 'url': '%s/%s' % (self.baseURL, step.__name__),
+ 'selected': self.step.__name__ == step.__name__,
+ 'class': cssClass,
+ 'first': firstStep,
+ 'last': lastStep
+ })
+ return items
+
+ def getDefaultStep(self):
+ """Can return the first or first not completed step as default."""
+ # return first step if this option is set
+ if self.firstStepAsDefault:
+ return self.steps[0]
+ # return first not completed step
+ for step in self.steps:
+ if step.completed == False:
+ return step
+ # fallback to first step if all steps completed
+ return self.steps[0]
+
+ def doAdjustStep(self):
+ # Make sure all previous steps got completed. If not, redirect to the
+ # last uncomplete step
+ if not self.adjustStep:
+ return False
+ for step in self.steps:
+ if step.__name__ is self.step.__name__:
+ break
+ if step.completed == False:
+ # prepare redirect to not completed step and return True
+ self.nextURL = '%s/%s' % (self.baseURL, step.__name__)
+ return True
+ # or return False
+ return False
+
+ def updateActions(self):
+ self.actions = WizardButtonActions(self, self.request,
+ self.context)
+ self.actions.update()
+
+ def update(self):
+ if self.doAdjustStep():
+ return
+ self.updateActions()
+
+ def publishTraverse(self, request, name):
+ """Traverse to step by it's name."""
+ # Remove HTML ending
+ if '.' in name:
+ rawName = name.rsplit('.', 1)[0]
+ else:
+ rawName = name
+ # Find the active step
+ for step in self.steps:
+ if step.__name__ == rawName:
+ self.step = step
+ return self.step
+ raise NotFound(self, name, request)
+
+ def browserDefault(self, request):
+ """The default step is our browserDefault traversal setp."""
+ if self.step is None:
+ step = self.getDefaultStep()
+ # always return default step as default view for our wizard
+ return self, (step.__name__,)
+
+ def goToStep(self, stepName):
+ self.nextURL = '%s/%s' % (self.baseURL, stepName)
+
+ def goToBack(self):
+ # redirect to next step if previous get sucessfuly processed
+ self.goToStep(self.previousStepName)
+
+ def goToNext(self):
+ # redirect to next step if previous get sucessfuly processed
+ self.goToStep(self.nextStepName)
+
+ def doBack(self, action):
+ if self.step.doBack(action):
+ self.goToBack()
+
+ def doNext(self, action):
+ if self.step.doNext(action):
+ self.goToNext()
+
+ def doComplete(self, action):
+ if self.step.doComplete(action):
+ # do finsih after step get completed is completed
+ self.doFinish()
+
+ def doFinish(self):
+ """Force redirect after doComplete if confirmationPageName is given."""
+ if self.confirmationPageName is not None:
+ self.nextURL = '%s/%s' % (absoluteURL(self.context, self.request),
+ self.confirmationPageName)
+
+ @button.handler(interfaces.IWizardButtons['back'])
+ def handleBack(self, action):
+ self.doBack(action)
+
+ @button.handler(interfaces.IWizardButtons['next'])
+ def handleNext(self, action):
+ self.doNext(action)
+
+ @button.handler(interfaces.IWizardButtons['complete'])
+ def handleComplete(self, action):
+ self.doComplete(action)
+
+ def render(self, *args, **kws):
+ raise NotImplementedError('render is no supported')
+
+ def __repr__(self):
+ return '<%s %r>' % (self.__class__.__name__, self.__name__)
Property changes on: z3c.wizard/trunk/src/z3c/wizard/wizard.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/zcml.py
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/zcml.py (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/zcml.py 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,220 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""Wizard button actions implementation
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.component
+import zope.schema
+import zope.configuration.fields
+import zope.security.checker
+import zope.security.zcml
+from zope.configuration.exceptions import ConfigurationError
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+
+from zope.app.publisher.browser import viewmeta
+from zope.app.component import metadirectives
+
+import z3c.pagelet.zcml
+
+from z3c.wizard import interfaces
+from z3c.wizard import step
+from z3c.wizard import wizard
+
+
+class IWizardDirective(z3c.pagelet.zcml.IPageletDirective):
+ """A directive to register a new wizard.
+
+ The wizard directive also supports an undefined set of keyword arguments
+ that are set as attributes on the wizard after creation.
+ """
+
+
+class IWizardStepDirective(metadirectives.IBasicViewInformation):
+ """A directive to register a new wizard step.
+
+ The wizard step directive also supports an undefined set of keyword
+ arguments that are set as attributes on the wizard step after creation.
+ """
+
+ name = zope.schema.TextLine(
+ title=u"The name of the pagelet.",
+ description=u"The name shows up in URLs/paths. For example 'foo'.",
+ required=True)
+
+ class_ = zope.configuration.fields.GlobalObject(
+ title=u"Class",
+ description=u"A class that provides attributes used by the pagelet.",
+ required=True,
+ )
+
+ permission = zope.security.zcml.Permission(
+ title=u"Permission",
+ description=u"The permission needed to use the pagelet.",
+ required=True
+ )
+
+ for_ = zope.configuration.fields.GlobalObject(
+ title=u"Context",
+ description=u"The content interface or class this pagelet is for.",
+ required=False
+ )
+
+ wizard = zope.configuration.fields.GlobalObject(
+ title=u"Wizard",
+ description=u"The wizard interface or class this step is for.",
+ required=False
+ )
+
+ provides = zope.configuration.fields.GlobalInterface(
+ title=u"The interface this pagelets provides.",
+ description=u"""
+ A pagelet can provide an interface. This would be used for
+ views that support other views.""",
+ required=False,
+ default=interfaces.IPagelet,
+ )
+
+
+# Arbitrary keys and values are allowed to be passed to the wizard.
+IWizardDirective.setTaggedValue('keyword_arguments', True)
+
+
+# Arbitrary keys and values are allowed to be passed to the step.
+IWizardStepDirective.setTaggedValue('keyword_arguments', True)
+
+
+# wizard directive
+def wizardDirective(
+ _context, class_, name, permission, for_=zope.interface.Interface,
+ layer=IDefaultBrowserLayer, provides=interfaces.IWizard,
+ allowed_interface=None, allowed_attributes=None, **kwargs):
+
+ # Security map dictionary
+ required = {}
+
+ # Get the permission; mainly to correctly handle CheckerPublic.
+ permission = viewmeta._handle_permission(_context, permission)
+
+ # The class must be specified.
+ if not class_:
+ raise ConfigurationError("Must specify a class.")
+
+ if not zope.interface.interfaces.IInterface.providedBy(provides):
+ raise ConfigurationError("Provides interface provide IInterface.")
+
+ ifaces = list(zope.interface.Declaration(provides).flattened())
+ if interfaces.IWizard not in ifaces:
+ raise ConfigurationError("Provides interface must inherit IWizard.")
+
+ # Build a new class that we can use different permission settings if we
+ # use the class more then once.
+ cdict = {}
+ cdict['__name__'] = name
+ cdict.update(kwargs)
+ new_class = type(class_.__name__, (class_, wizard.Wizard), cdict)
+
+ # Set up permission mapping for various accessible attributes
+ viewmeta._handle_allowed_interface(
+ _context, allowed_interface, permission, required)
+ viewmeta._handle_allowed_attributes(
+ _context, allowed_attributes, permission, required)
+ viewmeta._handle_allowed_attributes(
+ _context, kwargs.keys(), permission, required)
+ viewmeta._handle_allowed_attributes(
+ _context, ('__call__', 'browserDefault', 'update', 'render',
+ 'publishTraverse'), permission, required)
+
+ # Register the interfaces.
+ viewmeta._handle_for(_context, for_)
+
+ # provide the custom provides interface if not allready provided
+ if not provides.implementedBy(new_class):
+ zope.interface.classImplements(new_class, provides)
+
+ # Create the security checker for the new class
+ zope.security.checker.defineChecker(new_class,
+ zope.security.checker.Checker(required))
+
+ # register pagelet
+ _context.action(
+ discriminator = ('pagelet', for_, layer, name),
+ callable = zope.component.zcml.handler,
+ args = ('registerAdapter',
+ new_class, (for_, layer), provides, name, _context.info),)
+
+# step directive
+def wizardStepDirective(
+ _context, class_, name, permission, for_=zope.interface.Interface,
+ layer=IDefaultBrowserLayer, wizard=interfaces.IWizard,
+ provides=interfaces.IStep, allowed_interface=None,
+ allowed_attributes=None, **kwargs):
+
+ # Security map dictionary
+ required = {}
+
+ # Get the permission; mainly to correctly handle CheckerPublic.
+ permission = viewmeta._handle_permission(_context, permission)
+
+ # The class must be specified.
+ if not class_:
+ raise ConfigurationError("Must specify a class.")
+
+ if not zope.interface.interfaces.IInterface.providedBy(provides):
+ raise ConfigurationError("Provides interface provide IInterface.")
+
+ ifaces = list(zope.interface.Declaration(provides).flattened())
+ if interfaces.IPagelet not in ifaces:
+ raise ConfigurationError("Provides interface must inherit IPagelet.")
+
+ if not interfaces.IWizard.implementedBy(wizard):
+ raise ConfigurationError("Provides interface must inherit IWizard.")
+
+ # Build a new class that we can use different permission settings if we
+ # use the class more then once.
+ cdict = {}
+ cdict['__name__'] = name
+ cdict.update(kwargs)
+ new_class = type(class_.__name__, (class_, step.Step), cdict)
+
+ # Set up permission mapping for various accessible attributes
+ viewmeta._handle_allowed_interface(
+ _context, allowed_interface, permission, required)
+ viewmeta._handle_allowed_attributes(
+ _context, allowed_attributes, permission, required)
+ viewmeta._handle_allowed_attributes(
+ _context, kwargs.keys(), permission, required)
+ viewmeta._handle_allowed_attributes(
+ _context, ('__call__', 'browserDefault', 'update', 'render',
+ 'publishTraverse'), permission, required)
+
+ # Register the interfaces.
+ viewmeta._handle_for(_context, for_)
+
+ # provide the custom provides interface if not allready provided
+ if not provides.implementedBy(new_class):
+ zope.interface.classImplements(new_class, provides)
+
+ # Create the security checker for the new class
+ zope.security.checker.defineChecker(new_class,
+ zope.security.checker.Checker(required))
+
+ # register pagelet
+ _context.action(
+ discriminator = ('pagelet', for_, layer, name),
+ callable = zope.component.zcml.handler,
+ args = ('registerAdapter',
+ new_class, (for_, layer, wizard), provides, name, _context.info),)
Property changes on: z3c.wizard/trunk/src/z3c/wizard/zcml.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.wizard/trunk/src/z3c/wizard/zcml.txt
===================================================================
--- z3c.wizard/trunk/src/z3c/wizard/zcml.txt (rev 0)
+++ z3c.wizard/trunk/src/z3c/wizard/zcml.txt 2008-08-25 15:37:04 UTC (rev 90223)
@@ -0,0 +1,103 @@
+=========================
+Wizard and Step directive
+=========================
+
+Show how we can use the wizard and wizardStep directives. Register the meta
+configuration for the directive.
+
+ >>> import sys
+ >>> from zope.configuration import xmlconfig
+ >>> import z3c.wizard
+ >>> context = xmlconfig.file('meta.zcml', z3c.wizard)
+
+We need also a custom wizard class:
+
+ >>> import z3c.wizard
+ >>> class MyWizard(z3c.wizard.wizard.Wizard):
+ ... """Custom wizard"""
+
+Make them available under the fake package ``custom``:
+
+ >>> sys.modules['custom'] = type(
+ ... 'Module', (),
+ ... {'MyWizard': MyWizard})()
+
+Register a wizard within the directive with minimal attributes:
+
+ >>> context = xmlconfig.string("""
+ ... <configure
+ ... xmlns:z3c="http://namespaces.zope.org/z3c">
+ ... <z3c:wizard
+ ... name="wizard"
+ ... class="custom.MyWizard"
+ ... permission="zope.Public"
+ ... />
+ ... </configure>
+ ... """, context)
+
+Now define a step:
+
+ >>> import z3c.wizard
+ >>> class FirstStep(z3c.wizard.step.Step):
+ ... """First step"""
+
+register the new step classes in the custom module...
+
+ >>> sys.modules['custom'].FirstStep = FirstStep
+
+and use them in the ``wizardStep`` directive:
+
+ >>> context = xmlconfig.string("""
+ ... <configure
+ ... xmlns:z3c="http://namespaces.zope.org/z3c">
+ ... <z3c:wizardStep
+ ... name="first"
+ ... wizard="custom.MyWizard"
+ ... class="custom.FirstStep"
+ ... permission="zope.Public"
+ ... />
+ ... </configure>
+ ... """, context)
+
+Let's get the wizard
+
+ >>> import zope.component
+ >>> from zope.publisher.browser import TestRequest
+ >>> wizard = zope.component.queryMultiAdapter((object(), TestRequest()),
+ ... name='wizard')
+
+and check them:
+
+ >>> wizard
+ <MyWizard u'wizard'>
+
+ >>> z3c.wizard.interfaces.IWizard.providedBy(wizard)
+ True
+
+Let's get the wizard step
+
+ >>> import zope.component
+ >>> from zope.publisher.browser import TestRequest
+ >>> firstStep = zope.component.queryMultiAdapter(
+ ... (object(), TestRequest(), wizard), name='first')
+
+and check them
+
+ >>> firstStep
+ <FirstStep u'first'>
+
+ >>> firstStep.context
+ <object object at ...>
+
+ >>> firstStep.wizard
+ <MyWizard u'wizard'>
+
+ >>> z3c.wizard.interfaces.IStep.providedBy(firstStep)
+ True
+
+ >>> z3c.wizard.interfaces.IWizard.providedBy(firstStep.wizard)
+ True
+
+Clean up the custom module.
+
+ >>> del sys.modules['custom']
Property changes on: z3c.wizard/trunk/src/z3c/wizard/zcml.txt
___________________________________________________________________
Name: svn:eol-style
+ native
More information about the Checkins
mailing list