[Zope3-checkins] SVN: Zope3/branches/f12gsprint-widget/src/zope/ I need to get back to some other things, and don't want to leave this work

Gary Poster gary at zope.com
Mon Oct 3 13:40:36 EDT 2005


Log message for revision 38721:
  I need to get back to some other things, and don't want to leave this work
  without being checked in at least.  widget tests don't pass (but this is a
  branch that I don't think anyone is following).  Subview is the
  current place where most of the work is being concentrated; will return to
  widget later.  Hope to return to this late this week or beginning of next.
  
  

Changed:
  A   Zope3/branches/f12gsprint-widget/src/zope/subview/
  A   Zope3/branches/f12gsprint-widget/src/zope/subview/README.txt
  A   Zope3/branches/f12gsprint-widget/src/zope/subview/__init__.py
  A   Zope3/branches/f12gsprint-widget/src/zope/subview/_subview.py
  A   Zope3/branches/f12gsprint-widget/src/zope/subview/draganddrop.txt
  A   Zope3/branches/f12gsprint-widget/src/zope/subview/i18n.py
  A   Zope3/branches/f12gsprint-widget/src/zope/subview/interfaces.py
  U   Zope3/branches/f12gsprint-widget/src/zope/widget/interfaces.py
  U   Zope3/branches/f12gsprint-widget/src/zope/widget/widget.py
  U   Zope3/branches/f12gsprint-widget/src/zope/widget/widget.txt

-=-
Added: Zope3/branches/f12gsprint-widget/src/zope/subview/README.txt
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/subview/README.txt	2005-10-03 12:37:06 UTC (rev 38720)
+++ Zope3/branches/f12gsprint-widget/src/zope/subview/README.txt	2005-10-03 17:40:35 UTC (rev 38721)
@@ -0,0 +1,334 @@
+XXX The intent is to make this a test, as usual.
+
+===============
+Subview package
+===============
+
+The subview package provides a core set of interfaces for views that operate
+within other views, and some small base implementations.
+
+Views within other views include form widgets, JSR-168-type portlets, page
+composition portlets a la CPSskins, etc.
+
+This package is currently pertinent to browser views.  It may be expanded to
+other types of subviews if we ever gain experience with them.  It will need
+to be refactored at that time, if so.
+
+The goals of the core interface include the following.
+
+- Establish a standard pattern for subviews for easier interoperability.
+  Specifically, this package
+
+  * separates calculating subview state from rendering the subview; and
+  
+  * defines the composition and use of prefixes.
+
+- Enable interoperable rich client-side approaches among subviews.  In
+  particular, the package contemplates two common use cases:
+  
+  * AJAX, so that client code from a rendered subview may directly address
+    the Python subview on the server for a given AJAX call; and
+
+  * drag and drop, so that unrelated subviews can allow items to be dragged
+    and dropped between them, and enabled via site configuration.
+
+- Enable subview persistence, so that subviews may be stashed away
+  temporarily and then redrawn.  For instance, 
+  
+  * forms may need to defer to another form, perhaps adding a desired value to
+    a source used on the original form, before returning;
+
+  * wizards may simply persist views in a session as they gather information;
+
+  * form file widgets on persisted views may simply persist references to
+    gathered files as they validate and communicate with the user;
+
+  * one subview of many may be expanded, hiding all others, across
+    transactions; and then changed to a normal size again, revealing original
+    subviews;
+
+  * views may simply wish to persist, like portlets; and
+  
+  * AJAX calls may benefit from interacting with persistent views.
+
+We will look at each of the three main features in turn: a standard pattern,
+rich-client support, and subview persistence.
+
+------------------
+A Standard Pattern
+------------------
+
+Based on lessons learned from the Zope 3 form code and from an in-house Zope
+Corporation JSR-168-like portlet system, the subview interfaces encapsulate
+current best practices.  Some of these drive the other two main package goals,
+but two others bear highlighting.
+
+First, subviews often require an explicit delineation of
+initialization--calculating the state for the subview--and rendering. 
+Following the pattern of the zope.formlib.interfaces.ISubPage interface, the
+subview interface separates this into update and render.  All subviews should
+be updated before any are rendered.  All analysis of the request and
+setting of subview state should happen in update, while all rendering (for the
+response) should happen in render.  This is essential to supporting subviews
+that may affect other subviews: otherwise update bugs, in which the underlying
+data changed as expected but it was not drawn to screen because it was out of
+order, are frequent.
+
+For our examples, note that we are making no assumptions about the rendering
+output other than it being non-None.
+
+    >>> container = SubviewContainer()
+    >>> s = Subview()
+    >>> container._add('test', s) # !!! _add is not in API
+    >>> container.update() # this should also initialize the subview
+    >>> s.render() is not None # should output the subview rendering
+    True
+
+    >>> container = SubviewContainer()
+    >>> nested = NestedSubview()
+    >>> container._add('inter', nested) # !!! _add is not in API
+    >>> s = Subview()
+    >>> nested._add('test', s) # !!! _add is not in API
+    >>> container.update() # should cascade initialize down
+    >>> nested.render() is not None
+    True
+    >>> s.render() is not None
+    True
+
+Second, subviews have a prefix which, along with a final dot, is the namespace
+with which the subview should prefix all names and identifiers.  The prefix
+may change between the update and render calls, which highlights another
+important aspect of parsing the request only in initialize:
+stored values should not rely on the current prefix.
+
+name is the name for this view in the subview container.  parent is the
+immediate subview container of the subview.  They are both only supposed to be
+accurate after update is called.  The prefix of a subview is calculated by
+combining the prefix of the parent with a dot and the name of the subview.
+The prefix itself, as a name without a following dotted element, is reserved
+for use in the immediate subview container.  The prefix of a subview container
+(that is not itself a subview) may be set directly.
+
+    >>> nested.parent is container
+    True
+    >>> s.parent is nested
+    True
+    >>> nested.name
+    'inter'
+    >>> nested.prefix
+    'inter'
+    >>> s.name
+    'test'
+    >>> s.prefix
+    'inter.test'
+    >>> container.prefix = 'cont'
+    >>> nested.prefix
+    'cont.inter'
+    >>> s.prefix
+    'cont.inter.test'
+    >>> nested.name = 'nest'
+    >>> nested.prefix
+    'cont.nest'
+    >>> s.prefix
+    'cont.nest.test'
+    >>> nested.render() is not None
+    True
+    >>> s.render() is not None
+    True
+
+To emphasize, the prefix should be used to calculate state during update,
+and to render during render; do not assume that the prefix will be the same
+across the calls.  To reiterate, the prefix should be used as a namespace--a
+prefix--for identifiers within the subview, but the prefix itself, without a
+following '.', is reserved for use by the container, for a purpose described
+below.
+
+The fact that the prefix is calculated from the parent and the name, rather
+than set in toto, is another part of this best practice, different from
+previous interfaces.  It reduces the chance for bugs when moving subviews
+among parents.
+
+    >>> s.prefix
+    'cont.nest.test'
+    >>> s.parent = container
+    >>> s.parent is container
+    True
+    >>> s.prefix
+    'cont.test'
+
+It's also worth noting that concurrency issues suggest that subview containers
+should be very wary of reassigning names, and particularly of reusing names
+for different subviews.  For instance, imagine a subview container with two
+subviews.  The programmer decides to name them with their order, '0' and '1'
+hypothetically.  A first user looks at the page and begins to work with the
+second subview.  Meanwhile, a second user deletes the first subview ('0').  If
+the names are simply based on order, then now what was subview '1' is subview
+'0'--if the first user tries to interact with the system, the rug will have
+been pulled out, because the subview appears to no longer exist.  Further
+imagine that the second user added a completely new subview, now named '1'. 
+If the first user submits now, then information intended for one subview will
+be sent to another.
+
+This hints at another problem: what should we do when the parent of the subview
+changes, in a similar story involving concurreny?  This is a thornier problem
+for the applications that must address it.  If your application must consider
+this problem, here are two possible solutions.
+
+- Use a calculated prefix for the object that is not based on hierarchy, as
+  described in ISubview.prefix.
+
+- Don't do it.  Make a shallow static subview hierarchy and arrange subviews
+  using a different mechanism (a smarter main view, for instance).
+
+A separation between update and render and a prefix model are two important
+best practices used in the subview package.  Other best practices are
+discussed in the remaining two sections.
+
+---------------------------
+Rich-Client Browser Support
+---------------------------
+
+As discussed in the introduction, this package contemplates support for two
+rich-client approaches in subviews: AJAX and drag and
+drop.
+
+AJAX
+----
+
+The primary AJAX use case is that a subview should be able to communicate
+directly with itself.  This requires that there be a standard, agreed-upon way to
+address a subview via a URL.  This package offers a traverser that uses a
+traversal namespace of ++subview++.  It can be traversed with a subview's 
+prefix, and will offer up registered names of the subview for following
+traversal.  For instance, if a view is served at 
+http://localhost/foo/index.html, and it has a nested subview with a prefix of
+bar.baz.bing, and it has a method named getWeather that it wants to address,
+then this might work:
+
+    def getWeather(self, location)...
+
+    >>> zcml("<configure><page name='getWeather' ...></configure>")
+    >>> browser.open(
+    ... 'http://localhost/foo/index.html/++subview++bar.baz.bing/getWeather?location=Fredericksburg')
+    >>> print browser.contents
+    'The weather is lovely and cool right now.'
+
+This approach might also allow direct submission to a subview, if desired,
+which could return an html snippet to replace the subview.  This would remove
+some of the necessity for our reliance on the super-form approach,
+which was developed to maintain state in the browser for multiple sub-forms
+when only one form was submitted.
+
+    XXX example
+
+Subview containers are required to make a div around the subview output with an
+id of the subview prefix and a class of 'zope.subview'.  This div can be used
+to replace contents of the subview on the basis of the form submission, if
+desired.  The div also has another use: an identifier for drag and drop,
+discussed next.
+
+Drag and Drop
+-------------
+
+Generic drag and drop between subviews can enable a number of very exciting
+scenarios.  For instance, imagine a subview that provides a list of a user's
+most-used coworkers.  Selecting the user in a form might involve dragging the
+coworker from one subview into a widget, that is itself a subview.  Sending the
+user a message might involve dragging a document in one subview to a user in
+another.  A complicated assembly process might be aided by a subview that
+maintained a clipboard, into which components could be dragged from multiple
+parts of the site while dragging, and out of which components could be dragged
+into assembly.
+
+If you find these stories exciting and compelling, this package establishes a
+tested and easy pattern that makes them possible.  If you don't find them
+compelling, you'll be happy to know that opting out is trivial.  The only
+shared requirement for all is that subview containers place a div around each
+subview's output with an id of the subview's prefix, and a class of 
+'zope.subview'.  If you are not interested in drag and drop, you can skip to
+the next section now ("Subview Persistence").  That's it.
+
+If you want to use drag and drop, see draganddrop.txt for more information.
+
+-------------------
+Subview Persistence
+-------------------
+
+As indicated in the introduction to this file, subview persistence has many
+use cases.  The solution taken by the ISubview interface is simple, at least
+conceptually: make it implement standard ZODB persistence.  If the object is
+not attached to a persistent object, it will be thrown away in normal garbage
+collection.  If code wishes to persist the subview, simply attach it to a
+persistent data structure already part of the ZODB, such as a principal
+annotation.
+
+Unfortunately, the simple solution is not quite so simple in implementation. 
+If a subview is persisted, it does have some unusual requirements.  The
+majority of these are handled by the default base class.
+
+First, requests and non-IPersistent parents must not be persisted. The parent
+may not be persisted because it may be a transient view, not designed for
+persistence; the request is also not designed for persistence, and the next
+time the view is used it will probably need to be associated with the current
+request, not the original one.  This constraint is met in the default base
+implementation by always getting the request from the interaction, instead of
+making it an actual attribute; and by storing non-persistent parents as
+request annotations.
+
+    >>> from zope.publisher.interfaces import IRequest
+    >>> IRequest.providedBy(Subview().request)
+    True
+    >>> from zope.persistent.interfaces import IPersistent
+    >>> IPersistent.providedBy(s)
+    True
+    >>> IPersistent.providedBy(nested)
+    True
+    >>> IPersistent.providedBy(container)
+    False
+    >>> nested.parent is container
+    True
+    >>> import cPickle
+    >>> getattr(cPickle.loads(cPickle.dumps(nested)), 'parent', None) is None
+    True
+    >>> s.parent = nested
+    >>> cPickle.loads(cPickle.dumps(s)).parent is nested
+    True
+    # XXX maybe do this with a real app, in a functional test wrapper...
+
+Second, when persisted subviews are used again, they must have the correct
+parent and request.  The default implementation gets the request from the
+thread-local security interaction, so only re-updating the subview with the
+current parent should be necessary.  The update method should be careful to
+accomodate new system state, rather than make assumptions.  Note that a subview
+should always have 'update' called whenever it is used with a new request,
+before 'render' is called.  Note that the default implementation also defers
+its context to the parent view, so that may not need to be reset.
+
+    # XXX end transaction, start a new one with a new request, and call update
+    # and render on 's'
+
+Last, if the subview is shared among various users, then essentially spurious
+but very annoying conflict errors may occur when parents (and requests, if the
+default implementation is not used) are tacked on to the subview.  Each
+attribute assignment of 'parent' will tag the view as needing to be persisted,
+and thus cause the ZODB to raise a ConflictError if multiple users set the
+attribute--even though the attribute is not persisted!  Persisted subviews
+sometimes store per-principal customization information in principal
+annotations; this approach might also be used to store the parent, but then
+again the value should not actually be persisted.  The parent might also be
+stored in a request annotation: this might be the easiest approach, since the
+reference will naturally have the correct lifespan: this is taken by the
+default implementation.  A third approach might be to write a __getstate__ that
+does not include parent or request along with conflict resolution
+code that ignores 'imaginary' changes like the ones to parent and request
+(as a reminder to the writer of this file :-), that's
+"def _p_resolveConflict(self, oldState, savedState, newState):...").
+
+    # make another DB copy of s, make a 'conflict' by setting parent, and
+    # show that transaction has no error. XXX
+
+It is essential for interoperability that subviews do not opt-out of the
+requirements for this part of the interface.  Otherwise, their subviews will
+break intermediate persistent subviews in unpleasant ways (e.g., causing
+database exceptions when a transaction commits, etc.).  Hopefully the shared
+base class will alleviate the annoyance for individual subview writers.


Property changes on: Zope3/branches/f12gsprint-widget/src/zope/subview/README.txt
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: Zope3/branches/f12gsprint-widget/src/zope/subview/__init__.py
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/subview/__init__.py	2005-10-03 12:37:06 UTC (rev 38720)
+++ Zope3/branches/f12gsprint-widget/src/zope/subview/__init__.py	2005-10-03 17:40:35 UTC (rev 38721)
@@ -0,0 +1,3 @@
+from _subview import (
+    SubviewBase, SubviewContainerBase, IntermediateSubviewMixin,
+    IntermediateSubview)


Property changes on: Zope3/branches/f12gsprint-widget/src/zope/subview/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: Zope3/branches/f12gsprint-widget/src/zope/subview/_subview.py
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/subview/_subview.py	2005-10-03 12:37:06 UTC (rev 38720)
+++ Zope3/branches/f12gsprint-widget/src/zope/subview/_subview.py	2005-10-03 17:40:35 UTC (rev 38721)
@@ -0,0 +1,146 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Subview helper implementations
+
+$Id$
+"""
+
+import persistent
+import persistent.dict
+import persistent.interfaces
+from zope import interface
+from zope.subview import interfaces
+import zope.security.management
+import zope.security.proxy
+from zope.publisher.interfaces import IRequest
+
+class SubviewBase(persistent.Persistent):
+    interface.implements(interfaces.ISubview)
+
+    context = request = name = parent = None
+
+    @property
+    def prefix(self):
+        if self.name is None or self.parent is None:
+            raise ValueError('name and parent must be set')
+        return '%s.%s' % (self.parent.prefix, self.name)
+
+    def render(self):
+        if not self._initialized:
+            raise RuntimeError('Initialize subview first')
+         # subclases should render now
+
+    _initialized=False
+    def update(self, parent=None, name=None):
+        self._initialized = True
+        if parent is not None:
+            self.parent = parent
+        if name is not None:
+            self.name = name
+        if self.parent is None or self.name is None:
+            raise RuntimeError(
+                "parent and name must be set or passed in to update")
+        # subclasses should calculate state now!
+
+def getRequest():
+    i = zope.security.management.getInteraction() # raises NoInteraction
+    for p in i.participations:
+        if IRequest.providedBy(p):
+            return p
+    raise RuntimeError('No IRequest in interaction')
+
+ANNOTATIONS_KEY = 'zope.subview'
+
+PersistentSubviewBase(persistent.Persistent, SubviewBase):
+
+    interface.implements(interfaces.IPersistentSubview)
+
+    @property
+    def context(self):
+        # avoid persisting possible security wrappers; avoid possible conflict
+        # errors
+        return self.parent.context
+
+    @property
+    def request(self):
+        # avoid persisting the request; avoid possible conflict errors
+        return getRequest()
+
+    _parent = None
+    @apply
+    def parent():
+        # we store persistent parents on this object, only changing when
+        # necessary so as not to invite conflict errors unnecessarily.  we
+        # store non-persistent parents in the request, so that the parent
+        # is not persisted and so that we again do not invite conflict errors.
+        # we use id(self) because a request is not persistent: we are only
+        # stashing the id while the object is in memory, so we should be fine.
+        def get(self):
+            if self._parent is not None:
+                return self._parent
+            else:
+                parents = self.request.annotations.get(ANNOTATIONS_KEY)
+                if parents is not None:
+                    return parents.get(id(self))
+            return None
+        def set(self, value):
+            # parent views should typically be unproxied, but just to be sure:
+            value = zope.security.proxy.removeSecurityProxy(value)
+            # if it is a persistent object...
+            if persistent.interfaces.IPersistent.providedBy(value):
+                # ...and we haven't stored it before (avoid conflict errors)
+                # then 
+                if value is not self._parent:
+                    self._parent = value
+            else:
+                if self._parent is not None: # avoid conflict errors
+                    self._parent = None
+                parents = self.request.annotations.setdefault(
+                    ANNOTATIONS_KEY, [])
+                parents[id(self)] = value
+        return property(get, set)
+
+class IntermediateSubviewMixin(object):
+
+    def getSubview(self, name):
+        raise NotImplementedError
+
+    def iterSubviews(self):
+        raise NotImplementedError
+
+    def update(parent=None, name=None):
+        super(IntermediateSubviewMixin, self).update(parent, name)
+        for key, val in self.iterSubviews:
+            val.update(self, key)
+
+    def getSubviewByPrefix(self, prefix):
+        # somewhat naive but probably sufficient implementation
+        if prefix == self.prefix:
+            return self
+        if (prefix.startswith(self.prefix) or
+            prefix.startswith('zope.subview.')):
+            for key, val in self.iterSubviews:
+                if interfaces.ISubviewContainer.providedBy(val):
+                    res = val.getSubviewByPrefix(prefix)
+                    if res is not None:
+                        return res
+                elif val.prefix == prefix:
+                    return res
+
+class IntermediateSubviewBase(IntermediateSubviewMixin, SubviewBase):
+    interface.implements(interfaces.IIntermediateSubview)
+
+class PersistentIntermediateSubviewBase(
+    IntermediateSubviewMixin, PersistentSubviewBase):
+    interface.implements(interfaces.IPersistentIntermediateSubview)


Property changes on: Zope3/branches/f12gsprint-widget/src/zope/subview/_subview.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: Zope3/branches/f12gsprint-widget/src/zope/subview/draganddrop.txt
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/subview/draganddrop.txt	2005-10-03 12:37:06 UTC (rev 38720)
+++ Zope3/branches/f12gsprint-widget/src/zope/subview/draganddrop.txt	2005-10-03 17:40:35 UTC (rev 38721)
@@ -0,0 +1,31 @@
+=============
+Drag and Drop
+=============
+
+The pattern of the drag and drop approach is this. 
+
+* First, on the browser, dragging and dropping an item needs to generate the id
+  of the dragged item, the id of the containing subview, the id of the drag
+  target, and the id of the containing subview.  This means that draggable
+  items and targets need to be identified and given tokens that can be
+  resolved on the server back to the dragged item. The draggable items should
+  be identified with class="zope.subview.dnd_source"; the targets should be
+  identified with class="zope.subview.dnd_target".  They should have ids that
+  are tokens that can be converted to the appropriate objects on the server,
+  as described below.
+
+* These four tokens--source object, target object, source subview, and target
+  subview--should be sent back to the server to determine what
+  the effect of the action is, if any.  The call can be with AJAX or with a
+  full page submission.  
+
+* The server should use ISubviewCollection.getSubviewByPrefix to get the subviews by their prefix (the
+  id on the page).  They can then try to adapt the subviews to IDragAndDropSource
+  and IDragAndDropTarge, respectively.  If adapters exist, then the object tokens
+  should be converted to objects. XXX otherwise no action
+
+* Get the multi adapter for (source object, target object, source subview,
+  target subview) to IDragAndDropHandler. XXX document return values; add
+  interfaces
+
+XXX


Property changes on: Zope3/branches/f12gsprint-widget/src/zope/subview/draganddrop.txt
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: Zope3/branches/f12gsprint-widget/src/zope/subview/i18n.py
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/subview/i18n.py	2005-10-03 12:37:06 UTC (rev 38720)
+++ Zope3/branches/f12gsprint-widget/src/zope/subview/i18n.py	2005-10-03 17:40:35 UTC (rev 38721)
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL).  A copy of the ZVSL 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
+#
+##############################################################################
+"""I18N support for subviews.
+
+This defines a `MessageFactory` for the I18N domain for the zope.subview
+package.  This is normally used with this import::
+
+  from i18n import MessageFactory as _
+
+The factory is then used normally.  Two examples::
+
+  text = _('some internationalized text')
+  text = _('helpful-descriptive-message-id', 'default text')
+"""
+__docformat__ = "reStructuredText"
+
+
+from zope import i18nmessageid
+
+MessageFactory = _ = i18nmessageid.MessageFactory("zope.subview")


Property changes on: Zope3/branches/f12gsprint-widget/src/zope/subview/i18n.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: Zope3/branches/f12gsprint-widget/src/zope/subview/interfaces.py
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/subview/interfaces.py	2005-10-03 12:37:06 UTC (rev 38720)
+++ Zope3/branches/f12gsprint-widget/src/zope/subview/interfaces.py	2005-10-03 17:40:35 UTC (rev 38721)
@@ -0,0 +1,182 @@
+##############################################################################
+#
+# Copyright (c) 2005 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.
+#
+##############################################################################
+"""Subview interfaces
+
+$Id$
+"""
+
+from zope.component.interfaces import IView
+from zope import interface, schema
+from zope.subview.i18n import _
+
+# XXX try to reconcile with zope.formlib.interfaces.ISubPage: I hope to
+# remove setPrefix from that interface, and ideally add parent and name,
+# to that interface.  That will take discussion.  If that happens, then 
+# I'd like to try to import ISubPage and declare ISubview to extend ISubPage
+# if the formlib package exists.
+
+class IPrefixedView(IView):
+
+    prefix = schema.DottedName(
+        title=_('Prefix'), description=_(
+        """A prefix for view ids, uniquely identifying the element among the
+        view and any of its contained subviews.  The view must not use the
+        prefix directly for any names or ids: it is reserved by containing/calling
+        views, if any.  The prefix should be used with a following dot ('.')
+        for all identifiers within the view--that is, if a prefix is 'form',
+        then names and ids within the associated view should all begin with
+        'form.', like 'form.title'.""",
+        readonly=True, required=True)
+
+class ISubview(IPrefixedView):
+    """A view that does not render a full page, but part of a page."""
+    
+    def render():
+        """render subview; should (but not required) raise error if called 
+        before update
+
+        (see update)"""
+
+    def update(parent=None, name=None):
+        """Initialize subview: perform all state calculation here, not in render).
+
+        Initialize must be called before any other method that mutates the
+        instance (besides __init__).  Non mutating methods and attributes may
+        raise an error if used before calling update.  Subview may rely on
+        this order but are not required to explicitly enforce this. 
+        Implementations may enforce it as a developer aid.
+
+        parent and name must be set before this method is called, or
+        they must be passed in (as the parent and name arguments,
+        respectively) and set initially by this method.  See the attributes for
+        more detail.  parent and name may be set independently of this method,
+        such as between update and render, if state calculation should
+        be performed in one context, and rendering in another.
+
+        The subview will try to get its state from the environment (e.g., the
+        context and request), using the prefix, if any. The state will be used
+        to try to reinitialize the subview's user-visible state.
+
+        In order to facilitate non-stateless interactions, update
+        must be able to be called more than once on the same subview instance.
+        Each call should recalculate the subview state on the basis of the new
+        arguments.
+        
+        If this subview is a subview container, it is responsible for calling 
+        update on all directly contained subviews when update is
+        called (*not* postponed to render).
+        """
+
+    parent = interface.Attribute(
+        """The ISubviewCollection that contains this view.  Required.""")
+
+    name = schema.DottedName(
+        title=_('Name'), description=_(
+        """The name by which this subview may be obtained from its __parent__
+        via getSubview.  Subviews may suggest values but must be amenable
+        to having the suggestions overridden.  Suggested values, if any, must
+        be stable across renderings and transactions.
+        
+        Concurrency issues suggest that you should often avoid reusing subview
+        names (that is, having names refer to one subview and then another)
+        within a given logical set of renderings of a subview.
+        """),
+        min_dots=0, max_dots=0, required=True)
+
+    prefix = schema.DottedName(
+        title=_('Prefix'), description=_(
+        """See IPrefixedView.prefix.  Must be calculated.  Prefix must be one
+        of two types.
+
+        One choice is a hierarchically derived combination of the parent's
+        prefix value joined by a dot ('.') to the name.  For instance, if the
+        parent's prefix were 'left_slot.form' and the current name were
+        'title', then the prefix would be 'left_slot.form.title'.
+
+        The other choice is a guaranteed unique and consistent name, as
+        determined by the subview author, following a prescribed formula: the
+        reserved initial prefix of 'zope.subview.', plus the path to the
+        package of the code that is providing the unique ids, plus the unique
+        id.  For instance, if someone used the intid utility to provide unique
+        names, and the intid of the current view were 13576, the prefix might
+        be 'zope.subview.zope.app.intid.13576'.  Note that this subview might
+        itself be a parent to another subview that got its prefix via the
+        hierarchical combination; if its name were 'title' then its prefix
+        would be zope.subview.zope.app.intid.13576.title""", readonly=True,
+        required=True)
+
+class IPersistentSubview(ISubview):
+    """Must also implement IPersistent.  However, requests, contexts, and
+    non-IPersistent parents *must not be persisted*.  (Requests and security
+    wrappers around contexts are not persistable, and non-persistent parents
+    are not designed to be persistent and so may themselves contain references
+    to requests, contexts, or other problematic values.)"""
+
+class ISubviewCollection(IPrefixedView):
+    """Collection of subviews, geared towards iterating over subviews and
+    finding specific subviews by name.
+
+    To enable replacement of individual subviews on a page through technologies
+    such as AJAX, and to enable drag and drop approaches within subviews, 
+    subview collections must enclose the rendered output of each
+    directly contained subview within a div block with an id of the subview's
+    prefix and a class of "zope.subview".
+    """
+
+    def iterSubviews():
+        """return an iterator of pairs of (name, subview) for all directly
+        contained subviews."""
+
+    def getSubview(name):
+        """given a name, return the directly contained subview, or None if not
+        found."""
+
+    def getSubviewByPrefix(prefix):
+        """return the (nested) subview matching the given prefix, or None.
+        """
+
+    prefix = schema.DottedName(
+        title=_('Prefix'), description=_(
+        """See IPrefixedView.prefix.  If this is not also an ISubview,
+        then this value is writable.""", readonly=False, required=True)
+        
+    def update():
+        """Initialize all contained subviews: perform all state calculation.
+        """
+
+class IPersistentSubviewCollection(ISubviewCollection):
+    """Implements IPersistent.    However, requests and contexts *must not be
+    persisted*.  (Requests and security wrappers around contexts are not
+    persistable.)
+    
+    Any contained subview must implement IPersistentSubview (including
+    IPersistentIntermediateSubview, which extends IPersistentSubview).
+    """
+
+class IIntermediateSubview(ISubview, ISubviewCollection):
+    """A subview that may contain nested subviews.  Note that prefix is
+    readonly, like ISubview and IPrefixedView, but different from
+    ISubviewCollection"""
+
+class IPersistentIntermediateSubview(
+    IPersistentSubview, IPersistentSubviewCollection, IIntermediateSubview):
+    """Implements IPersistent.  However, requests, contexts, and
+    non-IPersistent parents *must not be persisted*.  (Requests and security
+    wrappers around contexts are not persistable, and non-persistent parents
+    are not designed to be persistent and so may themselves contain references
+    to requests, contexts, or other problematic values.)
+    
+    Any contained subview must implement IPersistentSubview (including
+    IPersistentIntermediateSubview, which extends IPersistentSubview).
+    """


Property changes on: Zope3/branches/f12gsprint-widget/src/zope/subview/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: Zope3/branches/f12gsprint-widget/src/zope/widget/interfaces.py
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/widget/interfaces.py	2005-10-03 12:37:06 UTC (rev 38720)
+++ Zope3/branches/f12gsprint-widget/src/zope/widget/interfaces.py	2005-10-03 17:40:35 UTC (rev 38721)
@@ -18,7 +18,7 @@
 __docformat__ = 'restructuredtext'
 
 from zope.schema.interfaces import ValidationError
-from zope.component.interfaces import IView
+import zope.subview.interfaces
 from zope.interface import Attribute, Interface, implements
 from zope.schema import Bool
 
@@ -28,21 +28,14 @@
 
 class ConversionError(ValueError):
     """ Value could not be converted to correct type """
-    
-class InvalidStateError(RuntimeError):
-    """ This widget's state has been invalidated by a call to setValue()"""
 
-class IWidget(IView):
+class IWidget(zope.subview.interfaces.ISubview):
     """Generically describes the behavior of a widget.
-
-    This must be presentation independent.
+    
+    Note that all state calculation (e.g., gathering value from request) must
+    happen within initialize.
     """
 
-    name = Attribute(
-        """The unique widget name
-
-        This must be unique within a set of widgets.""")
-
     label = Attribute(
         """The widget label.
 
@@ -64,12 +57,6 @@
         field can be set to False for widgets that always provide input (e.g.
         a checkbox) to avoid unnecessary 'required' UI notations.
         """)
-                
-    prefix = Attribute("""Element names should begin with the given `prefix`.
-        Prefix name may be None, or a string.  Any other value raises 
-        ValueError.  When prefixes are concatenated with the widget name, a dot 
-        is used as a delimiter; a trailing dot is neither required nor suggested
-        in the prefix itself. """)
         
     error = Attribute(""" An exception created by initialize or setValue.  If 
         this value is not None, it is raised when getValue is called.
@@ -81,60 +68,45 @@
         setValue.  Minimally, a message object is expected to be adaptable to
         a view named snippet.
         """)
-    
-    def __call__():
-        """ render widget """
         
-    def initialize(prefix=None, value=marker, state=None):
-        """ Initialize widget and set its value.
-
-        Initialize must be called before any other method besides __init__.
-
-        If prefix is passed in, the prefix attribute of the widget is set.
-        Prefix must be None or be a string.  See prefix attribute for more 
-        detail.
-        
-        If neither value nor state is included, the widget will try
-        to set its value from the request.  
-
-        If value is passed in, the widget will use that value.  If state is 
-        passed in, the widget will use the state object to set its value and
-        do any other initialization desired. The state object must be obtained 
-        from the getState method of a widget of the same class.  
-        
-        Only one of value or state may be passed, passing both raises
-        TypeError.        
-        
-        If the widget's value is not valid, its error attribute will contain 
-        either a ConversionError or zope.schema.interfaces.ValidationError.  If 
-        a widget wishes to add an object to message, that may be done here.
-        """        
-        
-    def getState():
-        """ If the widget has been viewed previously, returns a non-None, 
-        picklable object representing the widget's state, unless setValue has 
-        been called, in which case it raises InvalidStateError.  Otherwise, 
-        returns None.
-
-        A state object can later be passed in to initialize to restore the
-        state of the widget.
+    def hasInput():
+        """True if subview has a user-visible (client-side) state from a
+        previous rendering (either persistently or from the most recent
+        initialization).
         """
         
-    def hasState():
-        """ Return True if the widget has a state from a previous request.
+    def update(parent=None, name=None, value=marker):
+        """ Initialize widget, calculating all state; and set its value.
         
-        Should be equivalent to self.getState() is not None.
-        """
+        See ISubview.initialize for essential basic behavior.
 
+        If the value is not provided, it will try to calculate the value from
+        the environment (e.g., the a browser request).
+
+        After a call to initialize, if the widget's value is not valid, its
+        error attribute will contain either a ConversionError or
+        zope.schema.interfaces.ValidationError.  If a widget wishes to add an
+        object to message, that may be done here."""
+
     def getValue():
         """ Return the current value as set by initialize or setValue.
 
-        If error is not None, raise it."""
+        Value is *not* validated.  Value should not be used to assign to 
+        a field unless the widget's error attribute is None.
         
+        May raise ConversionError (i.e., widget has state but does not
+        know how to convert it to a validatable value) but not
+        ValidationError.  For instance, if the widget's value is currently a
+        required field's missing value, getValue must return the missing value.
+        
+        If setValue was called and did not raise an exception, the value
+        must be returnable by this method."""
+        
     def setValue(value):
         """ Sets the current value of the widget.  Resets the error and 
         message attributes to only the message and error pertaining 
-        to the new value, if any.
+        to the new value, if any.  Must minimally accept the widget's field's
+        missing value and valid values.
         """
     
 class IInputWidget(IWidget):
@@ -143,3 +115,57 @@
 class IDisplayWidget(IWidget):
     """a widget used for display"""
 
+class IBrowserInputWidget(IWidget):
+    """a browser widget used for input"""
+
+class IBrowserDisplayWidget(IWidget):
+    """a browser widget used for display"""
+
+class ICoreWidget(interface.Interface):
+    """A private interface to support lean widgets usable in both the old and
+    new widget frameworks.
+    
+    The interface intends to allow for full flexibility by the widget author,
+    while enforcing the patterns needed by the subview and widget interfaces.
+    The only particularly unusual approach is using a state object to pass
+    calculated values.  This state should not depend on the widget's name or
+    prefix for use.  It encourages all state calculation to be done once,
+    as is necessary for the initialize method.
+    """
+
+    def renderInvalidValue(context, request, name, value):
+        """render a widget for an invalid value; typically renders a widget
+        with an empty value.
+        
+        This is only called if calculateState returned None, or if
+        a value has been explicitly set on the widget; and the value is
+        invalid, according to the widget's field (context)."""
+
+    def renderValidValue(context, request, name, value):
+        """render a widget for the given valid value
+        
+        This is only called if calculateState returned None, or if
+        a value has been explicitly set on the widget; and the value is
+        valid, according to the widget's field (context)."""
+
+    def renderState(context, request, name, state):
+        """render a widget for the given valid (non-None) state.
+        
+        This is called if calculateState returned a non-None value and the
+        widget's value was not set explicitly.
+        """
+
+    def calculateState(context, request, name):
+        """return the widget's state object if the (logical) widget was
+        rendered previously, or None.
+        
+        State must be persistable."""
+
+    def calculateValue(state):
+        """given a state provided by calculateState, return a value, or raise
+        ConversionError"""
+
+    def needsRedraw(context, request, state):
+        """return a boolean on whether the given state (provided by
+        calculateState) suggests that the widget should be redrawn (for
+        instance, because the widget processed an internal submit)."""

Modified: Zope3/branches/f12gsprint-widget/src/zope/widget/widget.py
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/widget/widget.py	2005-10-03 12:37:06 UTC (rev 38720)
+++ Zope3/branches/f12gsprint-widget/src/zope/widget/widget.py	2005-10-03 17:40:35 UTC (rev 38721)
@@ -14,74 +14,60 @@
 
 $Id: form.py 38007 2005-08-19 17:50:28Z poster $
 """
+import persistent
+import persistent.dict
 from zope import interface
+import zope.subview
 from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
-from zope.schema.interfaces import ValidationError
+from zope.schema.interfaces import ValidationError, RequiredMissing
 from xml.sax.saxutils import quoteattr
 
 from zope.widget import interfaces
 
-class BaseInputWidget(object):
-    """base simple widget"""
+class Widget(zope.subview.SubviewBase):
+    """widget, designed to be used by composition or subclass to get core
+    behavior"""
     
-    interface.implements(interfaces.IInputWidget)
+    interface.implements(interfaces.IWidget)
     __doc__ = interfaces.IInputWidget.__doc__
     
-    _name = None
-    name = property(lambda self: self._name)
-
-    _prefix = None
-    def prefix(self, value):
-        self._prefix = value
-        if value is None:
-            self._name = self.context.__name__
-        else:
-            self._name = '.'.join((value, self.context.__name__))
-    prefix = property(lambda self: self._prefix, prefix)
-    
     _error = None
     error = property(lambda self: self._error)
 
     _message = None
     message = property(lambda self: self._message)
     
-    def __init__(self, context, request):
+    def __init__(self, context, request, core_widget=None):
         self.context = context
         self.request = request
         self.label = context.title
         self.hint = context.description
         self.required = context.required
-        
+        assert core_widget is None or interfaces.ICoreWidget.providedBy(
+            core_widget)
+        self._core_widget = core_widget
+
     _valueForced = False
-    _initialized = False    
-    _state = None
-    def initialize(self, prefix=None, value=interfaces.marker, state=None):
-        self._initialized = True
-        self.prefix = prefix
-        if state is None:
-            state = self._calculateStateFromRequest()
-        else:
-            if value is not interfaces.marker:
-                raise TypeError('May pass only one of value and state')
-        self._state = state
+    def update(self, parent=None, name=None, value=interfaces.marker):
+        super(Widget, self).initialize(parent, name)
+        self._state = self._calculateState()
         if value is interfaces.marker:
-            if self._state is None:
-                value = self.context.default
+            if not self.hasInput():
+                self.setValue(self.context.default) # effectively a force
             else:
-                value = self._calculateValueFromState()
-            self.setValue(value)
-            self._valueForced = False
+                try:
+                    value = self._calculateValue()
+                except interfaces.ConversionError, e:
+                    self._error = e
+                    self._message = None
+                    self._value = self.context.missing_value
+                else:
+                    self.setValue(value)
+                self._valueForced = False # not a force
         else:
-            self.setValue(value)
-            
-    def getState(self):
-        if not self._initialized:
-            raise RuntimeError('Initialize widget first')
-        if self._state is not None and self._valueForced:
-            raise interfaces.InvalidStateError()
-        return self._state
+            self.setValue(value) # this is a force.
     
-    def hasState(self):
+    def hasInput(self):
         if not self._initialized:
             raise RuntimeError('Initialize widget first')
         return self._state is not None
@@ -90,7 +76,8 @@
     def getValue(self):
         if not self._initialized:
             raise RuntimeError('Initialize widget first')
-        if self.error is not None:
+        if self.error is not None and isinstance(
+            self.error, interfaces.ConversionError):
             raise self.error
         return self._value
     
@@ -107,55 +94,127 @@
         self._value = value
         self._valueForced = True
 
-    def __call__(self):
+    # the following methods may typically either be overridden or be used
+    # as is, delegating to an ICoreWidget implementation.
+
+    def needsRedraw(self):
+        return self._valueForced or (
+            self._state is not None and self._core_widget.needsRedraw(
+                self.context, self.request, self._state))
+    needsRedraw = property(needsRedraw)
+
+    def render(self):
+        """render the widget.
+        
+        if self._valueForced:
+            if self.error is not None:
+                draw the widget with no value filled in
+            else:
+                draw the widget on the basis of the current value
+        else:
+            draw the widget on the basis of the _state (which should
+            always be non-None if _valueForced is False)"""
+        if self._valueForced:
+            if self.error is not None:
+                return self._core_widget.renderInvalidValue(
+                    self.context, self.request, self.prefix, self._value)
+            else:
+                return self._core_widget.renderValidValue(
+                    self.context, self.request, self.prefix, self._value)
+        else:
+            return self._core_widget.renderState(
+                    self.context, self.request, self.prefix, self._state)
+        
+    def _calculateState(self):
+        """return the widget's state object if the (logical) widget was
+        rendered previously, or None."""
+        return self._core_widget.calculateState(
+            self.context, self.request, self.name)
+
+    def _calculateValue(self):
+        """return the current value only on the basis of the _state attribute.
+
+        Do not validate.  If cannot generate a value from the current _state,
+        raise zope.widget.interfaces.ConversionError.
+
+        _state will never be None when this method is called."""
+        return self._core_widget.calculateValue(self._state)
+
+class CoreWidget(persistent.Persistent):
+
+    interface.implements(interfaces.ICoreWidget)
+
+    def renderInvalidValue(self, context, request, name, value):
+        """render a widget for an invalid value"""
         raise NotImplementedError
+
+    def renderValidValue(self, context, request, name, value):
+        """render a widget for the given valid value"""
+        raise NotImplementedError
+
+    def renderState(self, context, request, name, state):
+        """render a widget for the given state"""
+        raise NotImplementedError
+
+    def calculateState(self, context, request, name):
+        """return the widget's state object if the (logical) widget was
+        rendered previously, or None.
         
-    def _calculateStateFromRequest(self):
-        """return the widget's state object if it was rendered previously, or
-        None"""
-        return self.request.form.get(self.name)
+        This simple implementation is good for widgets that have only one
+        input field."""
+        return request.form.get(name+".value")
 
-    def _calculateValueFromState(self):
-        """return the current value on the basis of the _state attribute"""
+    def calculateValue(self, state):
+        """given a state, return a value, or raise ConversionError"""
         raise NotImplementedError
-    
 
-class AdvancedBaseInputWidget(BaseInputWidget):
+    def needsRedraw(self, context, request, state):
+        return False
+
+class AdvancedCoreWidget(object):
     """base advanced widget"""
 
-    def _calculateStateFromRequest(self):
-        res = {}
-        name = self.name
-        len_name = len(name)
-        prename = name + "."
-        for n, v in self.request.form.items():
-            if n == name or n.startswith(prename):
-                res[n[len_name:]] = v
+    def calculateState(self, context, request, name):
+        """return the widget's state object if the (logical) widget was
+        rendered previously, or None.
+        
+        This advanced implementation is good for widgets that are comprised
+        of multiple input fields.  It means that the state object (if not None)
+        is a dictionary."""
+        res = persistent.dict.PersistentDict()
+        prefix = self.prefix + "."
+        slice = len(prefix)
+        for n, v in request.form.items():
+            if n.startswith(prefix):
+                res[n[slice:]] = v
         return res or None
 
-class TextLineWidget(BaseInputWidget):
-    
-    #template = namedtemplate.NamedTemplate('default')   
-    
-    t = ("""<input type="text" value=%(value)s name=%(name)s """
-         """id=%(name)s size="20" />""")
-    
+class CoreTextLineWidget(SimpleCoreWidget):
 
-    def __call__(self):
-        if self._valueForced:
-            value = self.getValue()
-        else:
-            value = self._state
-        return self.t % {'value':quoteattr(value or ''),
-                         'name':quoteattr(self.name)}
+    def __init__(self, size='20', extra=''):
+        self.size = size
+        self.extra = extra
 
-    #("""<input type="text" value=%(value)s name=%(name)s """
-    #            """id=%(name)s size="20" />""")
-    
-    def _calculateValueFromState(self):
+    def renderInvalidValue(self, context, request, name, value):
+        return self.renderValidValue(name, '')
+
+    def renderValidValue(self, context, request, name, value):
+        return (
+            """<input type="text" value=%(value)s name=%(name)s """
+            """id=%(name)s size="%(size)s" %(extra)s />""" % 
+            {'value': value, 'name': name+".value", 'size': self.size, 
+             'extra': self.extra})
+    renderState = renderValidValue
+
+    def calculateValue(self, state):
         try:
-            value = unicode(self._state)
-        except ValueError, v: # XXX
-            e = interfaces.ConversionError(v)
-        else:
-            return value
+            return unicode(state)
+        except ValueError, v:
+            raise interfaces.ConversionError(v)
+
+textLineWidget = CoreTextLineWidget()
+
+def TextLineWidget(context, request):
+    w = InputWidget(context, request, textLineWidget)
+    interface.directlyProvides(w, interfaces.IBrowserInputWidget)
+    return w

Modified: Zope3/branches/f12gsprint-widget/src/zope/widget/widget.txt
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/widget/widget.txt	2005-10-03 12:37:06 UTC (rev 38720)
+++ Zope3/branches/f12gsprint-widget/src/zope/widget/widget.txt	2005-10-03 17:40:35 UTC (rev 38721)
@@ -1,18 +1,51 @@
-=====
+==========
 Widget API
-=====
+==========
 
+Widgets are views on schema fields--that is, components that adapt a schema
+field and a request, and provide methods that are intended to generate user
+interface code.  The user interface is either designed to gather input from
+a user or to display values to a user.  Widgets must be assembled and displayed
+by a coordinating component such as a zope.formlib form.
 
+The widget package has three clients: 
+
+- developers who want to use widgets to gather information from end users;
+- developers who want to create widgets; and
+- the end users themselves.
+
+This document addresses the first two clients.  We will first examine the full
+widget API, trying to provide a guide for developers who want to use widgets.
+We will then 
+for 
+The widget API is a simple approach based on the concept of calculating and
+processing user-visible state objects.  The widget module provides some simple
+abstract classes that should let most widget writers concentrate on only two
+tasks: rendering the widget, a
+
     >>> from zope.widget import widget, interfaces
-
     >>> from zope.publisher.browser import TestRequest
     >>> from zope.schema import TextLine
     >>> request = TestRequest()
+    >>> context = object()
+
+Field is required but has no default
+
     >>> field = TextLine(__name__='test_name', title=u'Test Title', 
     ...                  min_length=2, description=u'Test Description')
-    >>> context = object()
     >>> field = field.bind(context)
+
+Field2 is required and has a default value.
+
+    >>> field2 = TextLine(__name__='test_name', title=u'Test Title', 
+    ...                  min_length=2, description=u'Test Description',
+    ...                  default=u'test_default')
+    >>> field2 = field2.bind(context)    
+    
+Create the widget.    
+    
     >>> w = widget.TextLineWidget(field, request)
+
     
 After creating a widget, the next step that must be taken is to initialize it.
 
@@ -42,7 +75,17 @@
     Traceback (most recent call last):
     ...
     RequiredMissing
+    
+A widget with a required field with a default value, will obtain that default.
 
+    >>> request = TestRequest()
+    >>> w = widget.TextLineWidget(field2, request)
+    >>> w.initialize()
+    >>> w.getValue()
+    u'test_default'
+
+
+
 If the previous request rendered the field, the state will be gathered from it
 during initialize; if value is not passed explicitly to initialize, it will
 be set from the gathered state.
@@ -118,6 +161,24 @@
     False
     >>> w.getState() # None    
     
+    
+It is possible to explicitly set the value to None rather than accept the 
+default value.
+
+    >>> request = TestRequest()
+    >>> w = widget.TextLineWidget(field2, request)
+    >>> w.initialize(value=None)
+    >>> w.getValue() # None
+
+    >>> request = TestRequest()
+    >>> w = widget.TextLineWidget(field2, request)
+    >>> w.initialize()  # will use field default value
+    >>> w.getValue()
+    'test_default'
+    >>> w.setValue(None)
+    >>> w.getValue() # None
+    
+    
 State objects are black boxes.  The only contract they have is that they are
 pickleable.  Do not rely on their values, except for their intended use.
     



More information about the Zope3-Checkins mailing list