[Checkins] SVN: zope3org/trunk/src/zorg/multiform/ added container TODO: fix newDisplayMode

Bernd Dorn bernd.dorn at fhv.at
Tue Apr 11 17:16:51 EDT 2006


Log message for revision 66865:
  added container TODO: fix newDisplayMode

Changed:
  A   zope3org/trunk/src/zorg/multiform/container/
  A   zope3org/trunk/src/zorg/multiform/container/__init__.py
  A   zope3org/trunk/src/zorg/multiform/container/configure.zcml
  A   zope3org/trunk/src/zorg/multiform/container/container.txt
  A   zope3org/trunk/src/zorg/multiform/container/grid.pt
  A   zope3org/trunk/src/zorg/multiform/container/griditem.pt
  A   zope3org/trunk/src/zorg/multiform/container/interfaces.py
  A   zope3org/trunk/src/zorg/multiform/container/location.py
  A   zope3org/trunk/src/zorg/multiform/container/tests.py
  A   zope3org/trunk/src/zorg/multiform/container/views.py
  U   zope3org/trunk/src/zorg/multiform/multiform.py

-=-
Added: zope3org/trunk/src/zorg/multiform/container/__init__.py
===================================================================
--- zope3org/trunk/src/zorg/multiform/container/__init__.py	2006-04-11 20:37:34 UTC (rev 66864)
+++ zope3org/trunk/src/zorg/multiform/container/__init__.py	2006-04-11 21:16:50 UTC (rev 66865)
@@ -0,0 +1,3 @@
+# container
+
+

Added: zope3org/trunk/src/zorg/multiform/container/configure.zcml
===================================================================
--- zope3org/trunk/src/zorg/multiform/container/configure.zcml	2006-04-11 20:37:34 UTC (rev 66864)
+++ zope3org/trunk/src/zorg/multiform/container/configure.zcml	2006-04-11 21:16:50 UTC (rev 66865)
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<configure xmlns:zope="http://namespaces.zope.org/zope"
+           xmlns="http://namespaces.zope.org/browser"
+           xmlns:i18n="http://namespaces.zope.org/i18n"
+           i18n_domain="zope">
+
+ <page for="zope.app.container.interfaces.IContainer"
+       name="grid.html"
+       permission="zope.View"
+       class=".views.ContainerGridForm"/>
+
+  <zope:adapter for="zope.app.location.interfaces.ILocation"
+                factory=".location.MovableLocation"
+                provides=".interfaces.IMovableLocation"/>
+ 
+</configure>
+

Added: zope3org/trunk/src/zorg/multiform/container/container.txt
===================================================================
--- zope3org/trunk/src/zorg/multiform/container/container.txt	2006-04-11 20:37:34 UTC (rev 66864)
+++ zope3org/trunk/src/zorg/multiform/container/container.txt	2006-04-11 21:16:50 UTC (rev 66865)
@@ -0,0 +1,45 @@
+=====================
+ Container Grid Form
+=====================
+
+A container contents view implementation using multiform. This view
+provides cut, copy, paste, edit and save actions. Additionally the
+__name__ attribute of the items can be transparently changed.
+
+In order to test the behaviour of the ``ContainerGridForm`` we
+subclass it and override the template method.
+
+    >>> from zope.app.container.sample import SampleContainer
+    >>> from zope.app.annotation.interfaces import IAttributeAnnotatable
+    >>> from zope.app.dublincore.interfaces import IZopeDublinCore
+    >>> from zope.app.dublincore.interfaces import IDCDescriptiveProperties
+    >>> from multiform.container.views import ContainerGridForm
+    >>> from zope import interface
+    >>> from zope.publisher.browser import TestRequest
+    >>> class ContainerTest(ContainerGridForm):
+    ...     actions = ContainerGridForm.actions.copy()
+    ...     def template(self):
+    ...         res = u''
+    ...         forms = list(self.getForms())
+    ...         forms.sort(lambda x,y: cmp(x.prefix,y.prefix))
+    ...         for form in forms:
+    ...             res += '<div>%s</div>\n' % form.render()
+    ...         return res
+
+    >>> c = SampleContainer()
+    >>> for i in range(2):
+    ...     o = SampleContainer()
+    ...     interface.directlyProvides(o,IAttributeAnnotatable)
+    ...     c[u'name of %s' % i]=o
+
+    >>> request = TestRequest()
+    >>> view = ContainerTest(c,request)
+    >>> print view()
+    <div...<td>name of 0</td>...<td>name of 1</td>...             
+
+
+
+
+
+    
+

Added: zope3org/trunk/src/zorg/multiform/container/grid.pt
===================================================================
--- zope3org/trunk/src/zorg/multiform/container/grid.pt	2006-04-11 20:37:34 UTC (rev 66864)
+++ zope3org/trunk/src/zorg/multiform/container/grid.pt	2006-04-11 21:16:50 UTC (rev 66865)
@@ -0,0 +1,192 @@
+<html metal:extend-macro="context/@@standard_macros/view"
+      metal:define-macro="main">
+<head>
+</head>
+
+<body>
+<div metal:fill-slot="body">
+
+<div metal:define-macro="form">
+
+<form action="." metal:define-macro="master"
+      tal:attributes="action request/URL" method="post"
+      class="edit-form" enctype="multipart/form-data"
+      id="zc.page.browser_form">
+
+<script type="text/javascript"><!--
+
+function toggleFormFieldHelp(ob,state) {
+  // ob is the label element
+  var field = ob.form[ob.htmlFor];
+  if (field) {
+    var viz = state && 'hidden' || 'visible';
+    if (field.length == null) {
+      field.style.visibility = viz;
+    }
+    else {
+      for (var i = 0; i < field.length; ++i) {
+        var e = field.item(i);
+        e.style.visibility = viz;
+      }
+    }
+    var help = document.getElementById("field-help-for-" + ob.htmlFor);
+    if (help) {
+      help.style.visibility = state && 'visible' || 'hidden';
+    }
+  }
+}
+
+//-->
+</script>
+
+<div id="viewspace" metal:define-slot="viewspace">
+
+  <h1  i18n:translate=""
+       tal:condition="view/label"
+       tal:content="view/label"
+       metal:define-slot="heading"
+       >Do something</h1>
+
+  <metal:block define-macro="header">
+
+    <div class="form-status"
+       tal:define="status view/status"
+       tal:condition="status">
+
+      <div class="summary"
+           i18n:translate=""
+           tal:content="view/status">
+        Form status summary
+      </div>
+
+      <ul class="errors" tal:condition="view/errors">
+         <li tal:repeat="error view/error_views">
+            <span tal:replace="structure error">Error Type</span>
+         </li>
+      </ul>
+    </div>
+
+  </metal:block>
+
+  <div metal:define-slot="extra_info" tal:replace="nothing">
+  </div>
+
+  <div metal:define-slot="main_form">
+    <table class="form-fields" metal:define-macro="formtable">
+      <tr class="row" metal:define-slot="extra_top" tal:replace="nothing">
+          <td class="label">Extra top</td>
+          <td class="field"><input type="text" /></td>
+      </tr>
+      <tbody metal:define-slot="formbody" tal:omit-tag="">
+        <tal:block omit-tag="" repeat="widget view/widgets">
+          <tr metal:define-macro="formrow">
+            <td class="label" tal:define="hint widget/hint"
+              metal:define-macro="labelcell">
+              <label tal:condition="python:hint"
+                     tal:attributes="for widget/name"
+                     onmousedown="toggleFormFieldHelp(this,1)"
+                     onmouseup="toggleFormFieldHelp(this,0)"
+                     onmouseout="toggleFormFieldHelp(this,0)"
+                     style="cursor: help">
+                <span class="required" tal:condition="widget/required"
+                >*</span><span i18n:translate=""
+                               tal:content="widget/label">label</span>
+              </label>
+              <label tal:condition="python:not hint"
+                     tal:attributes="for widget/name">
+                <span class="required" tal:condition="widget/required"
+                >*</span><span i18n:translate=""
+                               tal:content="widget/label">label</span>
+              </label>
+            </td>
+            <td class="field" tal:define="hint widget/hint"
+                metal:define-macro="widgetcell">
+              <div class="form-fields-help"
+                   i18n:translate=""
+                   tal:content="hint"
+                   tal:condition="hint"
+                   tal:attributes="id string:field-help-for-${widget/name}"
+                   onclick="this.style.visibility='hidden';"
+                   style="visibility: hidden; position: absolute;"
+                   >Title of this content object.</div>
+              <div class="widget" tal:content="structure widget">
+              <input type="text" /></div>
+              <div class="error"
+                   tal:condition="widget/error"
+                   >
+                <!-- TODO Put this back, the Zope3 way.
+                <img src="alert.gif" alt="Error"
+                tal:replace="structure context/alert.gif" />
+                -->
+                <span tal:replace="structure widget/error">error</span>
+              </div>
+            </td>
+          </tr>
+        </tal:block>
+      </tbody>
+      <tr class="row" metal:define-slot="extra_bottom" tal:replace="nothing">
+        <td class="label">Extra bottom</td>
+        <td class="label"><input type="text" /></td>
+      </tr>
+    </table>
+  </div>
+  <!-- start: subforms -->
+  <table id="sortable" class="listing"
+         summary="Content listing">
+   <thead>
+    <tr>
+     <th tal:repeat="field view/itemFormFactory/form_fields"
+         tal:content="field/field/title" i18n:translate="">
+     </th>
+    </tr>
+   </thead>
+   <tbody>
+    <tr tal:repeat="subForm view/getForms"
+        tal:attributes="class python:repeat['subForm'].even and 'even' or 'odd'"
+        tal:content="structure subForm"/>
+   </tbody>
+  </table>
+  
+  <!-- end: subforms -->
+
+  <!-- start: parentActions -->
+
+  <div id="parentActionsView"
+       metal:define-macro="form_parent_actions">
+   <span class="actionButtons"
+        tal:condition="view/availableSubActions"
+        metal:define-slot="bottom_buttons">
+    <input tal:repeat="action view/availableSubActions"
+           tal:replace="structure action/render"
+           />
+  </span>
+</div>
+
+  <!-- end: parentActions -->
+
+  
+  
+  <metal:block define-slot="above_buttons" />
+</div>
+<div id="actionsView"
+     metal:define-macro="form_actions">
+  <span class="actionButtons"
+        tal:condition="view/availableActions"
+        metal:define-slot="bottom_buttons">
+    <input tal:repeat="action view/actions"
+           tal:replace="structure action/render"
+           />
+  </span>
+</div>
+
+</form>
+<script type="text/javascript" metal:define-slot="trackChanges">
+  zc_trackChanges(document.getElementById('zc.page.browser_form'));
+</script>
+
+<script type="text/javascript"
+    tal:define="extra_script view/extra_script | nothing"
+    tal:condition="extra_script"
+    tal:content="structure extra_script" />
+
+</div></div></body></html>

Added: zope3org/trunk/src/zorg/multiform/container/griditem.pt
===================================================================
--- zope3org/trunk/src/zorg/multiform/container/griditem.pt	2006-04-11 20:37:34 UTC (rev 66864)
+++ zope3org/trunk/src/zorg/multiform/container/griditem.pt	2006-04-11 21:16:50 UTC (rev 66865)
@@ -0,0 +1,2 @@
+<td tal:repeat="widget view/widgets"
+     tal:content="structure widget"/>

Added: zope3org/trunk/src/zorg/multiform/container/interfaces.py
===================================================================
--- zope3org/trunk/src/zorg/multiform/container/interfaces.py	2006-04-11 20:37:34 UTC (rev 66864)
+++ zope3org/trunk/src/zorg/multiform/container/interfaces.py	2006-04-11 21:16:50 UTC (rev 66865)
@@ -0,0 +1,15 @@
+from zope.app.location.interfaces import ILocation
+from zope import schema
+from zope.interface import Interface, Attribute
+
+class IMovableLocation(ILocation):
+
+    """a located object that can change its __name__ attribute by
+    itself"""
+
+    __name__ = schema.TextLine(
+        title=u"The name within the parent",
+        description=u"Traverse the parent with this name to get the object.",
+        required=False,
+        default=None)
+

Added: zope3org/trunk/src/zorg/multiform/container/location.py
===================================================================
--- zope3org/trunk/src/zorg/multiform/container/location.py	2006-04-11 20:37:34 UTC (rev 66864)
+++ zope3org/trunk/src/zorg/multiform/container/location.py	2006-04-11 21:16:50 UTC (rev 66865)
@@ -0,0 +1,35 @@
+from interfaces import IMovableLocation
+from zope.interface import implements
+from zope.app.copypastemove.interfaces import IContainerItemRenamer
+from multiform.interfaces import IFormLocation
+from zope.security.proxy import removeSecurityProxy
+
+class MovableLocation(object):
+
+    implements(IMovableLocation)
+
+    def __init__(self,context):
+        self.context = context
+        self.__parent__ = self.context.__parent__
+
+    def _setName(self,v):
+        old = self.context.__name__
+        if v != old:
+            renamer = IContainerItemRenamer(self.__parent__)
+            renamer.renameItem(old, v)
+            if IFormLocation.providedBy(self.context):
+                # if we are in a multiform, we have to add the request
+                # data to our prefix
+                form = removeSecurityProxy(self.context.__form__)
+                oldPrefix = form.prefix
+                newPrefix = form.prefix[:-len(old)] + v
+                for oldKey in list(form.request.form.keys()):
+                    if oldKey.startswith(oldPrefix):
+                        newKey = newPrefix + oldKey[len(oldPrefix):]
+                        form.request.form[newKey]=form.request.form[oldKey]
+                
+    def _getName(self):
+        return self.context.__name__
+
+    __name__ = property(_getName,_setName)
+    

Added: zope3org/trunk/src/zorg/multiform/container/tests.py
===================================================================
--- zope3org/trunk/src/zorg/multiform/container/tests.py	2006-04-11 20:37:34 UTC (rev 66864)
+++ zope3org/trunk/src/zorg/multiform/container/tests.py	2006-04-11 21:16:50 UTC (rev 66865)
@@ -0,0 +1,113 @@
+import doctest
+import unittest
+from zope.testing.doctestunit import DocFileSuite, DocTestSuite
+from zope.app.testing import setup
+from zope import component
+import zope.schema.interfaces
+import zope.app.form.browser
+import zope.publisher.interfaces.browser
+import zope.app.form.interfaces
+from zope.formlib import form
+from zope.app.dublincore.annotatableadapter import ZDCAnnotatableAdapter
+from zope.app.dublincore.interfaces import IWriteZopeDublinCore
+from zope.app.annotation.interfaces import IAnnotatable
+import location
+from interfaces import IMovableLocation
+from multiform import gridform,multiform,selection
+from multiform.interfaces import IFormLocation,ISelection
+
+from zope.app.location.interfaces import ILocation
+
+def setUp(test):
+    setup.placefulSetUp()
+
+    component.provideAdapter(
+        zope.app.form.browser.TextWidget,
+        [zope.schema.interfaces.ITextLine,
+         zope.publisher.interfaces.browser.IBrowserRequest,
+         ],
+        zope.app.form.interfaces.IInputWidget,
+        )
+    component.provideAdapter(
+        zope.app.form.browser.UnicodeDisplayWidget,
+        [zope.schema.interfaces.ITextLine,
+         zope.publisher.interfaces.browser.IBrowserRequest,
+         ],
+        zope.app.form.interfaces.IDisplayWidget,
+        )
+    component.provideAdapter(
+        zope.app.form.browser.boolwidgets.BooleanDisplayWidget,
+        [zope.schema.interfaces.IBool,
+         zope.publisher.interfaces.browser.IBrowserRequest,
+         ],
+        zope.app.form.interfaces.IDisplayWidget,
+        )
+    component.provideAdapter(
+        zope.app.form.browser.CheckBoxWidget,
+        [zope.schema.interfaces.IBool,
+         zope.publisher.interfaces.browser.IBrowserRequest,
+         ],
+        zope.app.form.interfaces.IInputWidget,
+        )
+    component.provideAdapter(
+        zope.app.form.browser.UnicodeDisplayWidget,
+        [zope.schema.interfaces.IInt,
+         zope.publisher.interfaces.browser.IBrowserRequest,
+         ],
+        zope.app.form.interfaces.IDisplayWidget,
+        )
+    component.provideAdapter(
+        zope.app.form.browser.IntWidget,
+        [zope.schema.interfaces.IInt,
+         zope.publisher.interfaces.browser.IBrowserRequest,
+         ],
+        zope.app.form.interfaces.IInputWidget,
+        )
+    component.provideAdapter(
+        selection.FormLocationProxy,
+        [zope.app.location.interfaces.ILocation,
+         zope.formlib.interfaces.IForm
+         ],
+        IFormLocation
+        )
+    component.provideAdapter(
+        selection.FormLocationSelection,
+        [IFormLocation],
+        ISelection
+        )
+    component.provideAdapter(
+        location.MovableLocation,
+        [ILocation],
+        IMovableLocation
+        )
+    component.provideAdapter(
+        ZDCAnnotatableAdapter,
+        [IAnnotatable],
+        IWriteZopeDublinCore
+        )
+
+    
+    component.provideAdapter(gridform.default_grid_template,
+                             name="default")
+    component.provideAdapter(gridform.default_griditem_template,
+                             name="default")
+    component.provideAdapter(form.render_submit_button, name='render')
+    component.provideAdapter(multiform.render_submit_button, name='render')
+    
+def tearDown(test):
+    setup.placefulTearDown()
+
+
+def test_suite():
+    
+    return unittest.TestSuite(
+        (
+        DocFileSuite('container.txt',
+                     setUp=setUp, tearDown=tearDown,
+                     optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+                     ),
+        ))
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: zope3org/trunk/src/zorg/multiform/container/views.py
===================================================================
--- zope3org/trunk/src/zorg/multiform/container/views.py	2006-04-11 20:37:34 UTC (rev 66864)
+++ zope3org/trunk/src/zorg/multiform/container/views.py	2006-04-11 21:16:50 UTC (rev 66865)
@@ -0,0 +1,297 @@
+from zope.formlib.i18n import _
+from multiform import multiform
+from zope.formlib import form
+from multiform.interfaces import ISelection
+from zope.app.dublincore.interfaces import IWriteZopeDublinCore
+from zope.app.size.interfaces import ISized
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.app.location.interfaces import ILocation
+from interfaces import IMovableLocation
+from zope.event import notify
+from zope.app.event.objectevent import ObjectModifiedEvent
+from zope.interface.common import idatetime
+import datetime
+import pytz
+from zope.app import zapi
+from zope.app.copypastemove.interfaces import IPrincipalClipboard
+from zope.app.copypastemove.interfaces import IObjectCopier
+from zope.app.copypastemove.interfaces import IObjectMover
+from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
+from zope.app.container.interfaces import DuplicateIDError
+from zope.security.interfaces import Unauthorized
+from zope.app.traversing.interfaces import TraversalError
+
+
+def isSelected(form,action):
+    return ISelection(form.context).selected
+    
+def isSelectedInput(form,action):
+    print "isSelectedInput",form,form.inputMode,action.__name__,isSelected(form,action)
+    if not form.inputMode:
+        return False
+    return isSelected(form,action)
+
+
+def isSelectedOrDisplay(form,action):
+    print "isSelectedOrDisplay",form.inputMode,isSelected(form,action)
+    return not form.inputMode
+
+
+def pasteable(containerForm):
+    """Decide if there is anything to paste."""
+    target = containerForm.context
+    clipboard = getPrincipalClipboard(containerForm.request)
+    items = clipboard.getContents()
+    for item in items:
+        try:
+            obj = zapi.traverse(target, item['target'])
+        except TraversalError:
+            pass
+        else:
+            if item['action'] == 'cut':
+                mover = IObjectMover(obj)
+                moveableTo = safe_getattr(mover, 'moveableTo', None)
+                if moveableTo is None or not moveableTo(target):
+                    return False
+            elif item['action'] == 'copy':
+                copier = IObjectCopier(obj)
+                copyableTo = safe_getattr(copier, 'copyableTo', None)
+                if copyableTo is None or not copyableTo(target):
+                    return False
+            else:
+                raise
+
+    return True
+
+def safe_getattr(obj, attr, default):
+    """Attempts to read the attr, returning default if Unauthorized."""
+    try:
+        return getattr(obj, attr, default)
+    except Unauthorized:
+        return default
+
+def hasClipboardContents(form, action):
+    """ interogates the `PrinicipalAnnotation` to see if
+       clipboard contents exist """
+
+    if multiform.anySubFormInputMode(form,action):
+        return False
+
+    if not pasteable(form):
+        return False
+
+    # touch at least one item to in clipboard confirm contents
+    clipboard = getPrincipalClipboard(form.request)
+    items = clipboard.getContents()
+    for item in items:
+        try:
+            zapi.traverse(form.context, item['target'])
+        except TraversalError:
+            pass
+        else:
+            return True
+
+    return False
+
+
+
+class ContainerItemForm(multiform.ItemFormBase):
+
+    inputMode=False
+    forceInput=['selected']
+    template = ViewPageTemplateFile('griditem.pt')
+    form_fields = form.Fields(ISelection['selected'],
+                              IMovableLocation['__name__'],
+                              IWriteZopeDublinCore['title'],
+                        omit_readonly=False,render_context=True)
+
+    @multiform.parentAction('Edit',
+                            condition=multiform.allSubFormsDisplayMode)
+    def handle_edit_action(self, action, data):
+        #print "handle_edit_action",action,data,isSelected(self,action)
+        if isSelected(self,action):
+            self.newInputMode = True
+
+    @multiform.parentAction("Save", inputMode=True,
+                            condition=multiform.anySubFormInputMode)
+    def handle_save_action(self, action, data):
+        if form.applyChanges(self.context, self.form_fields,
+                             data, self.adapters):
+            notify(ObjectModifiedEvent(self.context))
+            formatter = self.request.locale.dates.getFormatter(
+                'dateTime', 'medium')
+            try:
+                time_zone = idatetime.ITZInfo(self.request)
+            except TypeError:
+                time_zone = pytz.UTC
+            m = {'date_time':formatter.format(datetime.datetime.now(time_zone))}
+            self.status = (_("Updated on ${date_time}", mapping=m),)
+        else:
+            self.status = (_('No changes'),)
+        self.newInputMode = False
+
+
+
+class ContainerGridForm(multiform.MultiFormBase):
+
+    itemFormFactory=ContainerItemForm
+
+    template = ViewPageTemplateFile('grid.pt')
+
+    @form.action('Cancel',condition=multiform.anySubFormInputMode)
+    def handle_cancel_action(self, action, data):
+        for form in self.subForms.values():
+            form.newInputMode = False
+
+    @form.action("Paste", condition=hasClipboardContents)
+    def handle_paste_action(self, action, data):
+        """Paste ojects in the user clipboard to the container"""
+        self.form_reset = True
+        target = self.context
+        clipboard = getPrincipalClipboard(self.request)
+        items = clipboard.getContents()
+        moved = False
+        not_pasteable_ids = []
+        for item in items:
+            duplicated_id = False
+            try:
+                obj = zapi.traverse(target, item['target'])
+            except TraversalError:
+                pass
+            else:
+                if item['action'] == 'cut':
+                    mover = IObjectMover(obj)
+                    try:
+                        mover.moveTo(target)
+                        moved = True
+                    except DuplicateIDError:
+                        duplicated_id = True
+                elif item['action'] == 'copy':
+                    copier = IObjectCopier(obj)
+                    try:
+                        copier.copyTo(target)
+                    except DuplicateIDError:
+                        duplicated_id = True
+                else:
+                    raise
+
+            if duplicated_id:
+                not_pasteable_ids.append(zapi.getName(obj))                
+
+        if moved:
+            # Clear the clipboard if we do a move, but not if we only do a copy
+            clipboard.clearContents()
+
+        if not_pasteable_ids != []:
+            # Show the ids of objects that can't be pasted because
+            # their ids are already taken.
+            # TODO Can't we add a 'copy_of' or something as a prefix
+            # instead of raising an exception ?
+            self.errors = (
+                _("The given name(s) %s is / are already being used" %(
+                str(not_pasteable_ids))),)
+
+    @form.action("Cut", condition=multiform.allSubFormsDisplayMode)
+    def handle_cut_action(self, action, data):
+        """move objects specified in a list of object ids"""
+
+        container_path = zapi.getPath(self.context)
+
+        # For each item, check that it can be moved; if so, save the
+        # path of the object for later moving when a destination has
+        # been selected; if not movable, provide an error message
+        # explaining that the object can't be moved.
+        items = []
+        for form in self.getForms():
+            ob = form.context
+            name = ob.__name__
+            selection = ISelection(ob)
+            if not selection.selected:
+                continue
+            selection.selected=False
+            mover = IObjectMover(ob)
+            if not mover.moveable():
+                m = {"name": name}
+                title = getDCTitle(ob)
+                if title:
+                    m["title"] = title
+                    self.errors = (_(
+                        "Object '${name}' (${title}) cannot be moved",
+                        mapping=m),)
+                else:
+                    self.errors = (_("Object '${name}' cannot be moved",
+                                     mapping=m),)
+                return
+            items.append(zapi.joinPath(container_path, name))
+        if len(items) == 0:
+            self.errors = (_("You didn't specify any ids to cut."),)
+        else:
+            # store the requested operation in the principal annotations:
+            clipboard = getPrincipalClipboard(self.request)
+            clipboard.clearContents()
+            clipboard.addItems('cut', items)
+
+    @form.action("Copy", condition=multiform.allSubFormsDisplayMode)
+    def handle_copy_action(self, action, data):
+        """Copy objects specified in a list of object ids"""
+
+        container_path = zapi.getPath(self.context)
+
+        # For each item, check that it can be copied; if so, save the
+        # path of the object for later copying when a destination has
+        # been selected; if not copyable, provide an error message
+        # explaining that the object can't be copied.
+
+        items = []
+        for form in self.getForms():
+            ob = form.context
+            name = ob.__name__
+            selection = ISelection(ob)
+            if not selection.selected:
+                continue
+            selection.selected=False
+            copier = IObjectCopier(ob)
+            if not copier.copyable():
+                m = {"name": name}
+                title = getDCTitle(ob)
+                if title:
+                    m["title"] = title
+                    self.errors = (_(
+                        "Object '${name}' (${title}) cannot be copied",
+                        mapping=m),)
+                else:
+                    self.errors = (_("Object '${name}' cannot be copied",
+                                     mapping=m),)
+                return
+            items.append(zapi.joinPath(container_path, name))
+        if len(items) == 0:
+            self.errors = (_("You didn't specify any ids to copy."),)
+        else:
+            # store the requested operation in the principal annotations:
+            clipboard = getPrincipalClipboard(self.request)
+            clipboard.clearContents()
+            clipboard.addItems('copy', items)
+
+    @form.action("Delete", condition=multiform.allSubFormsDisplayMode)
+    def handle_delete_action(self, action, data):
+        """Delete objects specified in a list of object ids"""
+        container = self.context
+        toDelete = []
+        for form in self.getForms():
+            if not ISelection(form.context).selected:
+                continue
+            toDelete.append(form.context.__name__)
+        if toDelete:
+            for name in toDelete:
+                del(container[name])
+            self.form_reset = True
+        else:
+            self.errors = (_("You didn't specify any ids to delete."),)            
+
+def getPrincipalClipboard(request):
+    """Return the clipboard based on the request."""
+    user = request.principal
+    annotationutil = zapi.getUtility(IPrincipalAnnotationUtility)
+    annotations = annotationutil.getAnnotations(user)
+    return IPrincipalClipboard(annotations)
+

Modified: zope3org/trunk/src/zorg/multiform/multiform.py
===================================================================
--- zope3org/trunk/src/zorg/multiform/multiform.py	2006-04-11 20:37:34 UTC (rev 66864)
+++ zope3org/trunk/src/zorg/multiform/multiform.py	2006-04-11 21:16:50 UTC (rev 66865)
@@ -107,7 +107,7 @@
     newInputMode = None
     form_fields=[]
     actions = []
-
+    
     def __init__(self,context,request,parentForm):
         # we have to copy the default fields, so that we can mutate
         # them in our instance
@@ -140,7 +140,6 @@
     implements(IMultiForm)
     itemFormFactory = ItemFormBase
     subForms={}
-    subFormsList = []
     form_fields = []
     actions = []
     subActionNames = []
@@ -186,17 +185,21 @@
 
     def setUpForms(self, *args, **kw):
         self.subForms = {}
-        self.subFormsList = []
         for name, item in self.context.items():
             inputMode = self.subFormInputMode.get(name,self.itemFormFactory.inputMode)
             self.setUpForm(name, item, inputMode)
-            self.subFormsList.append(name)
         self.refreshSubActionNames()
 
     def getForms(self):
-        for name in self.subFormsList:
+        # we have to use the keys here to support all actions that
+        # modifies the keys of our mapping, e.g. rename, delete
+        for name in self.context.keys():
+            if not self.subForms.has_key(name):
+                self.setUpForm(name,self.context[name],
+                               self.itemFormFactory.inputMode)
             yield self.subForms[name]
 
+
     def refreshSubActionNames(self):
         availableActions = set()
         for subForm in self.getForms():



More information about the Checkins mailing list