Cleanup of Schema and Widgets

Cleanup of Misc Schema and Widget Issues

Status

IsProposal

Author

Garrett Smith

Background

Back in late February, I met with Jim, Gary Poster, and Fred Drake to discuss the state of "widgets" machinery. We had an excellent brain storming sessions, the notes of which are reflected in this proposal.

Goals

Continued improvement of the widgets machinery.

Proposed Solution

ZCML for Multi-view Lookup (IsImplemented)

We need ZCML support for declaring views for multiple interfaces. This will be used by the vocabulary widgets for looking up views for a) view types (IInputWidget? or IDisplayWidget?) and b) vocabulary types.

Changes to Vocabulary Implementation (IsImplemented)

The vocabulary widgets still use the old convention of looking up views by name instead of by view type. The lookup mechanism should be updated to use zapi.getViewProviding with specific vocabulary view interfaces.

Changes to Fields

These changes are to simplify some of the existing field types:

Vocabulary Fields (IsImplemented)

The VocabularyXyx? fields should be renamed to their Xyx component:

  • VocabularySet? -> Set - VocabularyList? -> List - VocabularyUniqueList? -> List with unique=True attribute

Set and List (all Sequence fields) should have a value_type field that specifies the type of values contained by Set and List objects.

For example:

          list = VocabularyList(name="postal_code")

where postal_code is the name of a vocabulary declared elsewhere, would become:

          list = List(value_type=Choice(vocabulary="postal_code"))

VocabularyField? should be replaced by Choice &emdash; see the next section for more information.

VocabularyBag? is not being used and should be removed. Bag can be represented by setting unique=True on a Set field.

Enumerated Fields (IsImplemented)

Remove all EnumeratedXXX? fields and replace their use with a Choice field. Choice would support either a values or vocabulary field to specify its allowed values.

For example:

          postalCode = EnumeratedText(allowed_values=('11111', '22222'))

would become:

          postalCode = Choice(values=('11111', '22222'))

or:

          postalCode = Choice(vocabulary="postal_code")

where postal_code is the name of a vocabulary declared elsewhere. Note that internally the passed in values are also converted to a vocabulary. This way we can use one well-defined way of dealing with choices.

In addition to simplifying the use of "enumerated" fields, this change enables mixed content fields. For example, the following would be legal:

          width = Choice(values=(480, 640, "25%", "50%", "75%"))

Sequence Fields (IsImplemented)

Sequence fields should support the following new constraint:

  • unique - a boolean value indicating whether or not the values in the sequence/set must be unique

Both Set and Sequence fields should also support:

  • value_type - a type indicating the types of objects that may be contained by the sequence/set

Misc Field Changes (IsImplemented)

The __doc__ argument should be removed for fields.

The title and description arguments should be moved to the front of the argument list for fields.

(SR): Removing __doc__ was no problem, and moving title and description to the front of the Field constructor theoretically neither. However, if you specify the title and description as the first two arguments (not as keywords), you will often not get what you expect, since many of the Field mixins do not honor this order. Therefore I would suggest keep using keyword arguments.

Schema Arithmetic

It should be possible to generate a new schema by applying a transformation to an existing schema.

One use case for this is the Add form. In some cases, the Add form needs to update a field that is "read only" in the target object schema. Since the Add form deals with initializing an object, it should have write access to that field. (The Edit form, however, should not.)

The solution to this would be to create a schema for use by the Add form that is a transformation of the object schema &emdash; in the example, the only change would be that the field in question be designated as writable instead of read only.

XXX details, code examples, etc. needed

Widget Related Changes

Widget Layout Implementation (IsImplemented)

We want to move the ZPT implementation out of any pages that use it (e.g. edit.pt, display.pt, etc.) into its own page for use as a macro. The will allow for easier maintenance of the code as well as let that portion of the pages be skinned.

Enhancements to IWidget? (IsImplemented)

Currently, a widget relies exclusively on its context field for its label and tooltip. To change a widget's label and tooltip, you must change the field's title and description, respectively. This is obviously a bad thing.

The following fields should be added to IWidget?:

  • label - if specified, this would be used as the widget's label; if not specified, the widget would use its field's title
  • hint - if specified, this would be used as the widget's tooltip; if not, the widget would use its field's description

The term "hint" is used instead of "tooltip" to clarify that widgets are free to display this information in various ways, tooltips being one. (This is borrowed from the XForms? specification.)

Cleanup Widget Implementation Framework

Currently, all browser widgets extend zope.app.form.BrowserWidget?. However, BrowserWidget? assumes a particular implementation that is for simple widgets that are rendered using single tags like input and select. BrowserWidget? is not well suited for complex widgets like those used for vocabularies.

Create an InputWidget? Mixin

This would be a simple mixin that would provide any functionality that could be implemented across all of the widgets, at least by default.

Changes to BrowserWidget?

This class should become a simple mixin for browser widgets. It should not assume any particular implementation in terms of form/widget relationships.

Create zope.app.form.browser.SimpleInputWidget?

This new class would take on most of the functionality currently handled by BrowserWidget?. The choice of "SimpleInputWidget?" reflects the fact that the browser is a) an input widget and b) simple. I prefer it to leave out the term "browser" in the name for brevity.

Along with the class rename, I propose the following changes:

  • Rename _convert to _toFieldValue
  • Rename _unconvert to _toFormValue
  • Rename _showData to _getFormValue
  • Remove deprecation warnings
Change Several Widgets' Base Class from BrowserWidget? to SimpleInputWidget?

All of the widgets in textwidgets.py, boolwidgets.py, and itemswigets.py should be derrived from SimpleInputWidget?.

Widgets that aggregate other widgets or otherwise use multiple HTML form elements (e.g. sequence widgets and object widgets) should continue to subclass BrowserWidget?. They may use the InputWidget? mixin described above.

Decouple Widget.required from Field.required

Currently a widget's required value is always equal to its field's required value. I believe this is wrong. The widget's required attribute should indicate how the widget is displayed in the UI, not whether or not the value is required. In fact, the widget validation correctly uses the field's required value, not the widget's required value.

The widget's required value should default to the field's, but it should be possible to change the widget's value in order to change the way the widget is displayed.

This requirement comes up for widgets that always provide a value such as the checkbox. In the case of the checkbox, it makes no sense to indicate that it's "required" in a form &emdash; it's impossible for a user not to enter a value.

The requirement also applies to the text widget. In some cases, a text widget may be similar to a checkbox in that it's impossible for a user not to enter a value. This may occur when an empty string is not considered a missing value and would therefore satisfy a required=True constraint. In such a case, we would not want to indicate that the widget input is required, even though the field input is required.

ZCML Support for Widgets in Form Pages (IsImplemented)

To configure widgets for a page, a developer must (a) create a view class and (b) configure its widgets using CustomWidgetFactory? attributes. It should also be possible to simply declare the widgets as part of the form's ZCML declaration.

For example, the following sample ZCML declares an edit form and specifies a widget to use for the foo field:

          <editform
            name="edit.html"
            schema="IMyContent"
            permission="zope.ManageContent">

            <widget
              field="foo"
              label="Foo"
              hint="Specify the value for 'foo'."
              factory=".MyFooWidget"
              width="30" />

          </editform>

The widget directive should have only two mandatory attributes, the field which specifies the name of the field in the schema and the factory which is a dotted name to the widget class.

All other attributes represent attributes/properties of the widget. It is the widget's responsibility to process the provided data correctly. Since widgets are just views, their constructors must be of the form __init__(self, context, request). That means all additional attributes should be set after creation, which is no problem, since we have the attribute name and value.

A serious disadvantage of this approach is that the values of the attributes will be always unicode strings, since there is no way to specify their fields generically. There are a couple of ways to solve the issue.

  1. Make the widget directive itself complex and allow the following syntax:
                 <widget field="foo" factory=".MyFooWidget">
                   <attribute 
                       name="label" 
                       type="zope.schema.TextLine" 
                       value="Foo" />
    
                   <attribute 
                       name="hint" 
                       type="zope.schema.Text" 
                       value="Specify the value for 'foo'." />
    
                   <attribute 
                       name="width" 
                       type="zope.schema.Int" 
                       value="30" />
                 </widget>
    

I like this approach, since it is very ZCMLish? and can be implemented without new machinery. On the other hand it feels extremly verbose, especially since we usually only have strings or numbers anyways.

  1. We could define some simple syntax/structure for the attribute value, like we did for message ids. Then the above example could be something like that:
                 <widget
                   field="foo"
                   label="Foo"
                   hint="Specify the value for 'foo'."
                   factory=".MyFooWidget"
                   width="int:30" />
    

We could have a simple prefix that specifies the Python type that should be used to convert the object. I think this is much better, since it is short and obvious. However, here we really assume that we will usually have only standard Python types as values. Maybe the prefix could also be a python path to a field, but that might look ugly:

             <widget
               field="foo"
               label="Foo"
               hint="Specify the value for 'foo'."
               factory=".MyFooWidget"
               width="zope.schema.Int:30" />

  1. Three weeks later... I just thought about the right way to do it. Since we know the widget to use, we also have its schema, so we know all about its attributes, their type and any other metadata. In fact we even have a fromUnicode(value) method lying around.

Other than that, I think the widget directive should have no meaning and act in any way like the current way of writing a custom view class in which the widget is placed. There was a concern whether declared custom widgets should be able to influence the fields and order in a form. I think this should be still left to the fields attribute of the form. If "foo" is not specified in fields, it should not be displayed, even though we created a custom widget directive for it. Any other behavior would be inconsistent.

Risks

Changes that will break existing applications will include the appropriate deprecations to preserve backward compatibility for a period of time.

Note: There was no way to be backward-compatible with the previous API. Since we reset soon anyways, this is not so bad. (SR)


comments:

Good idea overall and necessary for rapid content type dev --philikon, 2004/03/25 03:29 EST reply
If not commented here, I agree with what is stated above.

Sequence and Set fields should support the following new constraints:
  • unique - a boolean value indicating whether or not the values in the sequence/set must be unique * type - a type indicating the types of objects that may be contained by the sequence/set

The latter is already taken care of in the value_type constraint of sequence fields.

Create SimpleBrowserWidget??, that implements functionality suitable for single tag widgets. This would be a cleaned up and well documented version of the current BrowserWidget?? class. It will be easy to use by developers creating new widgets.

I would rather called the cleaned up, simple version BrowserWidget and the more fancy one automatically using input tags etc. FancyBrowserWidget or something like that. That way, we'll have an analogy from BrowserView to BrowserWidget.

The List widget needs to be fixed. (XXX how is it broken?)

I don't think it is. Over christmas, I worked on sequence fields and they seemed to work just fine.

Changes that will break existing applications will include the appropriate deprecations to preserve backward compatibility for a period of time.

Well, if we do this before X3.0 (I think we should), we won't have to provide backward compatability. We didn't provide it during the extensive tree restructuring (then again, that was only a restructuring). Zope3 already has so much backward compatability stuff that has been around way longer than originally intended. I think we can rip it all out for X3.0...

... --garrett, 2004/03/26 10:23 EST reply

The latter is already taken care of in the value_type constraint of sequence fields.

Yep...I renamed type to value_type.

I would rather called the cleaned up, simple version BrowserWidget and the more fancy one automatically using input tags etc. FancyBrowserWidget or something like that. That way, we'll have an analogy from BrowserView to BrowserWidget.

They'd both be cleaned up - but BrowserWidget would turn out to be pretty trivial, ala BrowserView. I think SimpleBrowserWidget is a reasonable name in that the widgets themselves are primitive, single element HTML widgets (buttons, text fields, checboxes, etc.)

In contrast, some widgets are composites of many HTML elements - not simple...some might even say fancy :-)

Well, if we do this before X3.0 (I think we should)

Agreed.

Zope3 already has so much backward compatability stuff that has been around way longer than originally intended.

Agreed.

I think we can rip it all out for X3.0...

Assuming no one objects, I'm for it. There's lots of talk of current Zope3 apps, in production, etc. though. When this proposal is in better shape, we can give the list a chance to object.

... --jim, 2004/03/29 14:26 EST reply

Cleanup of Misc Schema and Widget Issues

...

Proposed Solution

...

ZCML for Multi-view Lookup

We need ZCML support for declaring views for multiple interfaces. This will be used by the vocabulary widgets for looking up views for a) view types (IInputWidget? or IDisplayWidget?) and b) vocabulary types.

This is done. You have to use zope:view, rather than browser:view, because the later is really "page" oriented.

...

Changes to Fields

These changes are to simplify some of the existing field types:

Vocabulary Fields

The VocabularyXyx? fields should be renamed to their Xyx component:

  • VocabularySet? -> Set - VocabularyUniqueList? -> UniqueList? - VocabularyList? -> List

Set, UniqueList?, and List should have a value_type field that specifies the type of values contained by Set, UniqueList?, and List objects.

IMO, a given value_type should be allowed to be one of:

  • A field - An interface, or - A class (e.g. int)

If an interface or class is given, a corresponding field should be crreated and stored. IOW, the value of value_type is a field, but an interfface or class are allowed as conveniences.

For example:
          list = VocabularyList(name="postal_code")

        where 'postal_code' is the name of a vocabulary declared elsewhere,
        would become::

          list = List(value_type=PostalCode)

        where PostalCode is the type of object contained in the list. (XXX
        I have value_type=PostalCode() in my notes &emdash; but this doesn't
        seem right.)</blockquote>

It is right. In this example, PostalCode? is a field type. We need a field, thus we need to instantiate the field type.

...

Enumerated Fields

Remove all EnumeratedXXX? fields and replace their use with a Choice field.

It should be noted in the proposal that additional constraints (e.g. Int) aren't needed because we listing the set of possible values.

Misc Field Changes

The __doc__ argument should be removed for fields.

But fields will still have __doc__ attrs, computed from the title and description.

The title and description arguments should be moved to the front of the argument list for fields.

Why?

Didn't we discuss and decide to require all keyword arguments? I have to admin that I am divided on this. For example, I think that being able to say:

  authors = List(IAuthor, title="Document authors")

rather than:

  authors = List(value_type=IAuthor, title="Document authors")

has some appeal.

Schema Arithmetic

It should be possible to generate a new schema by applying a transformation to an existing schema.

One use case for this is the Add form. In some cases, the Add form needs to update a field that is "read only" in the target object schema. Since the Add form deals with initializing an object, it should have write access to that field. (The Edit form, however, should not.)

The solution to this would be to create a schema for use by the Add form that is a transformation of the object schema &emdash; in the example, the only change would be that the field in question be designated as writable instead of read only.

Note that, initially, we'll provide a simple function that converts read-only fields to non-read-only fields.

Widget Related Changes

Widget Layout Implementation

We want to move the ZPT implementation out of any pages that use it (e.g. edit.pt, display.pt, etc.) into its own page for use as a macro. The will allow for easier maintenance of the code as well as let that portion of the pages be skinned.

Please be more specific. I assume that you're refering to the bit that displays the "table" (div/whatever) of widgets and their labels and hints.



( 97 subscribers )