WidgetsAndMultiwayAdapters
Widgets and Multi-way Adapters
Status
Author
Garrett Smith
Problems
Currently widget names, "edit" and "display" are used to identify widget behavior. The names serve as implicit interfaces. This was done because, until recently, views, such as widgets, could not be registered and looked up according to the interface they provide. With recent changes to adapter and view support, it is now possible to register and look up views based on the interfaces they provide.
Looking up views by name has two disadvantages:
- The interfaces they provide are implicit,
- The name cannot be used for other purposes (e.g. for alternative widget configurations).
The functions in zope/app/form/utility.py have evolved over time to become unnecessarily complex and as a result are hard to use and maintain.
Goals
We want to lookup widgets by specifying a "providing" interface instead of special view names.
We want to cleanup the utility functions in zope/app/form/utility.py.
Proposed Solution
Changes to Widget Lookup
The primary goal of this refactoring is to change the way widgets are acquired for use in a form. Currently, widgets are looked up using a special naming convention. For example, to get a widget for editing an ITextLine? field, one would currently use:
zapi.getView(field, "edit", request)
This approach has always been viewed as a temporary solution until multi-way adapters were implemented. We've had multi-way adapters since ComponentArchitectureSimplification.
Instead of looking up widgets by name, we want to get an adapter for a particular field that provides an interface that is appropriate for a particular application. For example, to lookup a widget for a text field on an edit form, we should be able to find an interface that provides an interface suitable for text editing:
zapi.getViewProviding(field, IInputWidget, request)
This involves the following changes:
- Define widget interfaces that replace the use of "edit" and "display". The proposed interfaces are IInputWidget? (for use in add and edit forms) and IDisplayWidget? (for use in schema display views and for read-only fields).
- Change the way widgets are registered. Instead of registering views using special names, one would register views that provide specific interfaces.
Changes to Utility Functions
Some of the changes need in zope/app/form/utility.py relate to the
proposed widget lookup scheme. Others are intended to simplify the
module with the intent of making it easier to use and maintain.
Remove "Whining Widgets"
This was implemented to advertise deprecation and is no longer needed.
Change setUpWidget
- Replace the use of view name (
vname) with view type (viewType). For example, the use of vname="edit" will typically be replaced using viewType=IInputWidget?. - Replace None as the default value of
valuewith a marker objectno_valuethat indicates the widget should not be configured with a value. In some cases, None is a valid value for a widget to render and so cannot be used to imply thatno valuehas been specified. - Rename
forcetoignoreStickyValues. This name highlights subtle and important behavior by introducing the concept of a "sticky value". (This term is borrowed from the book "XForms?: XML Powered Web Forms" by T.V. Raman, Addison Wesley, 2004.) A sticky value is a value that a widget continues to display over multiple transactions without having applied the value to an object. Sticky values are currently displayed when a form validation error occurs - the underlying object is not updated and the previously entered values are still displayed in the form. - Refactor the implementation to clarify its behavior.
Change setUpWidgets
This function iterates through a list of fields to setup individual widgets. The changes to setUpWidget would also be applied to this function.
- Rename
contenttosourceto help clarify its use and to further distinguish it fromcontext.sourceis the object from which widget values are read. - Replace
vnamewithviewType. - Rename
forcetoignoreStickyValues.
Change setUpEditWidgets and setUpDisplayWidgets
- Rename
contenttosourceto clarify its use and help distinguish it fromcontext. - Rename
forcetoignoreStickyValues. - Because widgets are looked up by interface, the names "edit" and "display" must be replaced with IInputWidget? and IDisplayWidget?, respectively.
Change applyWidgetChanges
- Rename
contenttotargetto clarify its use and help distinguish it fromcontext. Also move the argument to the third position and made it optional to follow the pattern used by setUpEditWidgets and setUpDisplayWidgets.targetshould be made optional to simplify the use of this function, as most uses will apply changes to the view context. - Remove
strictandset_missingbecause they aren't used. - Remove
do_not_raise- a WidgetsError? should always be raised whenever a widget raise InputError? on applyChanges. We want to avoid the practice of disabling exceptions by passing switches to functions. - Remove
exclude_read_onlybecause widgets for read only fields will not, by definition, have input from the user to apply.
Change getWidgetsData
- Remove
strict, which is used to specify whether or not missing required input causes a WidgetsError? to be raised. An error should always raised in this case.To address caller's need to access the widget values obtained from the view, even when an error is raised, WidgetsError? will provide a
widgetsDataattribute that is a map of values, keyed by field name. Uses of getWidgetsData where strict=False should use this pattern:try: result = getWidgetsData(view, schema) except WidgetsData, e: partialResult = e.widgetsData - Remove
set_missing, which determines whether or not a field's missing_value (see IField?) is stored in the result for a widget that does not have input. The decision to use missing_value should be implemented as an explicit policy by the caller. For example:result = getWidgetsData(view, schema) for name in schema: if name not in result: result[name] = schema[name].missing_value - Remove
do_not_raise, which is used to specify whether or not InputErrors? raised by widgets (when their input is requested) causes a WidgetsError? to be raised. The same argument used for removingstrictapplies to this case. WidgetsError?'s widgetsData could be used to access any obtained widget values. - Remove
exclude_readonly. Widgets displaying read only fields will never have input and should never be returned in the result.The cases where this flag is set to False are when an object is being added by an add form. In this case, some read only fields should be editable so that the new object can be initialized. However, this approach should be handled by specifying an alternative interface to the add form that contains writeable fields as needed for an add scenario.
Delete getWidgetsDataForAdapter
This function is a convenience function for looking up data on an adapter and is not directly related to widgets or forms. The sole use of this function (zope/app/dav/propfind.py) should be updated with the code to perform the lookup itself without the help of a function. (See lines 110-199 or propfind.py in garrett-widgets2-branch.)
Risks
Changes to Widget Lookup Process
While this is a relatively simple change, it impacts any code that registers or uses widgets. The rework, however, should be almost entirely limited to the core Zope core. Projects that register or lookup widgets will require some mechanical rework.
This proposal does not consider any backward compatibility provisions - effected projects would have to be updated as soon as they are updated to the head.
Changes to Utility Functions
The proposed modifications to the utility functions will generally only impact the core code. Projects using any of these functions may be effected.
This proposal does not consider any backward compatibility provisions for changes to the utility functions.
