[Zope3-checkins]
SVN: Zope3/branches/f12gsprint-widget/src/zope/subview/
Add some notes and changes on the basis of recent discussions.
Gary Poster
gary at zope.com
Wed Oct 19 16:05:15 EDT 2005
Log message for revision 39517:
Add some notes and changes on the basis of recent discussions.
Changed:
U Zope3/branches/f12gsprint-widget/src/zope/subview/README.txt
U Zope3/branches/f12gsprint-widget/src/zope/subview/_subview.py
U Zope3/branches/f12gsprint-widget/src/zope/subview/interfaces.py
-=-
Modified: Zope3/branches/f12gsprint-widget/src/zope/subview/README.txt
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/subview/README.txt 2005-10-19 20:01:48 UTC (rev 39516)
+++ Zope3/branches/f12gsprint-widget/src/zope/subview/README.txt 2005-10-19 20:05:14 UTC (rev 39517)
@@ -1,5 +1,17 @@
XXX The intent is to make this a test, as usual.
+- settle on one of "initialize" or "update"
+- use "setBrowserState" or "getBrowserState" or similar rather than ZODB
+- clarify "a standard pattern" section
+- discuss why concurrency can happen even without persistence
+- think about possiblility of separating out some functionality
+- think about sufficiency of AJAX and drag and drop baseline
+- "browserState" not "state"
+- "identifier" not "prefix"
+- "__parent__" not "parent"
+- "__name__" not "name"
+- clarify prefix ("identifier" story)
+
===============
Subview package
===============
@@ -60,7 +72,9 @@
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,
+current best practices. Some of these best practices drive the approaches
+described in the other two sections below "Rich-Client Browser Support" and
+"Subview Persistence", main package goals,
but two others bear highlighting.
First, subviews often require an explicit delineation of
@@ -170,7 +184,7 @@
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
+changes, in a similar story involving concurrency? This is a thornier problem
for the applications that must address it. If your application must consider
this problem, here are two possible solutions.
@@ -189,8 +203,7 @@
---------------------------
As discussed in the introduction, this package contemplates support for two
-rich-client approaches in subviews: AJAX and drag and
-drop.
+rich-client approaches in subviews: AJAX and drag and drop.
AJAX
----
@@ -255,80 +268,11 @@
-------------------
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.
+use cases.
-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.
+XXX update/getBrowserState
-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.
+database exceptions when a transaction commits, etc.).
Modified: Zope3/branches/f12gsprint-widget/src/zope/subview/_subview.py
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/subview/_subview.py 2005-10-19 20:01:48 UTC (rev 39516)
+++ Zope3/branches/f12gsprint-widget/src/zope/subview/_subview.py 2005-10-19 20:05:14 UTC (rev 39517)
@@ -42,7 +42,7 @@
# subclases should render now
_initialized=False
- def update(self, parent=None, name=None):
+ def update(self, parent=None, name=None, state=None):
self._initialized = True
if parent is not None:
self.parent = parent
@@ -60,57 +60,6 @@
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):
@@ -140,7 +89,3 @@
class IntermediateSubviewBase(IntermediateSubviewMixin, SubviewBase):
interface.implements(interfaces.IIntermediateSubview)
-
-class PersistentIntermediateSubviewBase(
- IntermediateSubviewMixin, PersistentSubviewBase):
- interface.implements(interfaces.IPersistentIntermediateSubview)
Modified: Zope3/branches/f12gsprint-widget/src/zope/subview/interfaces.py
===================================================================
--- Zope3/branches/f12gsprint-widget/src/zope/subview/interfaces.py 2005-10-19 20:01:48 UTC (rev 39516)
+++ Zope3/branches/f12gsprint-widget/src/zope/subview/interfaces.py 2005-10-19 20:05:14 UTC (rev 39517)
@@ -15,7 +15,7 @@
$Id$
"""
-
+from zope.app.publisher.interfaces.browser import IBrowserView
from zope.component.interfaces import IView
from zope import interface, schema
from zope.subview.i18n import _
@@ -26,37 +26,62 @@
# I'd like to try to import ISubPage and declare ISubview to extend ISubPage
# if the formlib package exists.
-class IPrefixedView(IView):
+class IIdentifiedViewComponent(interface.Interface):
- 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
+ identifier = schema.DottedName(
+ # XXX!!! W3C validator reportedly doesn't like
+ # our dotted ids. Neither Fred nor I can find why in the XHTML or
+ # HTML or XML specs, so need to replicate in the validator and see if
+ # it has more information. Many other separators should work, but
+ # dots feel natural from a Python perspective.
+ title=_('Identifier'), description=_(
+ """An identifier. Should be unique among all identifiers used for a
+ view in a given request. Should be used as a prefix for view ids.
+ The view component must not use the prefix directly for any names or
+ ids in the rendered output: 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'.""",
+ for all identifiers within the view component--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
+class IStagedView(IView):
+ """A view that must have `update` called before `__call__`. Any contained
+ (or managed) subviews must also have `update` called before any view or
+ subview is rendered with __call__"""
+
+ def __call__():
+ """render; should (but not required) raise informative error if called
before update
(see update)"""
- def update(parent=None, name=None):
- """Initialize subview: perform all state calculation here, not in render).
+ def update():
+ """Initialize view; perform all state calculation here, not in render.
+
+ In this method, all state must be calculated from the current
+ interaction (e.g., the browser request); all contained or managed
+ IStagedViews must have update called; any additional stateful API
+ for contained or managed subviews must be handled; and persistent
+ objects should be modified, if the view is going to do it. Do
+ *not* store state about persistent objects: render should actually use
+ the persistent objects for the data, in case other components modify
+ the object between the update and render stages.
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.
+ raise an error if used before calling update. View may rely on
+ this order but is not required to explicitly enforce this.
Implementations may enforce it as a developer aid.
+ """
+class IBrowserSubview(IBrowserView, IStagedView, IIdentifiedViewComponent):
+ """A view that does not render a full page, but part of a page."""
+
+
+ def update(parent=None, name=None, state=None):
+ """All of IStagedView.update, plus optionally set prefix and state.
+
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
@@ -67,21 +92,18 @@
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).
+
+ XXX state
"""
- parent = interface.Attribute(
+ __parent__ = interface.Attribute(
"""The ISubviewCollection that contains this view. Required.""")
- name = schema.DottedName(
+ __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
@@ -94,36 +116,43 @@
"""),
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.
+ identifier = schema.DottedName(
+ title=_('Identifier'), description=_(
+ """See IIdentifiedViewComponent.prefix. Must be calculated.
+ Identifier 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
+ identifier 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)
+ package of the code that is guaranteeing the unique ids, plus the
+ unique id. For instance, if a zc.portlet package used the intid
+ utility to provide unique names, and the intid of the view's underlying
+ portlet object were 13576, the prefix might be
+ 'zope.subview.zc.portlet.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.zc.portlet.13576.title.
+
+ Note also that a package may want to offer multiple namespaces, so
+ zc.portlet might have zc.portlet.config and zc.portlet.view.""",
+ 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.)"""
+ def getState():
+ """XXX
+
+ None==no state."""
-class ISubviewCollection(IPrefixedView):
+ def hasBrowserState():
+ """XXX Needed to know when to initialize subviews.
+ """
+
+class ISubviewCollection(IIdentifiedViewComponent):
"""Collection of subviews, geared towards iterating over subviews and
finding specific subviews by name.
@@ -146,37 +175,26 @@
"""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,
+ identifier = schema.DottedName(
+ title=_('Identifier'), description=_(
+ """See IIdentifiedViewComponent.identifier. 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
+ """A subview that may contain nested subviews. Note that initialize is
+ readonly, like ISubview and IIdentifiedViewComponent, but different from
ISubviewCollection"""
+
+ def getBrowserState():
+ """Returns persistent object.
+
+ Should include state of contained subviews.
+ """
-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).
- """
+ def update(parent=None, name=None, browserstate=None):
+ """browserstate, if given, should contain state of contained subviews
+ """
More information about the Zope3-Checkins
mailing list