[Checkins] SVN: zmi.core/trunk/src/zmi/core/container/ Copy browser code from zope.app.container.

Yusei Tahara yusei at domen.cx
Sun Jun 7 11:25:08 EDT 2009


Log message for revision 100691:
  Copy browser code from zope.app.container.
  

Changed:
  A   zmi.core/trunk/src/zmi/core/container/__init__.py
  A   zmi.core/trunk/src/zmi/core/container/add.pt
  A   zmi.core/trunk/src/zmi/core/container/adding.py
  A   zmi.core/trunk/src/zmi/core/container/commontasks.pt
  A   zmi.core/trunk/src/zmi/core/container/configure.zcml
  A   zmi.core/trunk/src/zmi/core/container/contents.pt
  A   zmi.core/trunk/src/zmi/core/container/contents.py
  A   zmi.core/trunk/src/zmi/core/container/find.pt
  A   zmi.core/trunk/src/zmi/core/container/find.py
  A   zmi.core/trunk/src/zmi/core/container/index.pt
  A   zmi.core/trunk/src/zmi/core/container/tests/
  A   zmi.core/trunk/src/zmi/core/container/tests/__init__.py
  A   zmi.core/trunk/src/zmi/core/container/tests/configure.zcml
  A   zmi.core/trunk/src/zmi/core/container/tests/ftesting.zcml
  A   zmi.core/trunk/src/zmi/core/container/tests/index.txt
  A   zmi.core/trunk/src/zmi/core/container/tests/test_adding.py
  A   zmi.core/trunk/src/zmi/core/container/tests/test_contents.py
  A   zmi.core/trunk/src/zmi/core/container/tests/test_contents_functional.py
  A   zmi.core/trunk/src/zmi/core/container/tests/test_directive.py

-=-
Added: zmi.core/trunk/src/zmi/core/container/__init__.py
===================================================================
--- zmi.core/trunk/src/zmi/core/container/__init__.py	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/__init__.py	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.

Added: zmi.core/trunk/src/zmi/core/container/add.pt
===================================================================
--- zmi.core/trunk/src/zmi/core/container/add.pt	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/add.pt	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,62 @@
+<html metal:use-macro="context/@@standard_macros/addingdialog"
+    i18n:domain="zope">
+<body>
+<div metal:fill-slot="body" tal:define="infos view/addingInfo">
+
+  <p tal:condition="not:infos" i18n:translate="">
+    There are no addable content types for this container.
+  </p>
+
+  <form method="post" action="action.html" tal:condition="infos">
+    <table class="TypeListing" cellpadding="3">
+
+      <tal:block define="title view/title | nothing">
+         <caption tal:condition="title" tal:content="title"
+             i18n:translate="">Inserted title</caption>
+         <caption tal:condition="not:title"
+             i18n:translate="">Add Content</caption>
+      </tal:block>
+
+      <tbody>
+
+        <tr tal:repeat="info infos">
+
+          <td class="Selector">
+            <input type="radio" name="type_name"
+                   tal:attributes="value   info/action;
+                                   id      info/action;
+                                   checked python:len(infos)==1" />
+          </td>
+
+          <td class="TypeName">
+            <label style="font-weight: bold;"
+                   tal:attributes="for info/action">
+              <span tal:replace="info/title" i18n:translate="">Folder</span>
+            </label>
+            <div class="TypeDescription" tal:content="info/description"
+                i18n:translate="">
+              Folders are generic containers for content, including other
+              folders.
+            </div>
+          </td>
+        </tr>
+
+      <tr>
+        <td><br /></td>
+        <td><input type="text" name="id"
+                   tal:condition="view/nameAllowed"
+                   tal:attributes="value request/id | nothing" />
+            <input type="submit" name="add" value=" Add "
+                   i18n:attributes="value add-button" />
+        </td>
+      </tr>
+
+      </tbody>
+
+    </table>
+
+  </form>
+
+</div>
+</body>
+</html>

Added: zmi.core/trunk/src/zmi/core/container/adding.py
===================================================================
--- zmi.core/trunk/src/zmi/core/container/adding.py	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/adding.py	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,211 @@
+##############################################################################
+#
+# Copyright (c) 2002 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.
+#
+##############################################################################
+"""Adding View
+
+The Adding View is used to add new objects to a container. It is sort of a
+factory screen.
+
+$Id: adding.py 87419 2008-06-16 08:42:48Z ccomb $
+"""
+
+__docformat__ = 'restructuredtext'
+
+
+from zope.app.container.constraints import checkFactory
+from zope.app.container.constraints import checkObject
+from zope.app.container.i18n import ZopeMessageFactory as _
+from zope.app.container.interfaces import IAdding
+from zope.app.container.interfaces import IContainerNamesContainer
+from zope.app.container.interfaces import INameChooser
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+from zope.app.publisher.browser.menu import getMenu
+from zope.component import getMultiAdapter
+from zope.component import getUtility
+from zope.component import queryAdapter
+from zope.component import queryMultiAdapter
+from zope.component import queryUtility
+from zope.component.interfaces import IFactory
+from zope.event import notify
+from zope.exceptions.interfaces import UserError
+from zope.i18n.interfaces.locales import ICollator
+from zope.i18n.locales.fallbackcollator import FallbackCollator
+from zope.interface import implements
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.location import LocationProxy
+from zope.publisher.browser import BrowserView
+from zope.publisher.interfaces import IPublishTraverse
+from zope.security.proxy import removeSecurityProxy
+from zope.traversing.browser.absoluteurl import absoluteURL
+import zope.security.checker
+
+class Adding(BrowserView):
+    implements(IAdding, IPublishTraverse)
+
+    def add(self, content):
+        """See zope.app.container.interfaces.IAdding
+        """
+        container = self.context
+        name = self.contentName
+        chooser = INameChooser(container)
+
+        # check precondition
+        checkObject(container, name, content)
+
+        if IContainerNamesContainer.providedBy(container):
+            # The container picks its own names.
+            # We need to ask it to pick one.
+            name = chooser.chooseName(self.contentName or '', content)
+        else:
+            request = self.request
+            name = request.get('add_input_name', name)
+
+            if name is None:
+                name = chooser.chooseName(self.contentName or '', content)
+            elif name == '':
+                name = chooser.chooseName('', content)
+            chooser.checkName(name, content)
+
+        container[name] = content
+        self.contentName = name # Set the added object Name
+        return container[name]
+
+    contentName = None # usually set by Adding traverser
+
+    def nextURL(self):
+        """See zope.app.container.interfaces.IAdding"""
+        return absoluteURL(self.context, self.request) + '/@@contents.html'
+
+    # set in BrowserView.__init__
+    request = None
+    context = None
+
+    def publishTraverse(self, request, name):
+        """See zope.publisher.interfaces.IPublishTraverse"""
+        if '=' in name:
+            view_name, content_name = name.split("=", 1)
+            self.contentName = content_name
+
+            if view_name.startswith('@@'):
+                view_name = view_name[2:]
+            return getMultiAdapter((self, request), name=view_name)
+
+        if name.startswith('@@'):
+            view_name = name[2:]
+        else:
+            view_name = name
+
+        view = queryMultiAdapter((self, request), name=view_name)
+        if view is not None:
+            return view
+
+        factory = queryUtility(IFactory, name)
+        if factory is None:
+            return super(Adding, self).publishTraverse(request, name)
+
+        return factory
+
+    def action(self, type_name='', id=''):
+        if not type_name:
+            raise UserError(_(u"You must select the type of object to add."))
+
+        if type_name.startswith('@@'):
+            type_name = type_name[2:]
+
+        if '/' in type_name:
+            view_name = type_name.split('/', 1)[0]
+        else:
+            view_name = type_name
+
+        if queryMultiAdapter((self, self.request),
+                                  name=view_name) is not None:
+            url = "%s/%s=%s" % (
+                absoluteURL(self, self.request), type_name, id)
+            self.request.response.redirect(url)
+            return
+
+        if not self.contentName:
+            self.contentName = id
+
+        # TODO: If the factory wrapped by LocationProxy is already a Proxy,
+        #       then ProxyFactory does not do the right thing and the
+        #       original's checker info gets lost. No factory that was
+        #       registered via ZCML and was used via addMenuItem worked
+        #       here. (SR)
+        factory = getUtility(IFactory, type_name)
+        if not type(factory) is zope.security.checker.Proxy:
+            factory = LocationProxy(factory, self, type_name)
+            factory = zope.security.checker.ProxyFactory(factory)
+        content = factory()
+
+        # Can't store security proxies.
+        # Note that it is important to do this here, rather than
+        # in add, otherwise, someone might be able to trick add
+        # into unproxying an existing object,
+        content = removeSecurityProxy(content)
+
+        notify(ObjectCreatedEvent(content))
+
+        self.add(content)
+        self.request.response.redirect(self.nextURL())
+
+    def nameAllowed(self):
+        """Return whether names can be input by the user."""
+        return not IContainerNamesContainer.providedBy(self.context)
+
+    menu_id = None
+    index = ViewPageTemplateFile("add.pt")
+
+    def addingInfo(self):
+        """Return menu data.
+
+        This is sorted by title.
+        """
+        container = self.context
+        result = []
+        for menu_id in (self.menu_id, 'zope.app.container.add'):
+            if not menu_id:
+                continue
+            for item in getMenu(menu_id, self, self.request):
+                extra = item.get('extra')
+                if extra:
+                    factory = extra.get('factory')
+                    if factory:
+                        factory = getUtility(IFactory, factory)
+                        if not checkFactory(container, None, factory):
+                            continue
+                        elif item['extra']['factory'] != item['action']:
+                            item['has_custom_add_view']=True
+                # translate here to have a localized sorting
+                item['title'] = zope.i18n.translate(item['title'],
+                                                    context=self.request)
+                result.append(item)
+
+        # sort the adding info with a collator instead of a basic unicode sort
+        collator = queryAdapter(self.request.locale, ICollator)
+        if collator is None:
+            collator = FallbackCollator(self.request.locale)
+        result.sort(key = lambda x: collator.key(x['title']))
+        return result
+
+    def isSingleMenuItem(self):
+        "Return whether there is single menu item or not."
+        return len(self.addingInfo()) == 1
+
+    def hasCustomAddView(self):
+       "This should be called only if there is `singleMenuItem` else return 0"
+       if self.isSingleMenuItem():
+           menu_item = self.addingInfo()[0]
+           if 'has_custom_add_view' in menu_item:
+               return True
+       return False

Added: zmi.core/trunk/src/zmi/core/container/commontasks.pt
===================================================================
--- zmi.core/trunk/src/zmi/core/container/commontasks.pt	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/commontasks.pt	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,40 @@
+<tal:block define="addingInfo context/@@+/addingInfo|nothing"
+           condition="addingInfo">
+
+  <tal:block repeat="info addingInfo"
+    define="namesRequired context/@@+/nameAllowed">
+    <div tal:define="oddrow repeat/info/odd;
+                     has_custom_add_view python:'has_custom_add_view' in info"
+        tal:attributes="class python:oddrow and 'content even' or 'content odd'"
+        class="even">
+      <a href="#"
+        tal:define="baseurl python:request.getURL(1)"
+        tal:condition="python: not info['action'].startswith('../')
+                               and namesRequired and not has_custom_add_view"
+        tal:attributes="
+          href string:${baseurl}/@@contents.html?type_name=${info/action};
+          class info/selected"
+        tal:content="info/title">Folder
+      </a>
+
+      <a href="#"
+        tal:define="baseurl python:request.getURL(1)"
+        tal:condition="python: not info['action'].startswith('../')
+                   and (has_custom_add_view or not namesRequired)"
+        tal:attributes="
+          href string:${baseurl}/@@+/action.html?type_name=${info/action};
+          class info/selected"
+        tal:content="info/title">Folder
+      </a>
+
+      <a href="#"
+        tal:define="baseurl python:request.getURL(1)"
+        tal:condition="python: info['action'].startswith('../')"
+        tal:attributes="
+          href python: info['action'][3:];
+          class info/selected"
+        tal:content="info/title">Folder
+      </a>
+    </div>
+  </tal:block>
+</tal:block>

Added: zmi.core/trunk/src/zmi/core/container/configure.zcml
===================================================================
--- zmi.core/trunk/src/zmi/core/container/configure.zcml	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/configure.zcml	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,20 @@
+<zope:configure
+   xmlns:zope="http://namespaces.zope.org/zope"
+   xmlns="http://namespaces.zope.org/browser">
+
+  <page
+      for="zope.app.container.interfaces.IReadContainer"
+      name="find.html"
+      permission="zope.ManageContent"
+      class="zmi.core.container.find.Find"
+      template="find.pt"
+      menu="zmi_actions" title="Find" />
+
+  <page
+      for="zope.app.container.interfaces.IWriteContainer"
+      permission="zope.ManageContent"
+      name="commonTasks"
+      class="zmi.core.container.contents.Contents"
+      template="commontasks.pt" />
+
+</zope:configure>

Added: zmi.core/trunk/src/zmi/core/container/contents.pt
===================================================================
--- zmi.core/trunk/src/zmi/core/container/contents.pt	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/contents.pt	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,193 @@
+<html metal:use-macro="context/@@standard_macros/view"
+    i18n:domain="zope">
+<body>
+<div metal:fill-slot="body">
+  <div metal:define-macro="contents">
+
+    <form name="containerContentsForm" method="post" action="."
+          tal:attributes="action request/URL"
+          tal:define="container_contents view/listContentInfo">
+
+      <input type="hidden" name="type_name" value=""
+             tal:attributes="value request/type_name"
+             tal:condition="request/type_name|nothing"
+             />
+      <input type="hidden" name="retitle_id" value=""
+             tal:attributes="value request/retitle_id"
+             tal:condition="request/retitle_id|nothing"
+             />
+
+      <div class="page_error"
+           tal:condition="view/error"
+           tal:content="view/error"
+           i18n:translate="">
+        Error message
+      </div>
+
+      <table id="sortable" class="listing" summary="Content listing"
+             i18n:attributes="summary">
+
+        <thead>
+          <tr>
+            <th><input type="checkbox" onchange="updateCheckboxes(this, 'slaveBox');" /></th>
+            <th i18n:translate="">Name</th>
+            <th i18n:translate="">Title</th>
+            <th i18n:translate="">Size</th>
+            <th i18n:translate="">Created</th>
+            <th i18n:translate="">Modified</th>
+          </tr>
+        </thead>
+
+        <tbody>
+
+        <metal:block tal:condition="view/hasAdding">
+        <tr tal:define="names_required context/@@+/nameAllowed"
+            tal:condition="python:names_required and request.has_key('type_name')">
+          <td></td>
+          <td><input name="new_value" id="focusid" value="" /></td>
+          <td></td>
+          <td></td>
+          <td></td>
+        </tr>
+        </metal:block>
+
+        <metal:block tal:define="supportsRename view/supportsRename"
+                     tal:repeat="item container_contents">
+          <tr tal:define="oddrow repeat/item/odd; url item/url;
+                          id_quoted item/id/url:quote"
+              tal:attributes="class python:oddrow and 'even' or 'odd'" >
+            <td>
+              <input type="checkbox" class="noborder slaveBox" name="ids:list" id="#"
+                     value="#"
+                     tal:attributes="value item/id;
+                                     id item/cb_id;
+                                     checked request/ids_checked|nothing;"/>
+            </td>
+            <td><a href="#"
+                 tal:attributes="href
+                                 string:${url}/@@SelectedManagementView.html"
+                 tal:content="structure item/icon|default">
+                </a
+                ><span tal:condition="item/rename"
+                   ><input name="new_value:list"
+                     tal:attributes="value item/id"
+                     /><input type="hidden" name="rename_ids:list" value=""
+                              tal:attributes="value item/rename"
+                     /></span
+                ><span tal:condition="not:item/rename">
+                  <a href="#"
+                     tal:attributes="href
+                                 string:${url}/@@SelectedManagementView.html"
+                     tal:content="item/id"
+                     >foo</a
+                  ><a href="#"
+                     tal:attributes="href
+                         string:${request/URL}?rename_ids:list=${id_quoted}"
+                     tal:condition="supportsRename"
+                     >&nbsp;&nbsp;</a
+                ></span
+               ></td>
+            <td>
+              <input name="new_value" id="focusid"
+                     tal:attributes="value item/title|nothing"
+                     tal:condition="item/retitle"
+                     />
+              <a href="#"
+                 tal:attributes="href
+                                 string:${request/URL}?retitle_id=${id_quoted}"
+                 tal:condition="item/retitleable"
+                 tal:content="item/title|default"
+                 i18n:translate=""
+                 >&nbsp;&nbsp;&nbsp;&nbsp;</a>
+              <span
+                 tal:condition="item/plaintitle"
+                 tal:content="item/title|default"
+                 i18n:translate=""
+                 >&nbsp;&nbsp;&nbsp;&nbsp;</span>
+            </td>
+
+            <td><span tal:content="item/size/sizeForDisplay|nothing"
+                    i18n:translate="">
+                      &nbsp;</span></td>
+            <td><span tal:define="created item/created|default"
+                      tal:content="created"
+                      i18n:translate="">&nbsp;</span></td>
+            <td><span tal:define="modified item/modified|default"
+                      tal:content="modified"
+                      i18n:translate="">&nbsp;</span></td>
+          </tr>
+        </metal:block>
+
+        </tbody>
+      </table>
+
+      <tal:block tal:condition="view/normalButtons">
+
+        <input type="submit" name="container_rename_button" value="Rename"
+               i18n:attributes="value container-rename-button"
+               tal:condition="view/supportsRename"
+               />
+        <input type="submit" name="container_cut_button" value="Cut"
+               i18n:attributes="value container-cut-button"
+               tal:condition="view/supportsCut"
+               />
+        <input type="submit" name="container_copy_button" value="Copy"
+               i18n:attributes="value container-copy-button"
+               tal:condition="view/supportsCopy"
+               />
+        <input type="submit" name="container_paste_button" value="Paste"
+               tal:condition="view/hasClipboardContents"
+               i18n:attributes="value container-paste-button"
+               />
+        <input type="submit" name="container_delete_button" value="Delete"
+               i18n:attributes="value container-delete-button"
+               tal:condition="view/supportsDelete"
+               i18n:domain="zope"
+               />
+
+        <div tal:condition="view/hasAdding" tal:omit-tag="">
+        <div tal:omit-tag=""
+             tal:define="adding nocall:context/@@+;
+                         addingInfo adding/addingInfo;
+                         has_custom_add_view adding/hasCustomAddView;
+                         names_required adding/nameAllowed"
+             tal:condition="adding/isSingleMenuItem">
+          <input type="submit" name="container_add_button" value="Add"
+                 i18n:attributes="value add-button"
+                 i18n:domain="zope"
+               />
+          <input type="text" name="single_new_value" id="focusid"
+                 tal:condition="python:names_required and not has_custom_add_view"
+                 i18n:domain="zope"
+               />
+          <input type="hidden" name="single_type_name"
+               value=""
+               tal:attributes="value python:addingInfo[0]['action']"
+               />
+        </div>
+        </div>
+
+      </tal:block>
+
+      <div tal:condition="view/specialButtons">
+        <input type="submit" value="Apply"
+               i18n:attributes="value container-apply-button"
+               />
+        <input type="submit" name="container_cancel_button" value="Cancel"
+               i18n:attributes="value container-cancel-button"
+               />
+      </div>
+
+    </form>
+
+    <script type="text/javascript"><!--
+        if (document.containerContentsForm.new_value)
+            document.containerContentsForm.new_value.focus();
+        //-->
+    </script>
+
+  </div>
+
+</div>
+</body>
+</html>

Added: zmi.core/trunk/src/zmi/core/container/contents.py
===================================================================
--- zmi.core/trunk/src/zmi/core/container/contents.py	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/contents.py	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,464 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 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.
+#
+##############################################################################
+"""View Class for the Container's Contents view.
+
+$Id: contents.py 85553 2008-04-21 17:35:27Z lgs $
+"""
+__docformat__ = 'restructuredtext'
+
+import urllib
+
+from zope.component import queryMultiAdapter
+from zope.event import notify
+from zope.exceptions.interfaces import UserError
+from zope.security.interfaces import Unauthorized
+from zope.security import canWrite
+from zope.size.interfaces import ISized
+from zope.traversing.interfaces import TraversalError
+from zope.publisher.browser import BrowserView
+from zope.dublincore.interfaces import IZopeDublinCore
+from zope.dublincore.interfaces import IDCDescriptiveProperties
+from zope.copypastemove.interfaces import IPrincipalClipboard
+from zope.copypastemove.interfaces import IObjectCopier, IObjectMover
+from zope.copypastemove.interfaces import IContainerItemRenamer
+from zope.annotation.interfaces import IAnnotations
+from zope.lifecycleevent import ObjectModifiedEvent, Attributes
+from zope.traversing.api import getName, getPath, joinPath, traverse
+
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+from zope.app.container.i18n import ZopeMessageFactory as _
+
+from zmi.core.container.adding import Adding
+from zope.app.container.interfaces import IContainer, DuplicateIDError
+from zope.app.container.interfaces import IContainerNamesContainer
+
+class Contents(BrowserView):
+
+    __used_for__ = IContainer
+
+    error = ''
+    message = ''
+    normalButtons = False
+    specialButtons = False
+    supportsRename = False
+
+    def listContentInfo(self):
+        request = self.request
+
+        if  "container_cancel_button" in request:
+            if "type_name" in request:
+                del request.form['type_name']
+            if "rename_ids" in request and "new_value" in request:
+                del request.form['rename_ids']
+            if "retitle_id" in request and "new_value" in request:
+                del request.form['retitle_id']
+
+            return self._normalListContentsInfo()
+
+        elif "container_rename_button" in request and not request.get("ids"):
+            self.error = _("You didn't specify any ids to rename.")
+        elif "container_add_button" in request:
+            if "single_type_name" in request \
+                   and "single_new_value" in request:
+                request.form['type_name'] = request['single_type_name']
+                request.form['new_value'] = request['single_new_value']
+                self.addObject()
+            elif 'single_type_name' in request \
+                     and 'single_new_value' not in request:
+                request.form['type_name'] = request['single_type_name']
+                request.form['new_value'] = ""
+                self.addObject()
+        elif "type_name" in request and "new_value" in request:
+            self.addObject()
+        elif "rename_ids" in request and "new_value" in request:
+            self.renameObjects()
+        elif "retitle_id" in request and "new_value" in request:
+            self.changeTitle()
+        elif "container_cut_button" in request:
+            self.cutObjects()
+        elif "container_copy_button" in request:
+            self.copyObjects()
+        elif "container_paste_button" in request:
+            self.pasteObjects()
+        elif "container_delete_button" in request:
+            self.removeObjects()
+        else:
+            return self._normalListContentsInfo()
+
+        if self.error:
+            return self._normalListContentsInfo()
+
+        status = request.response.getStatus()
+        if status not in (302, 303):
+            # Only redirect if nothing else has
+            request.response.redirect(request.URL)
+        return ()
+
+    def normalListContentInfo(self):
+        return self._normalListContentsInfo()
+
+    def _normalListContentsInfo(self):
+        request = self.request
+
+        self.specialButtons = (
+                 'type_name' in request or
+                 'rename_ids' in request or
+                 ('container_rename_button' in request
+                  and request.get("ids")) or
+                 'retitle_id' in request
+                 )
+        self.normalButtons = not self.specialButtons
+
+        info = map(self._extractContentInfo, self.context.items())
+
+        self.supportsCut = info
+        self.supportsCopy = info
+        self.supportsDelete = info
+        self.supportsPaste = self.pasteable()
+        self.supportsRename = (
+            self.supportsCut and
+            not IContainerNamesContainer.providedBy(self.context)
+            )
+
+        return info
+
+
+    def _extractContentInfo(self, item):
+        request = self.request
+
+
+        rename_ids = {}
+        if "container_rename_button" in request:
+            for rename_id in request.get('ids', ()):
+                rename_ids[rename_id] = rename_id
+        elif "rename_ids" in request:
+            for rename_id in request.get('rename_ids', ()):
+                rename_ids[rename_id] = rename_id
+
+
+        retitle_id = request.get('retitle_id')
+
+        id, obj = item
+        info = {}
+        info['id'] = info['cb_id'] = id
+        info['object'] = obj
+
+        info['url'] = urllib.quote(id.encode('utf-8'))
+        info['rename'] = rename_ids.get(id)
+        info['retitle'] = id == retitle_id
+
+
+        zmi_icon = queryMultiAdapter((obj, self.request), name='zmi_icon')
+        if zmi_icon is None:
+            info['icon'] = None
+        else:
+            info['icon'] = zmi_icon()
+
+        dc = IZopeDublinCore(obj, None)
+        if dc is not None:
+            info['retitleable'] = canWrite(dc, 'title')
+            info['plaintitle'] = not info['retitleable']
+
+            title = self.safe_getattr(dc, 'title', None)
+            if title:
+                info['title'] = title
+
+            formatter = self.request.locale.dates.getFormatter(
+                'dateTime', 'short')
+
+            created = self.safe_getattr(dc, 'created', None)
+            if created is not None:
+                info['created'] = formatter.format(created)
+
+            modified = self.safe_getattr(dc, 'modified', None)
+            if modified is not None:
+                info['modified'] = formatter.format(modified)
+        else:
+            info['retitleable'] = 0
+            info['plaintitle'] = 1
+
+
+        sized_adapter = ISized(obj, None)
+        if sized_adapter is not None:
+            info['size'] = sized_adapter
+        return info
+
+    def safe_getattr(self, obj, attr, default):
+        """Attempts to read the attr, returning default if Unauthorized."""
+        try:
+            return getattr(obj, attr, default)
+        except Unauthorized:
+            return default
+
+    def renameObjects(self):
+        """Given a sequence of tuples of old, new ids we rename"""
+        request = self.request
+        ids = request.get("rename_ids")
+        newids = request.get("new_value")
+
+        renamer = IContainerItemRenamer(self.context)
+        for oldid, newid in zip(ids, newids):
+            if newid != oldid:
+                renamer.renameItem(oldid, newid)
+
+    def changeTitle(self):
+        """Given a sequence of tuples of old, new ids we rename"""
+        request = self.request
+        id = request.get("retitle_id")
+        new = request.get("new_value")
+
+        item = self.context[id]
+        dc = IDCDescriptiveProperties(item)
+        dc.title = new
+        notify(ObjectModifiedEvent(item, Attributes(IZopeDublinCore, 'title')))
+
+    def hasAdding(self):
+        """Returns true if an adding view is available."""
+        adding = queryMultiAdapter((self.context, self.request), name="+")
+        return (adding is not None)
+
+    def addObject(self):
+        request = self.request
+        if IContainerNamesContainer.providedBy(self.context):
+            new = ""
+        else:
+            new = request["new_value"]
+
+        adding = queryMultiAdapter((self.context, self.request), name="+")
+        if adding is None:
+            adding = Adding(self.context, request)
+        else:
+            # Set up context so that the adding can build a url
+            # if the type name names a view.
+            # Note that we can't so this for the "adding is None" case
+            # above, because there is no "+" view.
+            adding.__parent__ = self.context
+            adding.__name__ = '+'
+
+        adding.action(request['type_name'], new)
+
+    def removeObjects(self):
+        """Remove objects specified in a list of object ids"""
+        request = self.request
+        ids = request.get('ids')
+        if not ids:
+            self.error = _("You didn't specify any ids to remove.")
+            return
+
+        container = self.context
+        for id in ids:
+            del container[id]
+
+    def copyObjects(self):
+        """Copy objects specified in a list of object ids"""
+        request = self.request
+        ids = request.get('ids')
+        if not ids:
+            self.error = _("You didn't specify any ids to copy.")
+            return
+
+        container_path = 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 id in ids:
+            ob = self.context[id]
+            copier = IObjectCopier(ob)
+            if not copier.copyable():
+                m = {"name": id}
+                title = getDCTitle(ob)
+                if title:
+                    m["title"] = title
+                    self.error = _(
+                        "Object '${name}' (${title}) cannot be copied",
+                        mapping=m)
+                else:
+                    self.error = _("Object '${name}' cannot be copied",
+                                   mapping=m)
+                return
+            items.append(joinPath(container_path, id))
+
+        # store the requested operation in the principal annotations:
+        clipboard = getPrincipalClipboard(self.request)
+        clipboard.clearContents()
+        clipboard.addItems('copy', items)
+
+    def cutObjects(self):
+        """move objects specified in a list of object ids"""
+        request = self.request
+        ids = request.get('ids')
+        if not ids:
+            self.error = _("You didn't specify any ids to cut.")
+            return
+
+        container_path = 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 id in ids:
+            ob = self.context[id]
+            mover = IObjectMover(ob)
+            if not mover.moveable():
+                m = {"name": id}
+                title = getDCTitle(ob)
+                if title:
+                    m["title"] = title
+                    self.error = _(
+                        "Object '${name}' (${title}) cannot be moved",
+                        mapping=m)
+                else:
+                    self.error = _("Object '${name}' cannot be moved",
+                                   mapping=m)
+                return
+            items.append(joinPath(container_path, id))
+
+        # store the requested operation in the principal annotations:
+        clipboard = getPrincipalClipboard(self.request)
+        clipboard.clearContents()
+        clipboard.addItems('cut', items)
+
+
+    def pasteable(self):
+        """Decide if there is anything to paste
+        """
+        target = self.context
+        clipboard = getPrincipalClipboard(self.request)
+        items = clipboard.getContents()
+        for item in items:
+            try:
+                obj = traverse(target, item['target'])
+            except TraversalError:
+                pass
+            else:
+                if item['action'] == 'cut':
+                    mover = IObjectMover(obj)
+                    moveableTo = self.safe_getattr(mover, 'moveableTo', None)
+                    if moveableTo is None or not moveableTo(target):
+                        return False
+                elif item['action'] == 'copy':
+                    copier = IObjectCopier(obj)
+                    copyableTo = self.safe_getattr(copier, 'copyableTo', None)
+                    if copyableTo is None or not copyableTo(target):
+                        return False
+                else:
+                    raise
+
+        return True
+
+
+    def pasteObjects(self):
+        """Paste ojects in the user clipboard to the container
+        """
+        target = self.context
+        clipboard = getPrincipalClipboard(self.request)
+        items = clipboard.getContents()
+        moved = False
+        not_pasteable_ids = []
+        for item in items:
+            duplicated_id = False
+            try:
+                obj = 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(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 ?
+            raise UserError(
+                _("The given name(s) %s is / are already being used" %(
+                str(not_pasteable_ids))))
+
+    def hasClipboardContents(self):
+        """Interogate the ``PrinicipalAnnotation`` to see if clipboard
+        contents exist."""
+
+        if not self.supportsPaste:
+            return False
+
+        # touch at least one item to in clipboard confirm contents
+        clipboard = getPrincipalClipboard(self.request)
+        items = clipboard.getContents()
+        for item in items:
+            try:
+                traverse(self.context, item['target'])
+            except TraversalError:
+                pass
+            else:
+                return True
+
+        return False
+
+    contents = ViewPageTemplateFile('contents.pt')
+    contentsMacros = contents
+
+    _index = ViewPageTemplateFile('index.pt')
+
+    def index(self):
+        if 'index.html' in self.context:
+            self.request.response.redirect('index.html')
+            return ''
+
+        return self._index()
+
+class JustContents(Contents):
+    """Like Contents, but does't delegate to item named index.html"""
+
+    def index(self):
+        return self._index()
+
+
+def getDCTitle(ob):
+    dc = IDCDescriptiveProperties(ob, None)
+    if dc is None:
+        return None
+    else:
+        return dc.title
+
+
+def getPrincipalClipboard(request):
+    """Return the clipboard based on the request."""
+    user = request.principal
+    annotations = IAnnotations(user)
+    return IPrincipalClipboard(annotations)

Added: zmi.core/trunk/src/zmi/core/container/find.pt
===================================================================
--- zmi.core/trunk/src/zmi/core/container/find.pt	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/find.pt	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,22 @@
+<html metal:use-macro="context/@@standard_macros/dialog"
+    i18n:domain="zope">
+<body>
+<div metal:fill-slot="body" >
+
+  <form action="@@find.html" method="get">
+    <input type="text" name="ids" value="" /><br />
+    <input type="submit" name="find_submit" value=" Find " 
+           i18n:attributes="value find-button"/>
+  </form>
+
+  <table tal:condition="request/ids|nothing">
+    <tr tal:repeat="item python:view.findByIds(request['ids'])">
+      <td>
+        <a href="" tal:attributes="href item/url" tal:content="item/id">id</a>
+      </td>
+    </tr>
+  </table>
+
+</div>
+</body>
+</html>

Added: zmi.core/trunk/src/zmi/core/container/find.py
===================================================================
--- zmi.core/trunk/src/zmi/core/container/find.py	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/find.py	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,42 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 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.
+#
+##############################################################################
+"""Find View Class
+
+$Id: find.py 67630 2006-04-27 00:54:03Z jim $
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.traversing.api import getName
+from zope.traversing.browser.absoluteurl import absoluteURL
+from zope.publisher.browser import BrowserView
+
+from zope.app.container.find import SimpleIdFindFilter
+from zope.app.container.interfaces import IFind
+
+# Very simple implementation right now
+class Find(BrowserView):
+
+    def findByIds(self, ids):
+        """Do a find for the `ids` listed in `ids`, which is a string."""
+        finder = IFind(self.context)
+        ids = ids.split()
+        # if we don't have any ids listed, don't search at all
+        if not ids:
+            return []
+        request = self.request
+        result = []
+        for object in finder.find([SimpleIdFindFilter(ids)]):
+            url = absoluteURL(object, request)
+            result.append({ 'id': getName(object), 'url': url})
+        return result

Added: zmi.core/trunk/src/zmi/core/container/index.pt
===================================================================
--- zmi.core/trunk/src/zmi/core/container/index.pt	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/index.pt	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,66 @@
+<html metal:use-macro="context/@@standard_macros/page"
+    i18n:domain="zope">
+<head>
+  <style metal:fill-slot="headers" type="text/css">
+    <!--
+    .ContentIcon {
+      width: 20px;
+    }
+
+    .ContentTitle {
+      text-align: left;
+    }
+    -->
+  </style>
+</head>
+<body>
+<div metal:fill-slot="body">
+
+  <table
+      id="sortable" class="listing" summary="Content listing"
+      cellpadding="2" cellspacing="0"
+      i18n:attributes="summary">
+
+    <thead> 
+      <tr>
+        <th>&nbsp;</th>
+        <th i18n:translate="">Name</th>
+        <th i18n:translate="">Title</th>
+        <th i18n:translate="">Created</th>
+        <th i18n:translate="">Modified</th>
+      </tr>
+    </thead>
+
+    <tbody>
+
+      <tr tal:repeat="info view/listContentInfo">
+        <td>
+          <a
+              href="#"
+              tal:attributes="href info/url"
+              tal:content="structure info/icon|default" />
+        </td>
+
+        <td class="ContentTitle">
+          <a href="subfolder_id"
+             tal:attributes="href info/url"
+             tal:content="info/id"
+             i18n:translate=""
+          >ID here</a>
+        </td>
+
+        <td><span tal:content="info/title|default"
+            i18n:translate="">&nbsp;</span></td>
+        <td><span tal:content="info/created|default"
+            i18n:translate="">&nbsp;</span></td>
+        <td><span tal:content="info/modified|default"
+            i18n:translate="">&nbsp;</span></td>
+
+      </tr>
+    </tbody>
+
+  </table>
+
+</div>
+</body>
+</html>

Added: zmi.core/trunk/src/zmi/core/container/tests/__init__.py
===================================================================
--- zmi.core/trunk/src/zmi/core/container/tests/__init__.py	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/tests/__init__.py	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.

Added: zmi.core/trunk/src/zmi/core/container/tests/configure.zcml
===================================================================
--- zmi.core/trunk/src/zmi/core/container/tests/configure.zcml	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/tests/configure.zcml	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,16 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:browser="http://namespaces.zope.org/browser">
+
+  <class class=".test_contents_functional.ReadOnlyContainer">
+    <require
+        permission="zope.ManageContent"
+        interface="zope.app.container.interfaces.IReadContainer"
+        />
+  </class>
+
+  <browser:containerViews
+      for="zope.app.container.interfaces.IReadContainer"
+      contents="zope.ManageContent" />
+
+</configure>

Added: zmi.core/trunk/src/zmi/core/container/tests/ftesting.zcml
===================================================================
--- zmi.core/trunk/src/zmi/core/container/tests/ftesting.zcml	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/tests/ftesting.zcml	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,55 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   i18n_domain="zope"
+   package="zmi.core.container"
+   >
+  <!-- This file is the equivalent of site.zcml and it is -->
+  <!-- used for functional testing setup -->
+
+  <include package="zope.app.zcmlfiles" />
+  <include package="zmi.core.container.tests" />
+  <include package="zope.app.file"/>
+  <include package="zope.app.authentication" />
+
+  <include package="zope.securitypolicy" file="meta.zcml" />
+  <include package="zope.app.securitypolicy.browser.tests" file="functional.zcml" />
+  <include package="zope.app.securitypolicy" />
+
+  <securityPolicy
+      component="zope.securitypolicy.zopepolicy.ZopeSecurityPolicy" />
+
+  <role id="zope.Anonymous" title="Everybody"
+                 description="All users have this role implicitly" />
+  <role id="zope.Manager" title="Site Manager" />
+
+  <!-- Replace the following directive if you don't want public access -->
+  <grant permission="zope.View"
+                  role="zope.Anonymous" />
+  <grant permission="zope.app.dublincore.view"
+                  role="zope.Anonymous" />
+
+  <grantAll role="zope.Manager" />
+
+  <!-- Principals -->
+
+  <unauthenticatedPrincipal
+      id="zope.anybody"
+      title="Unauthenticated User" />
+
+  <!-- Principal that tests generally run as -->
+  <principal
+      id="zope.mgr"
+      title="Manager"
+      login="mgr"
+      password="mgrpw" />
+
+  <!-- Bootstrap principal used to make local grant to the principal above -->
+  <principal
+      id="zope.globalmgr"
+      title="Manager"
+      login="globalmgr"
+      password="globalmgrpw" />
+
+  <grant role="zope.Manager" principal="zope.globalmgr" />
+
+</configure>

Added: zmi.core/trunk/src/zmi/core/container/tests/index.txt
===================================================================
--- zmi.core/trunk/src/zmi/core/container/tests/index.txt	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/tests/index.txt	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,18 @@
+The containerViews directive lets us associate some standard forms
+for containers with an interface.  There's an "index.html" view that
+provides a listing of the contained objects without provinding any way
+to manage them (though it allows us to visit them by clicking on
+links).
+
+We can get this view from the root folder easily::
+
+  >>> response = http(r"""
+  ... GET / HTTP/1.1
+  ... """)
+
+And we can check that there isn't a form (where the management
+operations would have buttons)::
+
+  >>> body = response.getBody().lower()
+  >>> "<form" in body
+  False


Property changes on: zmi.core/trunk/src/zmi/core/container/tests/index.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Added: zmi.core/trunk/src/zmi/core/container/tests/test_adding.py
===================================================================
--- zmi.core/trunk/src/zmi/core/container/tests/test_adding.py	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/tests/test_adding.py	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,586 @@
+##############################################################################
+#
+# Copyright (c) 2002 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.
+#
+##############################################################################
+"""Adding implementation tests
+
+$Id: test_adding.py 96149 2009-02-05 17:24:17Z tseaver $
+"""
+import unittest
+
+import zope.interface
+import zope.security.checker
+from zope.component.interfaces import IFactory
+from zope.component.interfaces import ComponentLookupError
+from zope.interface import implements, Interface, directlyProvides
+from zope.publisher.browser import TestRequest
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.browser import BrowserView
+from zope.security.interfaces import ForbiddenAttribute
+from zope.testing.doctestunit import DocTestSuite
+from zope.exceptions.interfaces import UserError
+from zope.traversing.api import getParent
+from zope.traversing.browser import AbsoluteURL
+from zope.traversing.browser.absoluteurl import absoluteURL
+from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.traversing.interfaces import IContainmentRoot
+
+from zope.app.testing import ztapi
+from zope.app.testing.placelesssetup import PlacelessSetup, setUp, tearDown
+from zope.app.publisher.interfaces.browser import AddMenu
+from zope.app.publisher.interfaces.browser import IMenuItemType, IBrowserMenu
+from zope.app.publisher.browser.menu import BrowserMenuItem, BrowserMenu
+from zope.app.container.interfaces import IAdding
+from zope.app.container.interfaces import IObjectAddedEvent
+from zope.app.container.interfaces import IContainerNamesContainer
+from zope.app.container.interfaces import INameChooser
+from zope.app.container.interfaces import IContainer
+from zope.app.container.contained import contained
+from zmi.core.container.adding import Adding
+from zope.app.container.sample import SampleContainer
+
+
+class Root(object):
+    implements(IContainmentRoot)
+
+class Container(SampleContainer):
+    pass
+
+class CreationView(BrowserView):
+
+    def action(self):
+        return 'been there, done that'
+
+
+class Content(object):
+    pass
+
+class Factory(object):
+
+    implements(IFactory)
+
+    title = ''
+    description = ''
+
+    def getInterfaces(self):
+        return ()
+
+    def __call__(self):
+        return Content()
+
+
+class AbsoluteURL(BrowserView):
+
+    def __str__(self):
+        if IContainmentRoot.providedBy(self.context):
+            return ''
+        name = self.context.__name__
+        url = absoluteURL(getParent(self.context), self.request)
+        url += '/' + name
+        return url
+
+    __call__ = __str__
+
+def defineMenuItem(menuItemType, for_, action, title=u'', extra=None):
+    newclass = type(title, (BrowserMenuItem,),
+                    {'title':title, 'action':action,
+                     '_for': for_, 'extra':extra})
+    zope.interface.classImplements(newclass, menuItemType)
+    ztapi.provideAdapter((for_, IBrowserRequest), menuItemType, newclass, title)
+
+def registerAddMenu():
+  ztapi.provideUtility(IMenuItemType, AddMenu, 'zope.app.container.add')
+  ztapi.provideUtility(IBrowserMenu,
+                       BrowserMenu('zope.app.container.add', u'', u''),
+                       'zope.app.container.add')
+
+
+class Test(PlacelessSetup, unittest.TestCase):
+
+    def setUp(self):
+        super(Test, self).setUp()
+
+    def test(self):
+        container = Container()
+        request = TestRequest()
+        adding = Adding(container, request)
+        ztapi.browserView(IAdding, "Thing", CreationView)
+        self.assertEqual(adding.contentName, None)
+        view = adding.publishTraverse(request, 'Thing=foo')
+        self.assertEqual(view.action(), 'been there, done that')
+        self.assertEqual(adding.contentName, 'foo')
+
+        o = object()
+        result = adding.add(o)
+
+        # Check the state of the container and result
+        self.assertEqual(container["foo"], o)
+        self.assertEqual(result, o)
+
+    def testNoNameGiven(self):
+        container = Container()
+        request = TestRequest()
+        adding = Adding(container, request)
+        ztapi.browserView(IAdding, "Thing", CreationView)
+
+        self.assertEqual(adding.contentName, None)
+        view = adding.publishTraverse(request, 'Thing=')
+        self.assertEqual(adding.contentName, '')
+
+    def testAction(self):
+        # make a private factory
+        ztapi.provideUtility(IFactory, Factory(), 'fooprivate')
+
+        factory = Factory()
+        factory.__Security_checker__ = zope.security.checker.NamesChecker(
+            ['__call__'])
+        ztapi.provideUtility(IFactory, factory, 'foo')
+
+        container = Container()
+        adding = Adding(container, TestRequest())
+        adding.nextURL = lambda: '.'
+        adding.nameAllowed = lambda: True
+
+        # we can't use a private factory:
+        self.assertRaises(ForbiddenAttribute,
+                          adding.action, type_name='fooprivate', id='bar')
+
+        # typical add - id is provided by user
+        adding.action(type_name='foo', id='bar')
+        self.assert_('bar' in container)
+
+        # missing type_name
+        self.assertRaises(UserError, adding.action, id='bar')
+
+        # missing id
+        self.assertRaises(KeyError, adding.action, type_name='foo')
+
+        # bad type_name
+        self.assertRaises(ComponentLookupError, adding.action,
+            type_name='***', id='bar')
+
+        # alternative add - id is provided internally instead of from user
+        adding.nameAllowed = lambda: False
+        adding.contentName = 'baz'
+        adding.action(type_name='foo')
+        self.assert_('baz' in container)
+
+        # alternative add w/missing contentName
+        # Note: Passing is None as object name might be okay, if the container
+        #       is able to hand out ids itself. Let's not require a content
+        #       name to be specified!
+        # For the container, (or really, the chooser, to choose, we have to
+        # marke the container as a ContainerNamesContainer
+        directlyProvides(container, IContainerNamesContainer)
+        adding.contentName = None
+        adding.action(type_name='foo')
+        self.assert_('Content' in container)
+
+
+    def test_action(self):
+        container = Container()
+        container = contained(container, Root(), "container")
+        request = TestRequest()
+        adding = Adding(container, request)
+        adding.__name__ = '+'
+        ztapi.browserView(IAdding, "Thing", CreationView)
+        ztapi.browserView(Interface, "absolute_url", AbsoluteURL)
+        ztapi.browserView(None, '', AbsoluteURL, providing=IAbsoluteURL)
+        self.assertRaises(UserError, adding.action, '', 'foo')
+        adding.action('Thing', 'foo')
+        self.assertEqual(adding.request.response.getHeader('location'),
+                         '/container/+/Thing=foo')
+        adding.action('Thing/screen1', 'foo')
+        self.assertEqual(adding.request.response.getHeader('location'),
+                         '/container/+/Thing/screen1=foo')
+
+    def test_publishTraverse_factory(self):
+        factory = Factory()
+        ztapi.provideUtility(IFactory, factory, 'foo')
+        container = Container()
+        request = TestRequest()
+        adding = Adding(container, request)
+        self.assert_(adding.publishTraverse(request, 'foo') is factory)
+
+
+def test_constraint_driven_addingInfo():
+    """
+    >>> registerAddMenu()
+
+    >>> class TestMenu(zope.interface.Interface):
+    ...     pass
+    >>> zope.interface.directlyProvides(TestMenu, IMenuItemType)
+
+    >>> ztapi.provideUtility(IMenuItemType, TestMenu, 'TestMenu')
+    >>> ztapi.provideUtility(IBrowserMenu, BrowserMenu('TestMenu', u'', u''),
+    ...                      'TestMenu')
+
+    >>> defineMenuItem(TestMenu, IAdding, '', 'item1')
+    >>> defineMenuItem(TestMenu, IAdding, '', 'Item2')
+
+    >>> defineMenuItem(AddMenu, IAdding, '', 'item3', extra={'factory': 'f1'})
+    >>> defineMenuItem(AddMenu, IAdding, '', 'item4', extra={'factory': 'f2'})
+
+    >>> class F1(object):
+    ...     pass
+
+    >>> class F2(object):
+    ...     pass
+
+    >>> def pre(container, name, object):
+    ...     if not isinstance(object, F1):
+    ...         raise zope.interface.Invalid()
+    >>> def prefactory(container, name, factory):
+    ...     if factory._callable is not F1:
+    ...         raise zope.interface.Invalid()
+    >>> pre.factory = prefactory
+
+
+    >>> class IContainer(zope.interface.Interface):
+    ...     def __setitem__(name, object):
+    ...         pass
+    ...     __setitem__.precondition = pre
+
+
+    >>> class Container(object):
+    ...     zope.interface.implements(IContainer)
+
+    >>> from zope.component.factory import Factory
+    >>> ztapi.provideUtility(IFactory, Factory(F1), 'f1')
+    >>> ztapi.provideUtility(IFactory, Factory(F2), 'f2')
+
+    >>> from zmi.core.container.adding import Adding
+    >>> adding = Adding(Container(), TestRequest())
+    >>> items = adding.addingInfo()
+    >>> len(items)
+    1
+    >>> items[0]['title']
+    u'item3'
+
+    >>> adding.menu_id = 'TestMenu'
+    >>> items = adding.addingInfo()
+    >>> len(items)
+    3
+    >>> items[0]['title']
+    u'item1'
+    >>> items[1]['title'] # the collator ordered this one correctly!
+    u'Item2'
+    >>> items[2]['title']
+    u'item3'
+    """
+
+def test_constraint_driven_add():
+    """
+    >>> from zope.app.container.sample import SampleContainer
+    >>> from zmi.core.container.adding import Adding
+
+    >>> class F1(object):
+    ...     pass
+
+    >>> class F2(object):
+    ...     pass
+
+    >>> def pre(container, name, object):
+    ...     "a mock item constraint "
+    ...     if not isinstance(object, F1):
+    ...         raise zope.interface.Invalid('not a valid child')
+
+    >>> class ITestContainer(zope.interface.Interface):
+    ...     def __setitem__(name, object):
+    ...         pass
+    ...     __setitem__.precondition = pre
+
+    >>> class Container(SampleContainer):
+    ...     zope.interface.implements(ITestContainer)
+
+    >>> adding = Adding(Container(), TestRequest())
+    >>> c = adding.add(F1())
+
+    This test should fail, because the container only
+    accepts instances of F1
+
+    >>> adding.add(F2())
+    Traceback (most recent call last):
+    ...
+    Invalid: not a valid child
+
+    >>> class ValidContainer(SampleContainer):
+    ...     zope.interface.implements(ITestContainer)
+
+    >>> def constr(container):
+    ...     "a mock container constraint"
+    ...     if not isinstance(container, ValidContainer):
+    ...         raise zope.interface.Invalid('not a valid container')
+    ...     return True
+
+    >>> class I2(zope.interface.Interface):
+    ...     __parent__ = zope.schema.Field(constraint = constr)
+
+    >>> zope.interface.classImplements(F1, I2)
+
+    This adding now fails, because the Container is not a valid
+    parent for F1
+
+    >>> c = adding.add(F1())
+    Traceback (most recent call last):
+    ...
+    Invalid: not a valid container
+
+    >>> adding = Adding(ValidContainer(), TestRequest())
+    >>> c = adding.add(F1())
+
+    """
+
+
+def test_nameAllowed():
+    """
+    Test for nameAllowed in adding.py
+
+    >>> from zmi.core.container.adding import Adding
+    >>> from zope.app.container.interfaces import IContainerNamesContainer
+
+    Class implements IContainerNamesContainer
+
+    >>> class FakeContainer(object):
+    ...    zope.interface.implements(IContainerNamesContainer)
+
+    nameAllowed returns False if the class imlements
+    IContainerNamesContainer
+
+    >>> adding = Adding(FakeContainer(),TestRequest())
+    >>> adding.nameAllowed()
+    False
+
+    Fake class without IContainerNamesContainer
+
+    >>> class Fake(object):
+    ...    pass
+
+    nameAllowed returns True if the class
+    doesn't imlement IContainerNamesContainer
+
+    >>> adding = Adding(Fake(),TestRequest())
+    >>> adding.nameAllowed()
+    True
+
+    """
+
+
+
+def test_chooseName():
+    """If user don't enter name, pick one
+
+    >>> class MyContainer(object):
+    ...    args = {}
+    ...    zope.interface.implements(INameChooser, IContainer)
+    ...    def chooseName(self, name, object):
+    ...        self.args["choose"] = name, object
+    ...        return 'pickone'
+    ...    def checkName(self, name, object):
+    ...        self.args["check"] = name, object
+    ...    def __setitem__(self, name, object):
+    ...        setattr(self, name, object)
+    ...        self.name = name
+    ...    def __getitem__(self, key):
+    ...        return getattr(self, key)
+
+    >>> request = TestRequest()
+    >>> mycontainer = MyContainer()
+    >>> adding = Adding(mycontainer, request)
+    >>> o = object()
+    >>> add_obj = adding.add(o)
+    >>> mycontainer.name
+    'pickone'
+    >>> add_obj is o
+    True
+
+    Make sure right arguments passed to INameChooser adapter:
+
+    >>> name, obj = mycontainer.args["choose"]
+    >>> name
+    ''
+    >>> obj is o
+    True
+    >>> name, obj = mycontainer.args["check"]
+    >>> name
+    'pickone'
+    >>> obj is o
+    True
+    """
+
+
+
+def test_SingleMenuItem_and_CustomAddView_NonICNC():
+    """
+    This tests the condition if the content has Custom Add views and
+    the container contains only a single content object
+
+    >>> registerAddMenu()
+    >>> defineMenuItem(AddMenu, IAdding, '', 'item3', extra={'factory': 'f1'})
+
+    >>> class F1(object):
+    ...     pass
+
+    >>> class F2(object):
+    ...     pass
+
+    >>> def pre(container, name, object):
+    ...     if not isinstance(object, F1):
+    ...         raise zope.interface.Invalid()
+    >>> def prefactory(container, name, factory):
+    ...     if factory._callable is not F1:
+    ...         raise zope.interface.Invalid()
+    >>> pre.factory = prefactory
+
+
+    >>> class IContainer(zope.interface.Interface):
+    ...     def __setitem__(name, object):
+    ...         pass
+    ...     __setitem__.precondition = pre
+
+
+    >>> class Container(object):
+    ...     zope.interface.implements(IContainer)
+
+    >>> from zope.component.factory import Factory
+    >>> ztapi.provideUtility(IFactory, Factory(F1), 'f1')
+    >>> ztapi.provideUtility(IFactory, Factory(F2), 'f2')
+
+    >>> from zmi.core.container.adding import Adding
+    >>> adding = Adding(Container(), TestRequest())
+    >>> items = adding.addingInfo()
+    >>> len(items)
+    1
+
+    isSingleMenuItem returns True if there is only one content class
+    inside the Container
+
+    >>> adding.isSingleMenuItem()
+    True
+
+    hasCustomAddView will return False as the content does not have
+    a custom Add View
+
+    >>> adding.hasCustomAddView()
+    True
+
+    """
+
+def test_SingleMenuItem_and_NoCustomAddView_NonICNC():
+    """
+
+    This function checks the case where there is a single content object
+    and there is non custom add view . Also the container does not
+    implement IContainerNamesContainer
+
+    >>> registerAddMenu()
+    >>> defineMenuItem(AddMenu, None, '', 'item3', extra={'factory': ''})
+    >>> class F1(object):
+    ...     pass
+
+    >>> class F2(object):
+    ...     pass
+
+    >>> def pre(container, name, object):
+    ...     if not isinstance(object, F1):
+    ...         raise zope.interface.Invalid()
+    >>> def prefactory(container, name, factory):
+    ...     if factory._callable is not F1:
+    ...         raise zope.interface.Invalid()
+    >>> pre.factory = prefactory
+
+
+    >>> class IContainer(zope.interface.Interface):
+    ...     def __setitem__(name, object):
+    ...         pass
+    ...     __setitem__.precondition = pre
+
+
+    >>> class Container(object):
+    ...     zope.interface.implements(IContainer)
+
+    >>> from zope.component.factory import Factory
+    >>> ztapi.provideUtility(IFactory, Factory(F1), 'f1')
+    >>> ztapi.provideUtility(IFactory, Factory(F2), 'f2')
+
+    >>> from zmi.core.container.adding import Adding
+    >>> adding = Adding(Container(), TestRequest())
+    >>> items = adding.addingInfo()
+    >>> len(items)
+    1
+
+    The isSingleMenuItem will return True if there is one single content
+    that can be added inside the Container
+
+    >>> adding.isSingleMenuItem()
+    True
+
+    hasCustomAddView will return False as the content does not have
+    a custom Add View
+
+    >>> adding.hasCustomAddView()
+    False
+
+    """
+
+def test_isSingleMenuItem_with_ICNC():
+    """
+    This test checks for whether there is a single content that can be added
+    and the container uses IContainerNamesContaienr
+
+    >>> registerAddMenu()
+    >>> defineMenuItem(AddMenu, None, '', 'item3', extra={'factory': ''})
+
+    >>> class F1(object):
+    ...     pass
+
+    >>> class F2(object):
+    ...     pass
+
+    >>> def pre(container, name, object):
+    ...     if not isinstance(object, F1):
+    ...         raise zope.interface.Invalid()
+    >>> def prefactory(container, name, factory):
+    ...     if factory._callable is not F1:
+    ...         raise zope.interface.Invalid()
+    >>> pre.factory = prefactory
+
+
+    >>> class IContainer(zope.interface.Interface):
+    ...     def __setitem__(name, object):
+    ...         pass
+    ...     __setitem__.precondition = pre
+
+
+    >>> class Container(object):
+    ...     zope.interface.implements(IContainer, IContainerNamesContainer)
+
+    >>> from zmi.core.container.adding import Adding
+    >>> adding = Adding(Container(), TestRequest())
+    >>> items = adding.addingInfo()
+    >>> len(items)
+    1
+    >>> adding.isSingleMenuItem()
+    True
+    >>> adding.hasCustomAddView()
+    False
+
+    """
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(Test),
+        DocTestSuite(setUp=setUp, tearDown=tearDown),
+        ))
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')

Added: zmi.core/trunk/src/zmi/core/container/tests/test_contents.py
===================================================================
--- zmi.core/trunk/src/zmi/core/container/tests/test_contents.py	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/tests/test_contents.py	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,380 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 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.
+#
+##############################################################################
+"""Test Container Contents
+
+$Id: test_contents.py 81660 2007-11-09 19:28:02Z philikon $
+"""
+from unittest import TestCase, TestSuite, main, makeSuite
+
+from zope.interface import Interface, implements
+from zope.security import checker
+from zope.traversing.api import traverse
+
+from zope.component.eventtesting import getEvents
+
+from zope.annotation.interfaces import IAnnotations
+from zope.copypastemove import ContainerItemRenamer
+from zope.copypastemove import ObjectMover, ObjectCopier
+from zope.copypastemove import PrincipalClipboard
+from zope.copypastemove.interfaces import IContainerItemRenamer
+from zope.copypastemove.interfaces import IObjectMover, IObjectCopier
+from zope.copypastemove.interfaces import IPrincipalClipboard
+
+from zope.app.component.testing import PlacefulSetup
+from zope.app.container.contained import contained
+from zope.app.testing import ztapi
+from zope.app.container.interfaces import IContainer, IContained
+
+
+class BaseTestContentsBrowserView(PlacefulSetup):
+    """Base class for testing browser contents.
+
+    Subclasses need to define a method, '_TestView__newContext', that
+    takes no arguments and that returns a new empty test view context.
+
+    Subclasses need to define a method, '_TestView__newView', that
+    takes a context object and that returns a new test view.
+    """
+
+    def setUp(self):
+        PlacefulSetup.setUp(self)
+        PlacefulSetup.buildFolders(self)
+
+        ztapi.provideAdapter(IContained, IObjectCopier, ObjectCopier)
+        ztapi.provideAdapter(IContained, IObjectMover, ObjectMover)
+        ztapi.provideAdapter(IContainer, IContainerItemRenamer,
+            ContainerItemRenamer)
+
+        ztapi.provideAdapter(IAnnotations, IPrincipalClipboard,
+                             PrincipalClipboard)
+        ztapi.provideAdapter(Principal, IAnnotations,
+                             PrincipalAnnotations)
+
+    def testInfo(self):
+        # Do we get the correct information back from ContainerContents?
+        container = self._TestView__newContext()
+        subcontainer = self._TestView__newContext()
+        container['subcontainer'] = subcontainer
+        document = Document()
+        container['document'] = document
+
+        fc = self._TestView__newView(container)
+        info_list = fc.listContentInfo()
+
+        self.assertEquals(len(info_list), 2)
+
+        ids = map(lambda x: x['id'], info_list)
+        self.assert_('subcontainer' in ids)
+
+        objects = map(lambda x: x['object'], info_list)
+        self.assert_(subcontainer in objects)
+
+        urls = map(lambda x: x['url'], info_list)
+        self.assert_('subcontainer' in urls)
+
+        self.failIf(filter(None, map(lambda x: x['icon'], info_list)))
+
+    def testInfoUnicode(self):
+        # If the id contains non-ASCII characters, url has to be quoted
+        container = self._TestView__newContext()
+        subcontainer = self._TestView__newContext()
+        container[u'f\xf6\xf6'] = subcontainer
+
+        fc = self._TestView__newView(container)
+        info_list = fc.listContentInfo()
+
+        urls = map(lambda x: x['url'], info_list)
+        self.assert_('f%C3%B6%C3%B6' in urls)
+
+    def testInfoWDublinCore(self):
+        container = self._TestView__newContext()
+        document = Document()
+        container['document'] = document
+
+        from datetime import datetime
+        from zope.dublincore.interfaces import IZopeDublinCore
+        class FauxDCAdapter(object):
+            implements(IZopeDublinCore)
+
+            __Security_checker__ = checker.Checker(
+                {"created": "zope.Public",
+                 "modified": "zope.Public",
+                 "title": "zope.Public",
+                 },
+                {"title": "zope.app.dublincore.change"})
+
+            def __init__(self, context):
+                pass
+            title = 'faux title'
+            size = 1024
+            created = datetime(2001, 1, 1, 1, 1, 1)
+            modified = datetime(2002, 2, 2, 2, 2, 2)
+
+        ztapi.provideAdapter(IDocument, IZopeDublinCore, FauxDCAdapter)
+
+        fc = self._TestView__newView(container)
+        info = fc.listContentInfo()[0]
+
+        self.assertEqual(info['id'], 'document')
+        self.assertEqual(info['url'], 'document')
+        self.assertEqual(info['object'], document)
+        self.assertEqual(info['title'], 'faux title')
+        self.assertEqual(info['created'], '01/01/01 01:01')
+        self.assertEqual(info['modified'], '02/02/02 02:02')
+
+    def testRemove(self):
+        container = self._TestView__newContext()
+        subcontainer = self._TestView__newContext()
+        container['subcontainer'] = subcontainer
+        document = Document()
+        container['document'] = document
+        document2 = Document()
+        container['document2'] = document2
+
+        fc = self._TestView__newView(container)
+
+        fc.request.form.update({'ids': ['document2']})
+
+        fc.removeObjects()
+
+        info_list = fc.listContentInfo()
+
+        self.assertEquals(len(info_list), 2)
+
+        ids = map(lambda x: x['id'], info_list)
+        self.assert_('subcontainer' in ids)
+
+        objects = map(lambda x: x['object'], info_list)
+        self.assert_(subcontainer in objects)
+
+        urls = map(lambda x: x['url'], info_list)
+        self.assert_('subcontainer' in urls)
+
+    def testChangeTitle(self):
+        container = self._TestView__newContext()
+        document = Document()
+        container['document'] = document
+
+        from zope.dublincore.interfaces import IDCDescriptiveProperties
+        class FauxDCDescriptiveProperties(object):
+            implements(IDCDescriptiveProperties)
+
+            __Security_checker__ = checker.Checker(
+                {"title": "zope.Public",
+                 },
+                {"title": "zope.app.dublincore.change"})
+
+            def __init__(self, context):
+                self.context = context
+
+            def setTitle(self, title):
+                self.context.title = title
+                
+            def getTitle(self):
+                return self.context.title
+                
+            title = property(getTitle, setTitle)
+
+        ztapi.provideAdapter(IDocument, IDCDescriptiveProperties, FauxDCDescriptiveProperties)
+        
+        fc = self._TestView__newView(container)
+
+        dc = IDCDescriptiveProperties(document)
+        
+        fc.request.form.update({'retitle_id': 'document', 'new_value': 'new'})
+        fc.changeTitle()
+        events = getEvents()
+        self.assertEquals(dc.title, 'new')
+        self.failIf('title' not in events[-1].descriptions[0].attributes)
+
+
+
+class IDocument(Interface):
+    pass
+
+class Document(object):
+    implements(IDocument)
+
+
+class Principal(object):
+
+    id = 'bob'
+
+class PrincipalAnnotations(dict):
+    implements(IAnnotations)
+    data = {}
+    def __new__(class_, context):
+        try:
+            annotations = class_.data[context.id]
+        except KeyError:
+            annotations = dict.__new__(class_)
+            class_.data[context.id] = annotations
+        return annotations
+    def __init__(self, context):
+        pass
+    def __repr__(self):
+        return "<%s.PrincipalAnnotations object>" % __name__
+
+
+class TestCutCopyPaste(PlacefulSetup, TestCase):
+
+    def setUp(self):
+        PlacefulSetup.setUp(self)
+        PlacefulSetup.buildFolders(self)
+        ztapi.provideAdapter(IContained, IObjectCopier, ObjectCopier)
+        ztapi.provideAdapter(IContained, IObjectMover, ObjectMover)
+        ztapi.provideAdapter(IContainer, IContainerItemRenamer,
+            ContainerItemRenamer)
+
+        ztapi.provideAdapter(IAnnotations, IPrincipalClipboard,
+                             PrincipalClipboard)
+        ztapi.provideAdapter(Principal, IAnnotations,
+                             PrincipalAnnotations)
+
+    def testRename(self):
+        container = traverse(self.rootFolder, 'folder1')
+        fc = self._TestView__newView(container)
+        ids=['document1', 'document2']
+        for id in ids:
+            document = Document()
+            container[id] = document
+        fc.request.form.update({'rename_ids': ids,
+                                'new_value': ['document1_1', 'document2_2']
+                                })
+        fc.renameObjects()
+        self.failIf('document1_1' not in container)
+        self.failIf('document1' in container)
+
+    def testCopyPaste(self):
+        container = traverse(self.rootFolder, 'folder1')
+        fc = self._TestView__newView(container)
+        ids=['document1', 'document2']
+        for id in ids:
+            document = Document()
+            container[id] = document
+
+        fc.request.form['ids'] = ids
+        fc.copyObjects()
+        fc.pasteObjects()
+        self.failIf('document1' not in container)
+        self.failIf('document2' not in container)
+        self.failIf('document1-2' not in container)
+        self.failIf('document2-2' not in container)
+
+    def testCopyFolder(self):
+        container = traverse(self.rootFolder, 'folder1')
+        fc = self._TestView__newView(container)
+        ids = ['folder1_1']
+        fc.request.form['ids'] = ids
+        fc.copyObjects()
+        fc.pasteObjects()
+        self.failIf('folder1_1' not in container)
+        self.failIf('folder1_1-2' not in container)
+
+    def testCopyFolder2(self):
+        container = traverse(self.rootFolder, '/folder1/folder1_1')
+        fc = self._TestView__newView(container)
+        ids = ['folder1_1_1']
+        fc.request.form['ids'] = ids
+        fc.copyObjects()
+        fc.pasteObjects()
+        self.failIf('folder1_1_1' not in container)
+        self.failIf('folder1_1_1-2' not in container)
+
+    def testCopyFolder3(self):
+        container = traverse(self.rootFolder, '/folder1/folder1_1')
+        target = traverse(self.rootFolder, '/folder2/folder2_1')
+        fc = self._TestView__newView(container)
+        tg = self._TestView__newView(target)
+        ids = ['folder1_1_1']
+        fc.request.form['ids'] = ids
+        fc.copyObjects()
+        tg.pasteObjects()
+        self.failIf('folder1_1_1' not in container)
+        self.failIf('folder1_1_1' not in target)
+
+    def testCutPaste(self):
+        container = traverse(self.rootFolder, 'folder1')
+        fc = self._TestView__newView(container)
+        ids=['document1', 'document2']
+        for id in ids:
+            document = Document()
+            container[id] = document
+        fc.request.form['ids'] = ids
+        fc.cutObjects()
+        fc.pasteObjects()
+        self.failIf('document1' not in container)
+        self.failIf('document2' not in container)
+
+    def testCutFolder(self):
+        container = traverse(self.rootFolder, 'folder1')
+        fc = self._TestView__newView(container)
+        ids = ['folder1_1']
+        fc.request.form['ids'] = ids
+        fc.cutObjects()
+        fc.pasteObjects()
+        self.failIf('folder1_1' not in container)
+
+    def testCutFolder2(self):
+        container = traverse(self.rootFolder, '/folder1/folder1_1')
+        fc = self._TestView__newView(container)
+        ids = ['folder1_1_1']
+        fc.request.form['ids'] = ids
+        fc.cutObjects()
+        fc.pasteObjects()
+        self.failIf('folder1_1_1' not in container)
+
+    def testCutFolder3(self):
+        container = traverse(self.rootFolder, '/folder1/folder1_1')
+        target = traverse(self.rootFolder, '/folder2/folder2_1')
+        fc = self._TestView__newView(container)
+        tg = self._TestView__newView(target)
+        ids = ['folder1_1_1']
+        fc.request.form['ids'] = ids
+        fc.cutObjects()
+        tg.pasteObjects()
+        self.failIf('folder1_1_1' in container)
+        self.failIf('folder1_1_1' not in target)
+
+    def _TestView__newView(self, container):
+        from zope.app.container.browser.contents import Contents
+        from zope.publisher.browser import TestRequest
+        request = TestRequest()
+        request.setPrincipal(Principal())
+        return Contents(container, request)
+
+class Test(BaseTestContentsBrowserView, TestCase):
+
+    def _TestView__newContext(self):
+        from zope.app.container.sample import SampleContainer
+        from zope.app.folder import rootFolder
+        root = rootFolder()
+        container = SampleContainer()
+        return contained(container, root, 'sample')
+
+    def _TestView__newView(self, container):
+        from zope.app.container.browser.contents import Contents
+        from zope.publisher.browser import TestRequest
+        request = TestRequest()
+        request.setPrincipal(Principal())
+        return Contents(container, request)
+
+def test_suite():
+    return TestSuite((
+        makeSuite(Test),
+        makeSuite(TestCutCopyPaste),
+        ))
+
+if __name__=='__main__':
+    main(defaultTest='test_suite')

Added: zmi.core/trunk/src/zmi/core/container/tests/test_contents_functional.py
===================================================================
--- zmi.core/trunk/src/zmi/core/container/tests/test_contents_functional.py	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/tests/test_contents_functional.py	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,380 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 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.
+#
+##############################################################################
+"""Functional tests for the Container's 'Contents' view
+
+$Id: test_contents_functional.py 87310 2008-06-11 09:06:34Z ccomb $
+"""
+
+import unittest
+import os
+
+from persistent import Persistent
+import transaction
+from zope import copypastemove
+from zope.interface import implements, Interface
+from zope.annotation.interfaces import IAttributeAnnotatable
+from zope.dublincore.interfaces import IZopeDublinCore
+
+from zope.app.container.interfaces import IReadContainer, IContained
+from zope.app.testing import ztapi
+from zope.app.testing.functional import BrowserTestCase
+from zope.app.testing.functional import FunctionalDocFileSuite
+from zope.app.testing.functional import ZCMLLayer
+
+ZMICoreContainerLayer = ZCMLLayer(
+    os.path.join(os.path.split(__file__)[0], 'ftesting.zcml'),
+    __name__, 'ZMICoreContainerLayer', allow_teardown=True)
+
+
+class IImmovable(Interface):
+    """Marker interface for immovable objects."""
+
+class IUncopyable(Interface):
+    """Marker interface for uncopyable objects."""
+
+class File(Persistent):
+    implements(IAttributeAnnotatable)
+
+class ImmovableFile(File):
+    implements(IImmovable)
+
+class UncopyableFile(File):
+    implements(IUncopyable)
+
+class ObjectNonCopier(copypastemove.ObjectCopier):
+
+    def copyable(self):
+        return False
+
+class ObjectNonMover(copypastemove.ObjectMover):
+
+    def moveable(self):
+        return False
+
+class ReadOnlyContainer(Persistent):
+    implements(IReadContainer, IContained)
+    __parent__ = __name__ = None
+
+    def __init__(self): self.data = {}
+    def keys(self): return self.data.keys()
+    def __getitem__(self, key): return self.data[key]
+    def get(self, key, default=None): return self.data.get(key, default)
+    def __iter__(self): return iter(self.data)
+    def values(self): return self.data.values()
+    def __len__(self): return len(self.data)
+    def items(self): return self.data.items()
+    def __contains__(self, key): return key in self.data
+    def has_key(self, key): return self.data.has_key(key)
+
+
+class Test(BrowserTestCase):
+
+    def test_inplace_add(self):
+        root = self.getRootFolder()
+        self.assert_('foo' not in root)
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'type_name': u'zope.app.content.File'})
+        body = ' '.join(response.getBody().split())
+        self.assert_(body.find('type="hidden" name="type_name"') >= 0)
+        self.assert_(body.find('input name="new_value"') >= 0)
+        self.assert_(body.find('type="submit" name="container_cancel_button"')
+                     >= 0)
+        self.assert_(body.find('type="submit" name="container_rename_button"')
+                     < 0)
+
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'type_name': u'zope.app.content.File',
+                                      'new_value': 'foo'})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/@@contents.html')
+
+        root._p_jar.sync()
+        self.assert_('foo' in root)
+
+    def test_inplace_rename_multiple(self):
+        root = self.getRootFolder()
+        root['foo'] = File()
+        self.assert_('foo' in root)
+        transaction.commit()
+
+        # Check that we don't change mode if there are no items selected
+        
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'container_rename_button': u''})
+        body = ' '.join(response.getBody().split())
+        self.assert_(body.find('input name="new_value:list"') < 0)
+        self.assert_(body.find('type="submit" name="container_cancel_button"')
+                     < 0)
+        self.assert_(body.find('type="submit" name="container_rename_button"')
+                     >= 0)
+        self.assert_(body.find('div class="page_error"')
+                     >= 0)
+
+
+        # Check normal multiple select
+
+        
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'container_rename_button': u'',
+                                      'ids': ['foo']})
+        body = ' '.join(response.getBody().split())
+        self.assert_(body.find('input name="new_value:list"') >= 0)
+        self.assert_(body.find('type="submit" name="container_cancel_button"')
+                     >= 0)
+        self.assert_(body.find('type="submit" name="container_rename_button"')
+                     < 0)
+
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'rename_ids': ['foo'],
+                                      'new_value': ['bar']})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/@@contents.html')
+
+        root._p_jar.sync()
+        self.assert_('foo' not in root)
+        self.assert_('bar' in root)
+
+
+    def test_inplace_rename_single(self):
+        root = self.getRootFolder()
+        root['foo'] = File()
+        self.assert_('foo' in root)
+        transaction.commit()
+        
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'rename_ids': ['foo']})
+        body = ' '.join(response.getBody().split())
+        self.assert_(body.find('input name="new_value:list"') >= 0)
+        self.assert_(body.find('type="submit" name="container_cancel_button"')
+                     >= 0)
+        self.assert_(body.find('type="submit" name="container_rename_button"')
+                     < 0)
+
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'rename_ids': ['foo'],
+                                      'new_value': ['bar']})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/@@contents.html')
+
+        root._p_jar.sync()
+        self.assert_('foo' not in root)
+        self.assert_('bar' in root)
+
+    def test_inplace_change_title(self):
+        root = self.getRootFolder()
+        root['foo'] = File()
+        transaction.commit()
+        self.assert_('foo' in root)
+        dc = IZopeDublinCore(root['foo'])
+        self.assert_(dc.title == '')
+
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'retitle_id': u'foo'})
+        body = ' '.join(response.getBody().split())
+        self.assert_(body.find('type="hidden" name="retitle_id"') >= 0)
+        self.assert_(body.find('input name="new_value"') >= 0)
+        self.assert_(body.find('type="submit" name="container_cancel_button"')
+                     >= 0)
+        self.assert_(body.find('type="submit" name="container_rename_button"')
+                     < 0)
+
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'retitle_id': u'foo',
+                                      'new_value': u'test title'})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/@@contents.html')
+
+        root._p_jar.sync()
+        self.assert_('foo' in root)
+        dc = IZopeDublinCore(root['foo'])
+        self.assert_(dc.title == 'test title')
+
+
+    def test_pasteable_for_deleted_clipboard_item(self):
+        """Tests Paste button visibility when copied item is deleted."""
+
+        root = self.getRootFolder()
+        root['foo'] = File()    # item to be copied/deleted
+        root['bar'] = File()    # ensures that there's always an item in
+                                # the collection view
+        transaction.commit()
+
+        # confirm foo in contents, Copy button visible, Paste not visible
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(response.getBody().find(
+            '<a href="foo/@@SelectedManagementView.html">foo</a>') != -1)
+        self.assert_(response.getBody().find(
+            '<input type="submit" name="container_copy_button"') != -1)
+        self.assert_(response.getBody().find(
+            '<input type="submit" name="container_paste_button"') == -1)
+
+        # copy foo - confirm Paste visible
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw', form={
+            'ids' : ('foo',),
+            'container_copy_button' : '' })
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+            'http://localhost/@@contents.html')
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(response.getBody().find(
+            '<input type="submit" name="container_paste_button"') != -1)
+
+        # delete foo -> nothing valid to paste -> Paste should not be visible
+        del root['foo']
+        transaction.commit()
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(response.getBody().find(
+            '<input type="submit" name="container_paste_button"') == -1)      
+
+
+    def test_paste_for_deleted_clipboard_item(self):
+        """Tests paste operation when one of two copied items is deleted."""
+
+        root = self.getRootFolder()
+        root['foo'] = File()
+        root['bar'] = File()
+        transaction.commit()
+
+        # confirm foo/bar in contents, Copy button visible, Paste not visible
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(response.getBody().find(
+            '<a href="foo/@@SelectedManagementView.html">foo</a>') != -1)
+        self.assert_(response.getBody().find(
+            '<a href="bar/@@SelectedManagementView.html">bar</a>') != -1)
+        self.assert_(response.getBody().find(
+            '<input type="submit" name="container_copy_button"') != -1)
+        self.assert_(response.getBody().find(
+            '<input type="submit" name="container_paste_button"') == -1)
+
+        # copy foo and bar - confirm Paste visible
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw', form={
+            'ids' : ('foo', 'bar'),
+            'container_copy_button' : '' })
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+            'http://localhost/@@contents.html')
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(response.getBody().find(
+            '<input type="submit" name="container_paste_button"') != -1)
+
+        # delete only foo -> bar still available -> Paste should be visible
+        del root['foo']
+        transaction.commit()
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(response.getBody().find(
+            '<input type="submit" name="container_paste_button"') != -1)
+
+        # paste clipboard contents - only bar should be copied
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw', form={
+            'container_paste_button' : '' })
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+            'http://localhost/@@contents.html')
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+        root._p_jar.sync()
+        self.assertEqual(tuple(root.keys()), ('bar', 'bar-2'))
+
+    def test_readonly_display(self):
+        root = self.getRootFolder()
+        root['foo'] = ReadOnlyContainer()
+        transaction.commit()
+        response = self.publish('/foo/@@contents.html', basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+
+    def test_uncopyable_object(self):
+        ztapi.provideAdapter(IUncopyable,
+                             copypastemove.interfaces.IObjectCopier,
+                             ObjectNonCopier)
+        root = self.getRootFolder()
+        root['uncopyable'] = UncopyableFile()
+        transaction.commit()
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'ids': [u'uncopyable'],
+                                      'container_copy_button': u'Copy'})
+        self.assertEqual(response.getStatus(), 200)
+        body = response.getBody()
+        self.assert_("cannot be copied" in body)
+
+    def test_unmoveable_object(self):
+        ztapi.provideAdapter(IImmovable,
+                             copypastemove.interfaces.IObjectMover,
+                             ObjectNonMover)
+        root = self.getRootFolder()
+        root['immovable'] = ImmovableFile()
+        transaction.commit()
+        response = self.publish('/@@contents.html',
+                                basic='mgr:mgrpw',
+                                form={'ids': [u'immovable'],
+                                      'container_cut_button': u'Cut'})
+        self.assertEqual(response.getStatus(), 200)
+        body = response.getBody()
+        self.assert_("cannot be moved" in body)
+
+    def test_copy_then_delete_with_unicode_name(self):
+        """Tests unicode on object copied then deleted (#238579)."""
+
+        # create a file with an accentuated unicode name
+        root = self.getRootFolder()
+        root[u'voil\xe0'] = File()
+        transaction.commit()
+
+        # copy the object
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw', form={
+            'ids' : (u'voil\xe0',),
+            'container_copy_button' : '' })
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+            'http://localhost/@@contents.html')
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+
+        # delete the object
+        del root[u'voil\xe0']
+        transaction.commit()
+        response = self.publish('/@@contents.html', basic='mgr:mgrpw')
+        self.assertEqual(response.getStatus(), 200)
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = ZMICoreContainerLayer
+    suite.addTest(unittest.makeSuite(Test))
+    index = FunctionalDocFileSuite("index.txt")
+    index.layer = ZMICoreContainerLayer
+    suite.addTest(index)
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')

Added: zmi.core/trunk/src/zmi/core/container/tests/test_directive.py
===================================================================
--- zmi.core/trunk/src/zmi/core/container/tests/test_directive.py	                        (rev 0)
+++ zmi.core/trunk/src/zmi/core/container/tests/test_directive.py	2009-06-07 15:25:08 UTC (rev 100691)
@@ -0,0 +1,294 @@
+##############################################################################
+#
+# Copyright (c) 2003 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.
+#
+##############################################################################
+"""'containerView' directive test
+
+$Id: test_directive.py 67630 2006-04-27 00:54:03Z jim $
+"""
+import re
+import pprint
+import cStringIO
+
+import unittest
+from zope.interface import Interface
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.testing.doctestunit import DocTestSuite
+from zope.app.container.browser.metaconfigure import containerViews
+
+atre = re.compile(' at [0-9a-fA-Fx]+')
+
+class Context(object):
+    actions = ()
+    info = ''
+    
+    def action(self, discriminator, callable, args):
+        self.actions += ((discriminator, callable, args), )
+        self.info = 'info'
+
+    def __repr__(self):
+        stream = cStringIO.StringIO()
+        pprinter = pprint.PrettyPrinter(stream=stream, width=60)
+        pprinter.pprint(self.actions)
+        r = stream.getvalue()
+        return (''.join(atre.split(r))).strip()
+
+class I(Interface):
+    pass
+
+
+class ITestLayer(IBrowserRequest):
+    pass
+
+
+def test_containerViews():
+    """
+    >>> from zope.app.publisher.browser.menumeta import menus
+    >>> from zope.interface.interface import InterfaceClass
+    >>> zmi_views = InterfaceClass('zmi_views', __module__='zope.app.menus')
+    >>> menus.zmi_views = zmi_views
+    >>> zmi_actions = InterfaceClass('zmi_actions', __module__='zope.app.menus')
+    >>> menus.zmi_actions = zmi_actions
+
+    >>> context = Context()
+    >>> containerViews(context, for_=I, contents='zope.ManageContent',
+    ...                add='zope.ManageContent', index='zope.View')
+    >>> context
+    ((('adapter',
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
+       <InterfaceClass zope.app.menus.zmi_views>,
+       u'Contents'),
+      <function handler>,
+      ('registerAdapter',
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
+       <InterfaceClass zope.app.menus.zmi_views>,
+       u'Contents',
+       '')),
+     (None,
+      <function provideInterface>,
+      ('', <InterfaceClass zope.app.menus.zmi_views>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>)),
+     (('view',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>,
+       'contents.html',
+       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>,
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
+      <function handler>,
+      ('registerAdapter',
+       <class 'zope.app.publisher.browser.viewmeta.Contents'>,
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
+       <InterfaceClass zope.interface.Interface>,
+       'contents.html',
+       'info')),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>)),
+     (('view',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>,
+       'index.html',
+       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>,
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
+      <function handler>,
+      ('registerAdapter',
+       <class 'zope.app.publisher.browser.viewmeta.Contents'>,
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
+       <InterfaceClass zope.interface.Interface>,
+       'index.html',
+       'info')),
+     (('adapter',
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
+       <InterfaceClass zope.app.menus.zmi_actions>,
+       u'Add'),
+      <function handler>,
+      ('registerAdapter',
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
+       <InterfaceClass zope.app.menus.zmi_actions>,
+       u'Add',
+       'info')),
+     (None,
+      <function provideInterface>,
+      ('', <InterfaceClass zope.app.menus.zmi_actions>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>)),
+     (None,
+      <function provideInterface>,
+      ('', <InterfaceClass zope.interface.Interface>)),
+     (('view',
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
+       '+',
+       <InterfaceClass zope.interface.Interface>),
+      <function handler>,
+      ('registerAdapter',
+       <class 'zope.app.publisher.browser.viewmeta.+'>,
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
+       <InterfaceClass zope.interface.Interface>,
+       '+',
+       'info')))
+    """
+
+def test_containerViews_layer():
+    """
+    >>> from zope.app.publisher.browser.menumeta import menus
+    >>> from zope.interface.interface import InterfaceClass
+    >>> zmi_views = InterfaceClass('zmi_views', __module__='zope.app.menus')
+    >>> menus.zmi_views = zmi_views
+    >>> zmi_actions = InterfaceClass('zmi_actions', __module__='zope.app.menus')
+    >>> menus.zmi_actions = zmi_actions
+
+    >>> context = Context()
+    >>> containerViews(context, for_=I, contents='zope.ManageContent',
+    ...                add='zope.ManageContent', index='zope.View', layer=ITestLayer)
+    >>> context
+    ((('adapter',
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>),
+       <InterfaceClass zope.app.menus.zmi_views>,
+       u'Contents'),
+      <function handler>,
+      ('registerAdapter',
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>),
+       <InterfaceClass zope.app.menus.zmi_views>,
+       u'Contents',
+       '')),
+     (None,
+      <function provideInterface>,
+      ('', <InterfaceClass zope.app.menus.zmi_views>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>)),
+     (('view',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>,
+       'contents.html',
+       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>,
+       <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>),
+      <function handler>,
+      ('registerAdapter',
+       <class 'zope.app.publisher.browser.viewmeta.Contents'>,
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>),
+       <InterfaceClass zope.interface.Interface>,
+       'contents.html',
+       'info')),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>)),
+     (('view',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>,
+       'index.html',
+       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>,
+       <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>),
+      <function handler>,
+      ('registerAdapter',
+       <class 'zope.app.publisher.browser.viewmeta.Contents'>,
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>),
+       <InterfaceClass zope.interface.Interface>,
+       'index.html',
+       'info')),
+     (('adapter',
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>),
+       <InterfaceClass zope.app.menus.zmi_actions>,
+       u'Add'),
+      <function handler>,
+      ('registerAdapter',
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>),
+       <InterfaceClass zope.app.menus.zmi_actions>,
+       u'Add',
+       'info')),
+     (None,
+      <function provideInterface>,
+      ('', <InterfaceClass zope.app.menus.zmi_actions>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zmi.core.container.tests.test_directive.I>)),
+     (None,
+      <function provideInterface>,
+      ('', <InterfaceClass zope.interface.Interface>)),
+     (('view',
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>),
+       '+',
+       <InterfaceClass zope.interface.Interface>),
+      <function handler>,
+      ('registerAdapter',
+       <class 'zope.app.publisher.browser.viewmeta.+'>,
+       (<InterfaceClass zmi.core.container.tests.test_directive.I>,
+        <InterfaceClass zmi.core.container.tests.test_directive.ITestLayer>),
+       <InterfaceClass zope.interface.Interface>,
+       '+',
+       'info')))
+    """
+
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite(),
+        ))
+
+if __name__ == '__main__':
+    unittest.main()



More information about the Checkins mailing list