[Checkins] SVN: z3c.form/trunk/ - Feature: An event handler for
``ActionErrorOccurred`` events is registered
Stephan Richter
srichter at cosmos.phy.tufts.edu
Tue Aug 14 11:44:04 EDT 2007
Log message for revision 78810:
- Feature: An event handler for ``ActionErrorOccurred`` events is registered
to merge the action error into the form's error collectors, such as
``form.widgets.errors`` and ``form.widgets['name'].error`` (if
applicable). It also sets the status of the form. (Thanks to Herman
Himmelbauer, who requested the feature, for providing use cases.)
- Feature: Action can now raise ``ActionExecutionError`` exceptions that will
be handled by the framework. These errors wrap the original error. If an
error is specific to a widget, then the widget name is passed to a special
``WidgetActionExecutionError`` error. (Thanks to Herman Himmelbauer, who
requested the feature, for providing use cases.)
- Feature: After an action handler has been executed, an action executed event
is sent to the system. If the execution was successful, the event is
``ActionSuccessfull`` event is sent. If an action execution error was
raised, the ``ActionErrorOccurred`` event is raised. (Thanks to Herman
Himmelbauer, who requested the feature, for providing use cases.)
Changed:
U z3c.form/trunk/CHANGES.txt
U z3c.form/trunk/src/z3c/form/action.py
U z3c.form/trunk/src/z3c/form/action.txt
U z3c.form/trunk/src/z3c/form/configure.zcml
U z3c.form/trunk/src/z3c/form/form.py
U z3c.form/trunk/src/z3c/form/form.txt
U z3c.form/trunk/src/z3c/form/interfaces.py
U z3c.form/trunk/src/z3c/form/testing.py
-=-
Modified: z3c.form/trunk/CHANGES.txt
===================================================================
--- z3c.form/trunk/CHANGES.txt 2007-08-14 11:50:24 UTC (rev 78809)
+++ z3c.form/trunk/CHANGES.txt 2007-08-14 15:44:04 UTC (rev 78810)
@@ -5,6 +5,24 @@
Version 1.6.0 (?/??/2007)
-------------------------
+- Feature: An event handler for ``ActionErrorOccurred`` events is registered
+ to merge the action error into the form's error collectors, such as
+ ``form.widgets.errors`` and ``form.widgets['name'].error`` (if
+ applicable). It also sets the status of the form. (Thanks to Herman
+ Himmelbauer, who requested the feature, for providing use cases.)
+
+- Feature: Action can now raise ``ActionExecutionError`` exceptions that will
+ be handled by the framework. These errors wrap the original error. If an
+ error is specific to a widget, then the widget name is passed to a special
+ ``WidgetActionExecutionError`` error. (Thanks to Herman Himmelbauer, who
+ requested the feature, for providing use cases.)
+
+- Feature: After an action handler has been executed, an action executed event
+ is sent to the system. If the execution was successful, the event is
+ ``ActionSuccessfull`` event is sent. If an action execution error was
+ raised, the ``ActionErrorOccurred`` event is raised. (Thanks to Herman
+ Himmelbauer, who requested the feature, for providing use cases.)
+
- Feature: The ``applyChanges()`` function now returns a dictionary of changes
(grouped by interface) instead of a boolean. This allows us to generate a
more detailed object-modified event. If no changes are applied, an empty
Modified: z3c.form/trunk/src/z3c/form/action.py
===================================================================
--- z3c.form/trunk/src/z3c/form/action.py 2007-08-14 11:50:24 UTC (rev 78809)
+++ z3c.form/trunk/src/z3c/form/action.py 2007-08-14 15:44:04 UTC (rev 78810)
@@ -22,6 +22,30 @@
from z3c.form import interfaces, util
+class ActionEvent(object):
+ zope.interface.implements(interfaces.IActionEvent)
+
+ def __init__(self, action):
+ self.action = action
+
+ def __repr__(self):
+ return '<%s for %r>' %(self.__class__.__name__, self.action)
+
+
+class ActionErrorOccurred(ActionEvent):
+ """An event telling the system that an error occurred during action
+ execution."""
+ zope.interface.implements(interfaces.IActionErrorEvent)
+
+ def __init__(self, action, error):
+ super(ActionErrorOccurred, self).__init__(action)
+ self.error = error
+
+
+class ActionSuccessful(ActionEvent):
+ """An event signalizing that an action has been successfully executed."""
+
+
class Action(object):
"""Action class."""
@@ -66,13 +90,18 @@
def execute(self):
"""See z3c.form.interfaces.IActions."""
- adapter = None
for action in self.executedActions:
- adapter = zope.component.queryMultiAdapter(
+ handler = zope.component.queryMultiAdapter(
(self.form, self.request, self.content, action),
interface=interfaces.IActionHandler)
- if adapter is not None:
- return adapter()
+ if handler is not None:
+ try:
+ result = handler()
+ except interfaces.ActionExecutionError, error:
+ zope.event.notify(ActionErrorOccurred(action, error))
+ else:
+ zope.event.notify(ActionSuccessful(action))
+ return result
def __repr__(self):
return '<%s %r>' % (self.__class__.__name__, self.__name__)
Modified: z3c.form/trunk/src/z3c/form/action.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/action.txt 2007-08-14 11:50:24 UTC (rev 78809)
+++ z3c.form/trunk/src/z3c/form/action.txt 2007-08-14 15:44:04 UTC (rev 78810)
@@ -185,3 +185,55 @@
>>> request = TestRequest(form={'cancel': 'Cancel'})
>>> manager.request = apply.request = cancel.request = request
>>> manager.execute()
+
+Further, when a handler is successfully executed, an event is sent out, so
+let's register an event handler:
+
+ >>> eventlog = []
+ >>> @zope.component.adapter(interfaces.IActionEvent)
+ ... def handleEvent(event):
+ ... eventlog.append(event)
+
+ >>> zope.component.provideHandler(handleEvent)
+
+Let's now execute the "Apply" action again:
+
+ >>> request = TestRequest(form={'apply': 'Apply'})
+ >>> manager.request = apply.request = cancel.request = request
+ >>> manager.execute()
+ successfully applied
+
+ >>> eventlog[-1]
+ <ActionSuccessful for <Action 'apply' u'Apply'>>
+
+Action handlers, however, can also raise action errors. These action errors
+are caught and an event is created notifying the system of the problem. The
+error is not further propagated. Other errors are not handled by the system to
+avoid hiding real failures of the code.
+
+Let's see how action errors can be used by implementing a handler for the
+cancel action:
+
+ >>> class ErrorActionHandler(action.ActionHandlerBase):
+ ... zope.component.adapts(
+ ... None, TestRequest, None, util.getSpecification(cancel))
+ ... def __call__(self):
+ ... raise interfaces.ActionExecutionError(
+ ... zope.interface.Invalid('Something went wrong'))
+
+ >>> zope.component.provideAdapter(ErrorActionHandler)
+
+As you can see, the action execution error wraps some other execption, in this
+case a simple invalid error.
+
+Executing the "Cancel" action now produces the action error event:
+
+ >>> request = TestRequest(form={'cancel': 'Cancel'})
+ >>> manager.request = apply.request = cancel.request = request
+ >>> manager.execute()
+
+ >>> eventlog[-1]
+ <ActionErrorOccurred for <Action 'cancel' u'Cancel'>>
+
+ >>> eventlog[-1].error
+ <ActionExecutionError wrapping <zope.interface.exceptions.Invalid ...>>
Modified: z3c.form/trunk/src/z3c/form/configure.zcml
===================================================================
--- z3c.form/trunk/src/z3c/form/configure.zcml 2007-08-14 11:50:24 UTC (rev 78809)
+++ z3c.form/trunk/src/z3c/form/configure.zcml 2007-08-14 15:44:04 UTC (rev 78810)
@@ -89,6 +89,9 @@
<adapter
factory=".button.ButtonActionHandler"
/>
+ <subscriber
+ handler=".form.handleActionError"
+ />
<!-- Error Views -->
<adapter
Modified: z3c.form/trunk/src/z3c/form/form.py
===================================================================
--- z3c.form/trunk/src/z3c/form/form.py 2007-08-14 11:50:24 UTC (rev 78809)
+++ z3c.form/trunk/src/z3c/form/form.py 2007-08-14 15:44:04 UTC (rev 78810)
@@ -66,6 +66,34 @@
f_locals['handlers'] += getattr(arg, 'handlers', button.Handlers())
+ at zope.component.adapter(interfaces.IActionErrorEvent)
+def handleActionError(event):
+ # Only react to the event, if the form is a standard form.
+ if not (interfaces.IFormAware.providedBy(event.action) and
+ interfaces.IForm.providedBy(event.action.form)):
+ return
+ # If the error was widget-specific, look up the widget.
+ widget = None
+ if isinstance(event.error, interfaces.WidgetActionExecutionError):
+ widget = event.action.form.widgets[event.error.widgetName]
+ # Create an error view for the error.
+ action = event.action
+ form = action.form
+ errorView = zope.component.getMultiAdapter(
+ (event.error.error, action.request, widget,
+ getattr(widget, 'field', None), form, form.getContent()),
+ interfaces.IErrorViewSnippet)
+ errorView.update()
+ # Assign the error view to all necessary places.
+ if widget:
+ widget.error = errorView
+ form.widgets.errors += (errorView,)
+ # If the form supports the ``formErrorsMessage`` attribute, then set the
+ # status to it.
+ if hasattr(form, 'formErrorsMessage'):
+ form.status = form.formErrorsMessage
+
+
class BaseForm(browser.BrowserPage):
"""A base form."""
zope.interface.implements(interfaces.IForm,
Modified: z3c.form/trunk/src/z3c/form/form.txt
===================================================================
--- z3c.form/trunk/src/z3c/form/form.txt 2007-08-14 11:50:24 UTC (rev 78809)
+++ z3c.form/trunk/src/z3c/form/form.txt 2007-08-14 15:44:04 UTC (rev 78810)
@@ -1158,7 +1158,7 @@
... fields = field.Fields(IPerson).select('name')
...
... @button.buttonAndHandler(u'Apply')
- ... def handleApply(self, form):
+ ... def handleApply(self, action):
... print 'success'
>>> BaseForm.fields.keys()
@@ -1174,7 +1174,7 @@
... fields = field.Fields(IPerson).select('gender')
...
... @button.buttonAndHandler(u'Cancel')
- ... def handleCancel(self, form):
+ ... def handleCancel(self, action):
... print 'cancel'
>>> DerivedForm.fields.keys()
@@ -1195,7 +1195,7 @@
... fields += field.Fields(IPerson).select('gender')
...
... @button.buttonAndHandler(u'Cancel')
- ... def handleCancel(self, form):
+ ... def handleCancel(self, action):
... print 'cancel'
>>> DerivedForm.fields.keys()
@@ -1216,7 +1216,7 @@
... fields += field.Fields(IPerson).select('gender')
...
... @button.buttonAndHandler(u'Cancel')
- ... def handleCancel(self, form):
+ ... def handleCancel(self, action):
... print 'cancel'
>>> DerivedForm.fields.keys()
@@ -1237,7 +1237,7 @@
... fields += field.Fields(IPerson).select('gender')
...
... @button.buttonAndHandler(u'Cancel')
- ... def handleCancel(self, form):
+ ... def handleCancel(self, action):
... print 'cancel'
>>> DerivedForm.fields.keys()
@@ -1320,3 +1320,77 @@
name="form.widgets.age" class="hidden-widget"
value="29" />
...
+
+
+Actions with Errors
+-------------------
+
+Even though the data might be validated correctly, it sometimes happens that
+data turns out to be invalid while the action is executed. In those cases a
+special action execution error can be raised that wraps the original error.
+
+ >>> class PersonAddForm(form.AddForm):
+ ...
+ ... fields = field.Fields(IPerson).select('id')
+ ...
+ ... @button.buttonAndHandler(u'Check')
+ ... def handleCheck(self, action):
+ ... data, errors = self.extractData()
+ ... if data['id'] in self.getContent():
+ ... raise interfaces.WidgetActionExecutionError(
+ ... 'id', zope.interface.Invalid('Id already exists'))
+
+In this case the action execution error is specific to a widget. The framework
+will attach a proper error view to the widget and the widget manager:
+
+ >>> request = TestRequest(form={
+ ... 'form.widgets.id': u'srichter',
+ ... 'form.buttons.check': u'Check'}
+ ... )
+
+ >>> addForm = PersonAddForm(root, request)
+ >>> addForm.update()
+
+ >>> addForm.widgets.errors
+ (<InvalidErrorViewSnippet for Invalid>,)
+ >>> addForm.widgets['id'].error
+ <InvalidErrorViewSnippet for Invalid>
+ >>> addForm.status
+ u'There were some errors.'
+
+If the error is non-widget specific, then we can simply use the generic action
+execution error:
+
+ >>> class PersonAddForm(form.AddForm):
+ ...
+ ... fields = field.Fields(IPerson).select('id')
+ ...
+ ... @button.buttonAndHandler(u'Check')
+ ... def handleCheck(self, action):
+ ... raise interfaces.ActionExecutionError(
+ ... zope.interface.Invalid('Some problem occurred.'))
+
+Let's have a look at the result:
+
+ >>> addForm = PersonAddForm(root, request)
+ >>> addForm.update()
+
+ >>> addForm.widgets.errors
+ (<InvalidErrorViewSnippet for Invalid>,)
+ >>> addForm.status
+ u'There were some errors.'
+
+__Note__:
+
+ The action execution errors are connected to the form via an event
+ listener called ``handlerActionError``. This event listener listens for
+ ``IActionErrorEvent`` events. If the event is called for an action associated
+ with a form, the listener does its work as seen above. If the action is not
+ coupled to a form, then event listener does nothing:
+
+ >>> from z3c.form import action
+
+ >>> cancel = action.Action(request, u'Cancel')
+ >>> event = action.ActionErrorOccurred(cancel, ValueError(3))
+
+ >>> form.handleActionError(event)
Modified: z3c.form/trunk/src/z3c/form/interfaces.py
===================================================================
--- z3c.form/trunk/src/z3c/form/interfaces.py 2007-08-14 11:50:24 UTC (rev 78809)
+++ z3c.form/trunk/src/z3c/form/interfaces.py 2007-08-14 15:44:04 UTC (rev 78810)
@@ -522,6 +522,25 @@
# ----[ Actions ]------------------------------------------------------------
+class ActionExecutionError(Exception):
+ """An error that occurs during the execution of an action handler."""
+
+ def __init__(self, error):
+ self.error = error
+
+ def __repr__(self):
+ return '<%s wrapping %r>' %(self.__class__.__name__, self.error)
+
+
+class WidgetActionExecutionError(ActionExecutionError):
+ """An action execution error that occurred due to a widget value being
+ incorrect."""
+
+ def __init__(self, widgetName, error):
+ ActionExecutionError.__init__(self, error)
+ self.widgetName = widgetName
+
+
class IAction(zope.interface.Interface):
"""Action"""
@@ -544,6 +563,25 @@
"""Action handler."""
+class IActionEvent(zope.interface.Interface):
+ """An event specific for an action."""
+
+ action = zope.schema.Object(
+ title=_('Action'),
+ description=_('The action for which the event is created.'),
+ schema=IAction,
+ required=True)
+
+
+class IActionErrorEvent(IActionEvent):
+ """An action event that is created when an error occurred."""
+
+ error = zope.schema.Field(
+ title=_('Error'),
+ description=_('The error that occurred during the action.'),
+ required=True)
+
+
class IActions(IManager):
"""A action manager"""
@@ -554,9 +592,14 @@
"""Setup actions."""
def execute():
- """Exceute actions."""
+ """Exceute actions.
+ If an action execution error is raised, the system is notified using
+ the action occurred error; on the other hand, if successful, the
+ action successfull event is sent to the system.
+ """
+
class IButton(zope.schema.interfaces.IField):
"""A button in a form."""
Modified: z3c.form/trunk/src/z3c/form/testing.py
===================================================================
--- z3c.form/trunk/src/z3c/form/testing.py 2007-08-14 11:50:24 UTC (rev 78809)
+++ z3c.form/trunk/src/z3c/form/testing.py 2007-08-14 15:44:04 UTC (rev 78810)
@@ -28,7 +28,7 @@
from zope.app.testing import setup
from z3c.form import browser, button, converter, datamanager, error, field
-from z3c.form import interfaces, term, validator, widget
+from z3c.form import form, interfaces, term, validator, widget
from z3c.form.browser import radio, select, text
@@ -143,6 +143,8 @@
zope.component.provideAdapter(button.ButtonActions)
# Adapter to use form.handlers to generate handle actions
zope.component.provideAdapter(button.ButtonActionHandler)
+ # Subscriber handling action execution error events
+ zope.component.provideHandler(form.handleActionError)
# Error View(s)
zope.component.provideAdapter(error.ErrorViewSnippet)
zope.component.provideAdapter(error.InvalidErrorViewSnippet)
More information about the Checkins
mailing list