[Zope3-checkins] CVS: Zope3/src/zope/app/browser/form - editwizard.pt:1.1 editwizard.py:1.1 meta.zcml:1.12 submit.py:1.3
Stuart Bishop
zen@shangri-la.dropbear.id.au
Sat, 12 Jul 2003 02:18:47 -0400
Update of /cvs-repository/Zope3/src/zope/app/browser/form
In directory cvs.zope.org:/tmp/cvs-serv27310
Modified Files:
meta.zcml submit.py
Added Files:
editwizard.pt editwizard.py
Log Message:
Wizards generated from schemas, take I
=== Added File Zope3/src/zope/app/browser/form/editwizard.pt ===
<tal:tag condition="view/update"
/><html metal:use-macro="views/standard_macros/page">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="body">
<form action="." tal:attributes="action request/URL" method="POST"
enctype="multipart/form-data"
>
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Edit something</h3>
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" />
<div tal:condition="view/errors">
<ul>
<li tal:repeat="error view/errors">
<strong tal:content="error/__class__" i18n:translate="">
Error Type</strong>:
<span tal:content="error">Error text</span>
</li>
</ul>
</div>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row"
metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="row"
metal:define-macro="widget_rows" tal:repeat="widget view/widgets"
tal:content="structure widget/row">
<div class="label">Name</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div tal:replace="structure view/renderHidden">
<!-- type=hidden input controls for passing state without session -->
<input type="hidden" name="example" value="foo" />
</div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
</div>
<div class="row">
<div class="controls">
<!-- <input type="submit" value="Refresh"
i18n:attributes="value refresh-button" /> -->
<input tal:condition="view/show_previous"
type="submit" name="PREVIOUS_SUBMIT" value="Previous"
i18n:attributes="value previous-button" />
<input tal:condition="view/show_submit"
type="submit" name="UPDATE_SUBMIT" value="Submit"
i18n:attributes="value submit-button"/>
<input tal:condition="view/show_next"
type="submit" name="NEXT_SUBMIT" value="Next"
i18n:attributes="value next-button" />
</div>
</div>
</form>
</div>
</div>
</body>
</html>
=== Added File Zope3/src/zope/app/browser/form/editwizard.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""
$Id: editwizard.py,v 1.1 2003/07/12 06:18:40 Zen Exp $
"""
import logging
from UserDict import UserDict
from zope.interface import implements, classProvides
from zope.publisher.interfaces.browser import IBrowserPresentation
from zope.component import getAdapter
from zope.app.publisher.browser.globalbrowsermenuservice \
import menuItemDirective, globalBrowserMenuService
from zope.configuration.action import Action
from zope.configuration.interfaces import INonEmptyDirective
from zope.configuration.interfaces import ISubdirectiveHandler
from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from editview import normalize, EditViewFactory, EditView
from zope.security.checker import defineChecker, NamesChecker
from zope.app.context import ContextWrapper
from zope.component.view import provideView
from zope.app.form.utility import setUpEditWidgets, getWidgetsData
from submit import Next, Previous, Update
from zope.app.interfaces.form import WidgetsError
PaneNumber = 'CURRENT_PANE_IDX'
# TODO: Needs to be persistent aware for session (?)
class WizardStorage(dict):
def __init__(self, fields, content):
super(WizardStorage, self).__init__(self)
for k in fields:
self[k] = getattr(content,k)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError, key
def __setattr__(self, key, value):
self[key] = value
class WizardEditView(EditView):
def _setUpWidgets(self):
adapted = getAdapter(self.context, self.schema)
if adapted is not self.context:
adapted = ContextWrapper(adapted, self.context, name='(adapted)')
self.adapted = adapted
if self.use_session:
raise NotImplementedError, 'use_storage'
else:
self.storage = WizardStorage(self.fieldNames, adapted)
# Add all our widgets as attributes on this view
setUpEditWidgets(
self, self.schema, names=self.fieldNames, content=self.storage
)
def widgets(self):
return [getattr(self, name+'_widget')
for name in self.currentPane().names
]
_current_pane_idx = 0
def currentPane(self):
return self.panes[self._current_pane_idx]
_update_called = 0
def update(self):
'''
if not pane submitted,
current_pane = 0
else
if current_pane is valid:
if submit == 'Next':
current_pane += 1
elif submit == 'Previous':
current_pane -= 1
elif submit == 'Submit':
apply_changes()
return redirect(next_pane())
assert current_pane > 0
assert current_pane < len(self.panes())
display current_pane
'''
if self._update_called:
return
self._update_called = 1
# Determine the current pane
if PaneNumber in self.request:
self._current_pane_idx = int(self.request[PaneNumber])
assert self._current_pane_idx >= 0
assert self._current_pane_idx < len(self.panes)
else:
# First page
self._current_pane_idx = 0
self.errors = []
self.label = self.currentPane().label
self._choose_buttons()
return
# Validate the current pane, and set self.errors
try:
if self.use_session:
names = self.currentPane().names
else:
names = self.fieldNames
#data = getWidgetsData(
# self, self.schema, strict=False, set_missing=True,
# names=names, exclude_readonly=True
# )
data = getWidgetsData(
self, self.schema, strict=True, set_missing=True,
names=names, exclude_readonly=True
)
valid = 1
self.errors = []
except WidgetsError, errors:
self.errors = errors
valid = 0
data = getWidgetsData(
self, self.schema, strict=False, set_missing=True,
names=names, exclude_readonly=True, do_not_raise=True
)
logging.fatal('data is %r' % (data,))
self.storage.update(data)
for k,v in self.storage.items():
getattr(self,k).setData(v)
else:
self.storage.update(data)
if Next in self.request:
self._current_pane_idx += 1
assert self._current_pane_idx < len(self.panes)
elif Previous in self.request:
self._current_pane_idx -= 1
assert self._current_pane_idx >= 0
elif Update in self.request:
self.apply_update(self.storage)
# Set last_pane flag - last_pane always gets a submit button
if self._current_pane_idx == len(self.panes) - 1:
self.last_pane = True
else:
self.last_pane = False
# Set the current label
self.label = self.currentPane().label
self._choose_buttons()
def _choose_buttons(self):
# TODO: show_submit should be true if all fields on every pane
# except the current one are valid
self.show_submit = 1
self.show_next = (self._current_pane_idx < len(self.panes) - 1)
self.show_previous = self._current_pane_idx > 0
def renderHidden(self):
''' Render state as hidden fields. Also render hidden fields to
propagate self.storage if we are not using the session to do this.
'''
olist = []
out = olist.append
out('<input class="hiddenType" type="hidden" name="%s" value="%d" />'%(
PaneNumber, self._current_pane_idx
))
if self.use_session:
''.join(olist)
current_fields = self.currentPane().names
for k in self.fieldNames:
if k not in current_fields:
widget = getattr(self, k)
out(widget.hidden())
return ''.join(olist)
class Pane:
def __init__(self, field_names, label):
self.names = field_names
self.label = label
class EditWizardDirective:
classProvides(INonEmptyDirective)
implements(ISubdirectiveHandler)
def __init__(self, _context, name, schema, permission,
for_=None, class_=None, template=None, layer='default',
menu=None, title='Edit', use_session='yes'):
self.name = name
self.permission = permission
self.title = title
self.layer = layer
self.menu = menu
if use_session == 'yes':
self.use_session = True
elif use_session == 'no':
self.use_session = False
else:
raise ValueError('Invalid value %r for use_session'%(use_session,))
if menu:
actions = menuItemDirective(
_context, menu, for_ or schema, '@@' + name, title,
permission=permission)
else:
actions = []
schema, for_, bases, template, fields = normalize(
_context, schema, for_, class_, template, 'editwizard.pt',
fields=None, omit=None, view=WizardEditView
)
self.schema = schema
self.for_ = for_
self.bases = bases
self.template = template
self.all_fields = fields
self.panes = []
self.actions = actions
def pane(self, _context, fields, label=''):
# TODO: Maybe accept a default of None for fields, meaning 'all
# remaining fields
fields = [str(f) for f in fields.split(' ')]
# TODO: unittest validation
for f in fields:
if f not in self.all_fields:
raise ValueError(
'Field name is not in schema',
name, self.schema
)
self.panes.append(Pane(fields, label))
return []
def __call__(self):
self.actions.append(
Action(
discriminator=(
'view', self.for_, self.name, IBrowserPresentation,
self.layer
),
callable=EditWizardViewFactory,
args=(
self.name, self.schema, self.permission, self.layer,
self.panes, self.all_fields, self.template, 'editwizard.pt',
self.bases, self.for_, self.menu, u'', self.use_session
)
)
)
return self.actions
def EditWizardViewFactory(name, schema, permission, layer,
panes, fields, template, default_template, bases, for_,
menu=u'', usage=u'', use_session=True):
# XXX What about the __implements__ of the bases?
class_ = SimpleViewClass(template, used_for=schema, bases=bases)
class_.schema = schema
class_.panes = panes
class_.fieldNames = fields
class_.use_session = use_session
class_.generated_form = ViewPageTemplateFile(default_template)
class_.usage = usage or (
menu and globalBrowserMenuService.getMenuUsage(menu))
defineChecker(class_,
NamesChecker(("__call__", "__getitem__", "browserDefault"),
permission))
provideView(for_, name, IBrowserPresentation, class_, layer)
=== Zope3/src/zope/app/browser/form/meta.zcml 1.11 => 1.12 ===
--- Zope3/src/zope/app/browser/form/meta.zcml:1.11 Mon Jun 2 17:18:05 2003
+++ Zope3/src/zope/app/browser/form/meta.zcml Sat Jul 12 02:18:40 2003
@@ -2,6 +2,121 @@
<directives namespace="http://namespaces.zope.org/browser">
+ <directive name="editwizard" handler=".editwizard.EditWizardDirective">
+ <description>
+ Define an automatically generated edit wizard.
+
+ The editwizard directive creates and register's a view for
+ editing an object based on a schema.
+ </description>
+
+ <attribute name="name" required="yes">
+ <description>
+ The name of the generated View
+ </description>
+ </attribute>
+
+ <attribute name="schema" required="yes">
+ <description>
+ The schema from which the edit form is generated.
+
+ A schema is an interface that includes fields.
+ </description>
+ </attribute>
+
+ <attribute name="for" required="no">
+ <description>
+ The interface this page (view) applies to.
+
+ The view will be for all objects that implement this interface.
+
+ The schema is used if the for attribute is not specified.
+
+ If the for attribute is specified, then the objects views must
+ implement or be adaptable to the schema.
+ </description>
+ </attribute>
+
+ <attribute name="permission" required="yes">
+ <description>
+ The permission needed to use the view.
+ </description>
+ </attribute>
+
+ <attribute name="template" required="no">
+ <description>
+ An alternate template to use for the edit form.
+
+ XXX Need to document how to extend the default.
+ </description>
+ </attribute>
+
+ <attribute name="layer" required="no">
+ <description>
+ The layer the view is in.
+
+ A skin is composed of layers. It is common to put skin specific
+ views in a layer named after the skin. If the 'layer' attribute
+ is not supplied, it defaults to 'default'.
+ </description>
+ </attribute>
+
+ <attribute name="class" required="no">
+ <description>
+ A class to provide custom widget definitions or methods to be
+ used by a custom template.
+
+ This class is used as a mix-in class. As a result, it needn't
+ subclass any special classes, such as BrowserView.
+ </description>
+ </attribute>
+
+ <attribute name="menu" required="no">
+ <description>
+ The browser menu to include the edit form in.
+
+ Many views are included in menus. It's convenient to name
+ the menu in the page directive, rather than having to give a
+ separate menuItem directive.
+ </description>
+ </attribute>
+
+ <attribute name="title" required="no">
+ <description>
+ The browser menu label for the edit form
+
+ This attribute defaults to "Edit".
+ </description>
+ </attribute>
+
+ <attribute name="use_session" required="no">
+ <description>
+ If 'no', hidden input controls are used to maintain state
+ between panes in the wizard. Only simple data types can
+ be propogated with this method.
+
+ Defaults to 'yes'.
+ </description>
+ </attribute>
+
+ <subdirective name="pane"
+ description="Define a Pane (page) of the wizard">
+ <attribute name="fields" required="yes">
+ <description>
+ The fields and the order in which to display them. If this
+ is not specified, all schema fields will be displayed in the
+ order specified in the schema itself.
+ </description>
+ </attribute>
+ <attribute name="label" required="no">
+ <description>
+ The label used as the heading on this pane
+ </description>
+ </attribute>
+ </subdirective>
+
+ </directive>
+
<directive name="editform" handler=".editview.edit">
<description>
=== Zope3/src/zope/app/browser/form/submit.py 1.2 => 1.3 ===
--- Zope3/src/zope/app/browser/form/submit.py:1.2 Wed Dec 25 09:12:32 2002
+++ Zope3/src/zope/app/browser/form/submit.py Sat Jul 12 02:18:40 2003
@@ -19,4 +19,7 @@
$Id$
"""
+Next = "NEXT_SUBMIT"
+Previous = "PREVIOUS_SUBMIT"
Update = "UPDATE_SUBMIT"
+