[Checkins] SVN: z3c.contents/ Move implementation form private repos to z3c

Roger Ineichen roger at projekt01.ch
Wed Feb 13 06:59:18 EST 2008


Log message for revision 83793:
  Move implementation form private repos to z3c

Changed:
  A   z3c.contents/branches/
  A   z3c.contents/tags/
  A   z3c.contents/trunk/
  A   z3c.contents/trunk/AUTHOR.txt
  A   z3c.contents/trunk/CHANGES.txt
  A   z3c.contents/trunk/LICENSE.txt
  A   z3c.contents/trunk/README.txt
  A   z3c.contents/trunk/TODO.txt
  A   z3c.contents/trunk/bootstrap.py
  A   z3c.contents/trunk/buildout.cfg
  A   z3c.contents/trunk/externals/
  A   z3c.contents/trunk/setup.py
  A   z3c.contents/trunk/src/
  A   z3c.contents/trunk/src/z3c/
  A   z3c.contents/trunk/src/z3c/__init__.py
  A   z3c.contents/trunk/src/z3c/contents/
  A   z3c.contents/trunk/src/z3c/contents/README.txt
  A   z3c.contents/trunk/src/z3c/contents/__init__.py
  A   z3c.contents/trunk/src/z3c/contents/browser.py
  A   z3c.contents/trunk/src/z3c/contents/column.py
  A   z3c.contents/trunk/src/z3c/contents/contents.pt
  A   z3c.contents/trunk/src/z3c/contents/interfaces.py
  A   z3c.contents/trunk/src/z3c/contents/testing.py
  A   z3c.contents/trunk/src/z3c/contents/tests.py

-=-
Added: z3c.contents/trunk/AUTHOR.txt
===================================================================
--- z3c.contents/trunk/AUTHOR.txt	                        (rev 0)
+++ z3c.contents/trunk/AUTHOR.txt	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1 @@
+Roger Ineichen (roger <at> projekt01 <dot> ch)


Property changes on: z3c.contents/trunk/AUTHOR.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/CHANGES.txt
===================================================================
--- z3c.contents/trunk/CHANGES.txt	                        (rev 0)
+++ z3c.contents/trunk/CHANGES.txt	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,8 @@
+=======
+CHANGES
+=======
+
+Version 0.5.0 (unreleased)
+--------------------------
+
+- Initial Release


Property changes on: z3c.contents/trunk/CHANGES.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/LICENSE.txt
===================================================================
--- z3c.contents/trunk/LICENSE.txt	                        (rev 0)
+++ z3c.contents/trunk/LICENSE.txt	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,54 @@
+Zope Public License (ZPL) Version 2.1
+-------------------------------------
+
+A copyright notice accompanies this license document that
+identifies the copyright holders.
+
+This license has been certified as open source. It has also
+been designated as GPL compatible by the Free Software
+Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions in source code must retain the
+   accompanying copyright notice, this list of conditions,
+   and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the accompanying
+   copyright notice, this list of conditions, and the
+   following disclaimer in the documentation and/or other
+   materials provided with the distribution.
+
+3. Names of the copyright holders must not be used to
+   endorse or promote products derived from this software
+   without prior written permission from the copyright
+   holders.
+
+4. The right to distribute this software or to use it for
+   any purpose does not give you the right to use
+   Servicemarks (sm) or Trademarks (tm) of the copyright
+   holders. Use of them is covered by separate agreement
+   with the copyright holders.
+
+5. If any files are modified, you must cause the modified
+   files to carry prominent notices stating that you changed
+   the files and the date of any change.
+
+Disclaimer
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
+  AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+  NO EVENT SHALL THE COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+  DAMAGE.


Property changes on: z3c.contents/trunk/LICENSE.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/README.txt
===================================================================
--- z3c.contents/trunk/README.txt	                        (rev 0)
+++ z3c.contents/trunk/README.txt	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,2 @@
+This package provides a contents.html page replacement for Zope3 based on 
+z3c.form and z3c.table.


Property changes on: z3c.contents/trunk/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/TODO.txt
===================================================================
--- z3c.contents/trunk/TODO.txt	                        (rev 0)
+++ z3c.contents/trunk/TODO.txt	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,7 @@
+====
+TODO
+====
+
+- implement functional doc tests with real traversable contents.html view
+
+- implement batch and test with more items


Property changes on: z3c.contents/trunk/TODO.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/bootstrap.py
===================================================================
--- z3c.contents/trunk/bootstrap.py	                        (rev 0)
+++ z3c.contents/trunk/bootstrap.py	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2008 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.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id:$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                     ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)


Property changes on: z3c.contents/trunk/bootstrap.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/buildout.cfg
===================================================================
--- z3c.contents/trunk/buildout.cfg	                        (rev 0)
+++ z3c.contents/trunk/buildout.cfg	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,20 @@
+[buildout]
+develop = .
+          externals/z3c.batching
+          externals/z3c.table
+parts = test checker coverage
+
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.contents [test]
+
+
+[checker]
+recipe = lovely.recipe:importchecker
+path = src/z3c/contents
+
+
+[coverage]
+recipe = zc.recipe.egg
+eggs = z3c.coverage

Added: z3c.contents/trunk/setup.py
===================================================================
--- z3c.contents/trunk/setup.py	                        (rev 0)
+++ z3c.contents/trunk/setup.py	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,72 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+
+import os
+from setuptools import setup, find_packages
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup (
+    name='z3c.contents',
+    version='0.5.0',
+    author = "Roger Ineichen and the Zope Community",
+    author_email = "zope3-dev at zope.org",
+    description = "Container management page based on z3c.form and z3c.table for Zope3",
+    long_description=(
+        read('README.txt')
+        + '\n\n' +
+        read('CHANGES.txt')
+        ),
+    license = "ZPL 2.1",
+    keywords = "zope3 z3c container manegement table contents",
+    classifiers = [
+        'Development Status :: 4 - Beta',
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Zope Public License',
+        'Programming Language :: Python',
+        'Natural Language :: English',
+        'Operating System :: OS Independent',
+        'Topic :: Internet :: WWW/HTTP',
+        'Framework :: Zope3'],
+    url = 'http://cheeseshop.python.org/pypi/z3c.contents',
+    packages = find_packages('src'),
+    include_package_data = True,
+    package_dir = {'':'src'},
+    namespace_packages = ['z3c'],
+    extras_require = dict(
+        test = [
+            'zope.testbrowser',
+            'zope.app.securitypolicy',
+            'zope.app.testing',
+            'zope.app.twisted',
+            'z3c.testing',
+            ],
+        ),
+    install_requires = [
+        'setuptools',
+        'z3c.form',
+        'z3c.formui',
+        'z3c.pagelet',
+        'z3c.table',
+        'z3c.template',
+        'zope.contentprovider',
+        'zope.interface',
+        ],
+    zip_safe = False,
+)
\ No newline at end of file


Property changes on: z3c.contents/trunk/setup.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/src/z3c/__init__.py
===================================================================
--- z3c.contents/trunk/src/z3c/__init__.py	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/__init__.py	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,6 @@
+try:
+    # Declare this a namespace package if pkg_resources is available.
+    import pkg_resources
+    pkg_resources.declare_namespace('z3c')
+except ImportError:
+    pass


Property changes on: z3c.contents/trunk/src/z3c/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/src/z3c/contents/README.txt
===================================================================
--- z3c.contents/trunk/src/z3c/contents/README.txt	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/README.txt	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,865 @@
+========
+Contents
+========
+
+The goal of this package is to offer a modular replacement for the default 
+contents.html page used in Zope3.
+
+
+Sample data setup
+-----------------
+
+Let's create a sample container which we can use as our context:
+
+  >>> import zope.app.container.interfaces
+  >>> class IContainer(zope.app.container.interfaces.IContainer):
+  ...     """Custom container marker use as discriminator."""
+
+  >>> import zope.interface
+  >>> from zope.app.container import btree
+  >>> class Container(btree.BTreeContainer):
+  ...     """Sample container."""
+  ... 
+  ...     zope.interface.implements(IContainer)
+  ... 
+  >>> container = Container()
+
+add them to the root:
+
+  >>> root['container'] = container
+
+Now setup some items based on our testing Content object. Note this object is
+defined in the testing module because it must be pickable because the object
+copier pickels objects during copy/paste:
+
+  >>> from z3c.contents.testing import Content
+  >>> container[u'zero'] = Content('Zero', 0)
+  >>> container[u'first'] = Content('First', 1)
+  >>> container[u'second'] = Content('Second', 2)
+  >>> container[u'third'] = Content('Third', 3)
+  >>> container[u'fourth'] = Content('Fourth', 4)
+
+And we need to setup the form defaults first:
+
+  >>> from z3c.form.testing import setupFormDefaults
+  >>> setupFormDefaults()
+
+And we need to configure our contents.pt template for the ContentsPage:
+
+  >>> import os
+  >>> import sys
+  >>> from zope.configuration import xmlconfig
+  >>> import z3c.template
+  >>> context = xmlconfig.file('meta.zcml', z3c.template)
+  >>> contentsTemplate = os.path.join(os.path.dirname(z3c.contents.__file__),
+  ...     'contents.pt')
+  >>> context = xmlconfig.string("""
+  ... <configure
+  ...     xmlns:z3c="http://namespaces.zope.org/z3c">
+  ...   <z3c:template
+  ...       for="z3c.contents.browser.ContentsPage"
+  ...       template="%s"
+  ...       />
+  ... </configure>
+  ... """ % contentsTemplate, context=context)
+
+
+And load the formui confguration, which will make sure that all macros get 
+registered correctly.
+
+  >>> from zope.configuration import xmlconfig
+  >>> import zope.component
+  >>> import zope.viewlet
+  >>> import zope.app.component
+  >>> import zope.app.publisher.browser
+  >>> import z3c.macro
+  >>> import z3c.template
+  >>> import z3c.formui
+  >>> xmlconfig.XMLConfig('meta.zcml', zope.component)()
+  >>> xmlconfig.XMLConfig('meta.zcml', zope.viewlet)()
+  >>> xmlconfig.XMLConfig('meta.zcml', zope.app.component)()
+  >>> xmlconfig.XMLConfig('meta.zcml', zope.app.publisher.browser)()
+  >>> xmlconfig.XMLConfig('meta.zcml', z3c.macro)()
+  >>> xmlconfig.XMLConfig('meta.zcml', z3c.template)()
+  >>> xmlconfig.XMLConfig('configure.zcml', z3c.formui)()
+
+And support the div form layer for our request:
+
+  >>> from z3c.formui.interfaces import IDivFormLayer
+  >>> from zope.interface import alsoProvides
+  >>> from z3c.form.testing import TestRequest
+  >>> request = TestRequest()
+  >>> alsoProvides(request, IDivFormLayer)
+
+
+ContentsPage
+------------
+
+Now we can create a ContentsPage:
+
+  >>> from z3c.contents import browser
+  >>> firstPage = browser.ContentsPage(container, request)
+  >>> firstPage.update()
+  >>> print firstPage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+    <div class="viewspace">
+        <div class="required-info">
+           <span class="required">*</span>
+           &ndash; required
+        </div>
+      <div>
+    </div>
+    </div>
+    <div>
+      <div class="buttons">
+        <input type="submit" id="contents-buttons-copy"
+         name="contents.buttons.copy"
+         class="submit-widget button-field" value="Copy" />
+        <input type="submit" id="contents-buttons-cut"
+         name="contents.buttons.cut"
+         class="submit-widget button-field" value="Cut" />
+        <input type="submit" id="contents-buttons-paste"
+         name="contents.buttons.paste"
+         class="submit-widget button-field" value="Paste" />
+        <input type="submit" id="contents-buttons-delete"
+         name="contents.buttons.delete"
+         class="submit-widget button-field" value="Delete" />
+        <input type="submit" id="contents-buttons-rename"
+         name="contents.buttons.rename"
+         class="submit-widget button-field" value="Rename" />
+      </div>
+    </div>
+  </form>
+
+
+Columns
+-------
+
+We register our predefined columns as adapters this allows us later to enhance 
+existing contents.html pages with additional columns. Use the adapter directive
+for this:
+
+  >>> import zope.component
+  >>> from z3c.table.interfaces import IColumn
+  >>> from z3c.contents import interfaces
+  >>> from z3c.table.column import CheckBoxColumn
+  >>> zope.component.provideAdapter(CheckBoxColumn,
+  ...     (IContainer, None, interfaces.IContentsPage),
+  ...      provides=IColumn, name='checkBoxColumn')
+
+  >>> from z3c.contents import column
+  >>> zope.component.provideAdapter(column.RenameColumn,
+  ...     (IContainer, None, interfaces.IContentsPage),
+  ...      provides=IColumn, name='renameColumn')
+
+  >>> from z3c.table.column import CreatedColumn
+  >>> zope.component.provideAdapter(CreatedColumn,
+  ...     (IContainer, None, interfaces.IContentsPage),
+  ...      provides=IColumn, name='createdColumn')
+
+  >>> from z3c.table.column import ModifiedColumn
+  >>> zope.component.provideAdapter(ModifiedColumn,
+  ...     (IContainer, None, interfaces.IContentsPage),
+  ...      provides=IColumn, name='modifiedColumn')
+
+Now let's update and render the contents page again:
+
+  >>> firstPage.update()
+  >>> print firstPage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+    <div class="viewspace">
+        <div class="required-info">
+           <span class="required">*</span>
+           &ndash; required
+        </div>
+      <div>
+        <table>
+          <thead>
+            <tr>
+              <th>X</th>
+              <th>Name</th>
+              <th>Created</th>
+              <th>Modified</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="first"  /></td>
+              <td>first</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
+              <td>fourth</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="second"  /></td>
+              <td>second</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="third"  /></td>
+              <td>third</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+              <td>zero</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+    <div>
+      <div class="buttons">
+        <input type="submit" id="contents-buttons-copy"
+         name="contents.buttons.copy"
+         class="submit-widget button-field" value="Copy" />
+        <input type="submit" id="contents-buttons-cut"
+         name="contents.buttons.cut"
+         class="submit-widget button-field" value="Cut" />
+        <input type="submit" id="contents-buttons-paste"
+         name="contents.buttons.paste"
+         class="submit-widget button-field" value="Paste" />
+        <input type="submit" id="contents-buttons-delete"
+         name="contents.buttons.delete"
+         class="submit-widget button-field" value="Delete" />
+        <input type="submit" id="contents-buttons-rename"
+         name="contents.buttons.rename"
+         class="submit-widget button-field" value="Rename" />
+      </div>
+    </div>
+  </form>
+
+
+Sorting
+-------
+
+The contents page supports sorting by columns. We can do this be set the 
+sortOn request variable as we see in the head link of each column. Let's 
+reverse the default sort order. Note, order depends on the alphabetic oder of 
+number names and not on the number itself.
+
+  >>> sorterRequest = TestRequest(form={'contents-sortOn': 'contents-checkBoxColumn-0',
+  ...                                   'contents-sortOrder':'descending'})
+  >>> alsoProvides(sorterRequest, IDivFormLayer)
+  >>> sortingPage = browser.ContentsPage(container, sorterRequest)
+  >>> sortingPage.update()
+  >>> print sortingPage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+    <div class="viewspace">
+      <div class="required-info">
+         <span class="required">*</span>
+         &ndash; required
+      </div>
+      <div>
+        <table>
+          <thead>
+            <tr>
+              <th>X</th>
+              <th>Name</th>
+              <th>Created</th>
+              <th>Modified</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+              <td>zero</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="third"  /></td>
+              <td>third</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="second"  /></td>
+              <td>second</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
+              <td>fourth</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="first"  /></td>
+              <td>first</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+    <div>
+      <div class="buttons">
+        <input type="submit" id="contents-buttons-copy"
+         name="contents.buttons.copy"
+         class="submit-widget button-field" value="Copy" />
+        <input type="submit" id="contents-buttons-cut"
+         name="contents.buttons.cut"
+         class="submit-widget button-field" value="Cut" />
+        <input type="submit" id="contents-buttons-paste"
+         name="contents.buttons.paste"
+         class="submit-widget button-field" value="Paste" />
+        <input type="submit" id="contents-buttons-delete"
+         name="contents.buttons.delete"
+         class="submit-widget button-field" value="Delete" />
+        <input type="submit" id="contents-buttons-rename"
+         name="contents.buttons.rename"
+         class="submit-widget button-field" value="Rename" />
+      </div>
+    </div>
+  </form>
+
+
+Copy
+----
+
+Frist we need to setup another container which we can copy to:
+
+  >>> secondContainer = Container()
+  >>> root['secondContainer'] = secondContainer
+
+And we need another contents page instance:
+
+  >>> secondPage = browser.ContentsPage(secondContainer, request)
+
+As you can see the second page for the second container has no items:
+
+  >>> secondPage.update()
+  >>> print secondPage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+    <div class="viewspace">
+        <div class="required-info">
+           <span class="required">*</span>
+           &ndash; required
+        </div>
+      <div>
+        <table>
+          <thead>
+            <tr>
+              <th>X</th>
+              <th>Name</th>
+              <th>Created</th>
+              <th>Modified</th>
+            </tr>
+          </thead>
+        </table>
+      </div>
+    </div>
+    <div>
+      <div class="buttons">
+        <input type="submit" id="contents-buttons-copy"
+         name="contents.buttons.copy"
+         class="submit-widget button-field" value="Copy" />
+        <input type="submit" id="contents-buttons-cut"
+         name="contents.buttons.cut"
+         class="submit-widget button-field" value="Cut" />
+        <input type="submit" id="contents-buttons-paste"
+         name="contents.buttons.paste"
+         class="submit-widget button-field" value="Paste" />
+        <input type="submit" id="contents-buttons-delete"
+         name="contents.buttons.delete"
+         class="submit-widget button-field" value="Delete" />
+        <input type="submit" id="contents-buttons-rename"
+         name="contents.buttons.rename"
+         class="submit-widget button-field" value="Rename" />
+      </div>
+    </div>
+  </form>
+
+Now we start with copy the ``zero`` item from the first page. We can do this
+by using some request variables. Let's setup a new request. See the feedback
+we will get as form message:
+
+  >>> copyRequest = TestRequest(
+  ...     form={'contents-checkBoxColumn-0-selectedItems': ['zero'],
+  ...           'contents.buttons.copy': 'Copy'})
+  >>> alsoProvides(copyRequest, IDivFormLayer)
+  >>> copyPage = browser.ContentsPage(container, copyRequest)
+  >>> copyPage.update()
+  >>> print copyPage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+  ...
+        <div class="status">
+          <div class="summary">Items choosen for copy</div>
+        </div>
+  ...
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Name</th>
+        <th>Created</th>
+        <th>Modified</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="first"  /></td>
+        <td>first</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
+        <td>fourth</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="second"  /></td>
+        <td>second</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="third"  /></td>
+        <td>third</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero" checked="checked" /></td>
+        <td>zero</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+    </tbody>
+  ...
+
+
+Copy - Paste
+------------
+
+Now we can go to the second page and paste our selected object. Just prepare
+a request which simualtes that we clicked at the paste button and we can see
+that we pasted the selected item to the second container:
+
+  >>> pasteRequest = TestRequest(form={'contents.buttons.paste': 'Paste'})
+  >>> alsoProvides(pasteRequest, IDivFormLayer)
+  >>> pastePage = browser.ContentsPage(secondContainer, pasteRequest)
+  >>> pastePage.update()
+  >>> print pastePage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+  ...
+        <div class="status">
+          <div class="summary">Data successfully copied</div>
+        </div>
+  ...
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Name</th>
+        <th>Created</th>
+        <th>Modified</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+        <td>zero</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+    </tbody>
+  ...
+
+
+Cut
+---
+
+This part shows how to cut an object from one container and paste it to another
+container.
+
+  >>> cutRequest = TestRequest(
+  ...     form={'contents-checkBoxColumn-0-selectedItems': ['first', 'second'],
+  ...           'contents.buttons.cut': 'Cut'})
+  >>> alsoProvides(cutRequest, IDivFormLayer)
+  >>> cutPage = browser.ContentsPage(container, cutRequest)
+  >>> cutPage.update()
+  >>> print cutPage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+  ...
+        <div class="status">
+          <div class="summary">Items selected for cut</div>
+        </div>
+  ...
+    <table>
+      <thead>
+        <tr>
+          <th>X</th>
+          <th>Name</th>
+          <th>Created</th>
+          <th>Modified</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
+          <td>first</td>
+          <td>01/01/01 01:01</td>
+          <td>02/02/02 02:02</td>
+        </tr>
+        <tr>
+          <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
+          <td>fourth</td>
+          <td>01/01/01 01:01</td>
+          <td>02/02/02 02:02</td>
+        </tr>
+        <tr>
+          <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="second" checked="checked" /></td>
+          <td>second</td>
+          <td>01/01/01 01:01</td>
+          <td>02/02/02 02:02</td>
+        </tr>
+        <tr>
+          <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="third"  /></td>
+          <td>third</td>
+          <td>01/01/01 01:01</td>
+          <td>02/02/02 02:02</td>
+        </tr>
+        <tr>
+          <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+          <td>zero</td>
+          <td>01/01/01 01:01</td>
+          <td>02/02/02 02:02</td>
+        </tr>
+      </tbody>
+    </table>
+  ...
+
+Cut - Paste
+-----------
+
+And we can paste the selectded items to the second container:
+
+  >>> pasteRequest = TestRequest(form={'contents.buttons.paste': 'Paste'})
+  >>> alsoProvides(pasteRequest, IDivFormLayer)
+  >>> pastePage = browser.ContentsPage(secondContainer, pasteRequest)
+  >>> pastePage.update()
+  >>> print pastePage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+  ...
+        <div class="status">
+          <div class="summary">Data successfully copied</div>
+        </div>
+  ...
+  <table>
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Name</th>
+        <th>Created</th>
+        <th>Modified</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="first"  /></td>
+        <td>first</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="second"  /></td>
+        <td>second</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+        <td>zero</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+    </tbody>
+  </table>
+  ...
+
+As you can see the first page does not contain the ``first`` and ``second`` 
+item after paste them to the second container:
+
+  >>> firstPage.update()
+  >>> print firstPage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+  ...
+  <table>
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Name</th>
+        <th>Created</th>
+        <th>Modified</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
+        <td>fourth</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="third"  /></td>
+        <td>third</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+        <td>zero</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+    </tbody>
+  </table>
+  ...
+
+
+Delete
+------
+
+The contents page offers also an option for delete items. Let's select some
+items and click the delete button:
+
+  >>> deleteRequest = TestRequest(
+  ...     form={'contents-checkBoxColumn-0-selectedItems': ['third', 'fourth'],
+  ...           'contents.buttons.delete': 'Delete'})
+  >>> alsoProvides(deleteRequest, IDivFormLayer)
+  >>> deletePage = browser.ContentsPage(container, deleteRequest)
+  >>> deletePage.update()
+  >>> print deletePage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+  ...
+        <div class="status">
+          <div class="summary">Data successfully deleted</div>
+        </div>
+  ...
+  <table>
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Name</th>
+        <th>Created</th>
+        <th>Modified</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+        <td>zero</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+    </tbody>
+  </table>
+  ...
+
+Rename
+------
+
+If we like to rename items, we can do this with the ``Rename`` button. This
+means if we use them, we will get input widgets for the selected items rendered
+in the table. After that, we can click the botton another time which will 
+do the renaming. Let's setup a table which we select items and click the 
+``Rename`` button:
+
+  >>> renameRequest = TestRequest(
+  ...     form={'contents-checkBoxColumn-0-selectedItems': ['first', 'second'],
+  ...           'contents.buttons.rename': 'Rename'})
+  >>> alsoProvides(renameRequest, IDivFormLayer)
+  >>> renamePage = browser.ContentsPage(secondContainer, renameRequest)
+  >>> renamePage.update()
+  >>> print renamePage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+  ...
+  <table>
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Name</th>
+        <th>Created</th>
+        <th>Modified</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
+        <td>first&nbsp;<input type="text" name="contents-renameColumn-1-Zmlyc3Q=-rename" value="first" /></td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="second" checked="checked" /></td>
+        <td>second&nbsp;<input type="text" name="contents-renameColumn-1-c2Vjb25k-rename" value="second" /></td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+        <td>zero</td>
+        <td>01/01/01 01:01</td>
+        <td>02/02/02 02:02</td>
+      </tr>
+    </tbody>
+  </table>
+  ...
+
+Now we rename the ``second`` item to ``fifth``:
+
+  >>> renameRequest = TestRequest(
+  ...     form={'contents-checkBoxColumn-0-selectedItems': ['first', 'second'],
+  ...           'contents-renameColumn-1-Zmlyc3Q=-rename': 'first',
+  ...           'contents-renameColumn-1-c2Vjb25k-rename': 'fifth',
+  ...           'contents.buttons.rename': 'Rename'})
+  >>> alsoProvides(renameRequest, IDivFormLayer)
+  >>> renamePage = browser.ContentsPage(secondContainer, renameRequest)
+  >>> renamePage.update()
+  >>> print renamePage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+    <div class="viewspace">
+        <div class="required-info">
+           <span class="required">*</span>
+           &ndash; required
+        </div>
+        <div class="status">
+          <div class="summary">Could not rename all selected items</div>
+        </div>
+      <div>
+    <table>
+      <thead>
+        <tr>
+          <th>X</th>
+          <th>Name</th>
+          <th>Created</th>
+          <th>Modified</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="fifth"  /></td>
+          <td>fifth</td>
+          <td>01/01/01 01:01</td>
+          <td>02/02/02 02:02</td>
+        </tr>
+        <tr>
+          <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
+          <td>first&nbsp;<input type="text" name="contents-renameColumn-1-Zmlyc3Q=-rename" value="first" />No new name given</td>
+          <td>01/01/01 01:01</td>
+          <td>02/02/02 02:02</td>
+        </tr>
+        <tr>
+          <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+          <td>zero</td>
+          <td>01/01/01 01:01</td>
+          <td>02/02/02 02:02</td>
+        </tr>
+      </tbody>
+    </table>
+  ...
+
+If we try to rename one item to another items name we will get a duplication 
+error. Let's test this:
+
+  >>> renameRequest = TestRequest(
+  ...     form={'contents-checkBoxColumn-0-selectedItems': ['fifth', 'first'],
+  ...           'contents-renameColumn-1-Zmlyc3Q=-rename': 'fifth',
+  ...           'contents-renameColumn-1-ZmlmdGg=-rename': 'first',
+  ...           'contents.buttons.rename': 'Rename'})
+  >>> alsoProvides(renameRequest, IDivFormLayer)
+  >>> renamePage = browser.ContentsPage(secondContainer, renameRequest)
+  >>> renamePage.update()
+  >>> print renamePage.render()
+  <form action="http://127.0.0.1" method="post"
+        enctype="multipart/form-data" class="edit-form"
+        name="contents" id="contents">
+    <div class="viewspace">
+      <div class="required-info">
+         <span class="required">*</span>
+         &ndash; required
+      </div>
+      <div class="status">
+        <div class="summary">Could not rename all selected items</div>
+      </div>
+      <div>
+        <table>
+          <thead>
+            <tr>
+              <th>X</th>
+              <th>Name</th>
+              <th>Created</th>
+              <th>Modified</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="fifth" checked="checked" /></td>
+              <td>fifth&nbsp;<input type="text" name="contents-renameColumn-1-ZmlmdGg=-rename" value="first" />Duplicated item name</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
+              <td>first&nbsp;<input type="text" name="contents-renameColumn-1-Zmlyc3Q=-rename" value="fifth" />Duplicated item name</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+            <tr>
+              <td><input type="checkbox" name="contents-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+              <td>zero</td>
+              <td>01/01/01 01:01</td>
+              <td>02/02/02 02:02</td>
+            </tr>
+          </tbody>
+        </table>
+  ...
+
+As you can see everything goes right. We can check the containers which should
+reflect the same as we see in the tables. Note the ``third`` and ``foruth`` 
+items get deleted and are gone now and the ``second`` item get renamed to 
+``fifth``:
+
+  >>> sorted(container.items())
+  [(u'zero', <Content Zero 0>)]
+
+  >>> sorted(secondContainer.items())
+  [(u'fifth', <Content Second 2>), (u'first', <Content First 1>),
+   (u'zero', <Content Zero 0>)]


Property changes on: z3c.contents/trunk/src/z3c/contents/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/src/z3c/contents/__init__.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/__init__.py	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/__init__.py	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1 @@
+# make a package


Property changes on: z3c.contents/trunk/src/z3c/contents/__init__.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/src/z3c/contents/browser.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/browser.py	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/browser.py	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,322 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import transaction
+import zope.interface
+import zope.i18nmessageid
+import zope.i18n
+from zope.annotation.interfaces import IAnnotations
+from zope.dublincore.interfaces import IZopeDublinCore
+from zope.dublincore.interfaces import IDCDescriptiveProperties
+from zope.copypastemove import ItemNotFoundError
+from zope.copypastemove.interfaces import IPrincipalClipboard
+from zope.copypastemove.interfaces import IObjectCopier, IObjectMover
+from zope.copypastemove.interfaces import IContainerItemRenamer
+from zope.exceptions import DuplicationError
+from zope.exceptions.interfaces import UserError
+from zope.security.interfaces import Unauthorized
+from zope.traversing import api
+
+from zope.app.container.interfaces import DuplicateIDError
+
+from z3c.form import button
+from z3c.formui import form
+from z3c.table import table
+from z3c.template.template import getPageTemplate
+
+from z3c.contents import interfaces
+
+_ = zope.i18nmessageid.MessageFactory('z3c')
+
+
+def getPrincipalClipboard(request):
+    """Return the clipboard based on the request."""
+    user = request.principal
+    annotations = IAnnotations(user)
+    return IPrincipalClipboard(annotations)
+
+
+def getDCTitle(ob):
+    dc = IDCDescriptiveProperties(ob, None)
+    if dc is None:
+        return None
+    else:
+        return dc.title
+
+def safeGetAttr(obj, attr, default):
+    """Attempts to read the attr, returning default if Unauthorized."""
+    try:
+        return getattr(obj, attr, default)
+    except Unauthorized:
+        return default
+
+
+class ContentsPage(table.Table, form.Form):
+    """Generic IContainer management page."""
+
+    zope.interface.implements(interfaces.IContentsPage)
+
+    template = getPageTemplate()
+
+    # internal defaults
+    selectedItems = []
+    supportsPaste = False
+    ignoreContext = False
+
+    # customize this part
+    allowPaste = True
+    prefix = 'contents'
+
+    # error messages
+    deleteErrorMessage = _('Could not delete the selected items')
+    deleteNoItemsMessage = _('No items selected for delete')
+    deleteSucsessMessage = _('Data successfully deleted')
+
+    copyItemsSelected = _('Items choosen for copy')
+    copyNoItemsMessage = _('No items selected for copy')
+    copySucsessMessage = _('Data successfully copied')
+
+    cutNoItemsMessage = _('No items selected for cut')
+    cutItemsSelected = _('Items selected for cut')
+
+    renameErrorMessage = _('Could not rename all selected items')
+    renameDuplicationMessage = _('Duplicated item name')
+    renameItemNotFoundMessage = _('Item not found')
+
+    def update(self):
+        # first setup columns and process the items as selected if any
+        super(ContentsPage, self).update()
+        # second find out if we support paste
+        if self.allowPaste:
+            self.supportsPaste = self.pasteable()
+        self.updateWidgets()
+        self.updateActions()
+        self.actions.execute()
+
+    def render(self):
+        """Render the template."""
+        return self.template()
+
+    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 = api.traverse(self.context, item['target'])
+            except TraversalError:
+                pass
+            else:
+                if item['action'] == 'cut':
+                    mover = IObjectMover(obj)
+                    moveableTo = safeGetAttr(mover, 'moveableTo', None)
+                    if moveableTo is None or not moveableTo(self.context):
+                        return False
+                elif item['action'] == 'copy':
+                    copier = IObjectCopier(obj)
+                    copyableTo = safeGetAttr(copier, 'copyableTo', None)
+                    if copyableTo is None or not copyableTo(self.context):
+                        return False
+                else:
+                    raise
+        return True
+
+    def hasClipboardContents(self):
+        """Interogate the ``PrinicipalAnnotation`` to see if clipboard
+        contents exist."""
+        if not self.supportsPaste:
+            return False
+        # touch at least one item in clipboard to confirm contents
+        clipboard = getPrincipalClipboard(self.request)
+        items = clipboard.getContents()
+        for item in items:
+            try:
+                api.traverse(self.context, item['target'])
+            except TraversalError:
+                pass
+            else:
+                return True
+        return False
+
+    @button.buttonAndHandler(_('Copy'), name='copy')
+    def handleCopy(self, action):
+        if not len(self.selectedItems):
+            self.status = self.copyNoItemsMessage
+            return
+
+        items = []
+        append = items.append
+        for obj in self.selectedItems:
+            __name__ = api.getName(obj)
+            copier = IObjectCopier(obj)
+            if not copier.copyable():
+                m = {"name": __name__}
+                if __name__:
+                    m["name"] = __name__
+                    self.status = _(
+                        "Object '${name}' (${name}) cannot be copied",
+                        mapping=m)
+                else:
+                    self.status = _("Object '${name}' cannot be copied",
+                                   mapping=m)
+                transaction.doom()
+                return
+            append(api.joinPath(api.getPath(self.context), __name__))
+
+        self.status = self.copyItemsSelected
+        # store the requested operation in the principal annotations:
+        clipboard = getPrincipalClipboard(self.request)
+        clipboard.clearContents()
+        clipboard.addItems('copy', items)
+
+    @button.buttonAndHandler(_('Cut'), name='cut')
+    def handleCut(self, action):
+        if not len(self.selectedItems):
+            self.status = self.cutNoItemsMessage
+            return
+
+        items = []
+        append = items.append
+        for obj in self.selectedItems:
+            mover = IObjectMover(obj)
+            __name__ = api.getName(obj)
+            if not mover.moveable():
+                m = {"name": __name__}
+                if name:
+                    m["name"] = __name__
+                    self.status = _(
+                        "Object '${name}' (${name}) cannot be moved",
+                        mapping=m)
+                else:
+                    self.status = _("Object '${name}' cannot be moved",
+                                   mapping=m)
+                transaction.doom()
+                return
+            append(api.joinPath(api.getPath(self.context), __name__))
+
+        self.status = self.cutItemsSelected
+        # store the requested operation in the principal annotations:
+        clipboard = getPrincipalClipboard(self.request)
+        clipboard.clearContents()
+        clipboard.addItems('cut', items)
+
+    @button.buttonAndHandler(_('Paste'), name='paste')
+    def handlePaste(self, action):
+        clipboard = getPrincipalClipboard(self.request)
+        items = clipboard.getContents()
+        moved = False
+        not_pasteable_ids = []
+        for item in items:
+            duplicated_id = False
+            try:
+                obj = api.traverse(self.context, item['target'])
+            except TraversalError:
+                pass
+            else:
+                if item['action'] == 'cut':
+                    mover = IObjectMover(obj)
+                    try:
+                        mover.moveTo(self.context)
+                        moved = True
+                    except DuplicateIDError:
+                        duplicated_id = True
+                elif item['action'] == 'copy':
+                    copier = IObjectCopier(obj)
+                    try:
+                        copier.copyTo(self.context)
+                    except DuplicateIDError:
+                        duplicated_id = True
+                else:
+                    raise
+
+            if duplicated_id:
+                not_pasteable_ids.append(api.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 ?
+            transaction.doom()
+            raise UserError(
+                _("The given name(s) %s is / are already being used" %(
+                str(not_pasteable_ids))))
+        else:
+            # we need to update the table rows again, otherwise we don't 
+            # see the new item in the table
+            super(ContentsPage, self).update()
+            self.status = self.copySucsessMessage
+
+    @button.buttonAndHandler(_('Delete'), name='delete')
+    def handleDelete(self, action):
+        if not len(self.selectedItems):
+            self.status = self.deleteNoItemsMessage
+            return
+        try:
+            for item in self.selectedItems:
+                del self.context[api.getName(item)]
+        except KeyError:
+            self.status = self.deleteErrorMessage
+            transaction.doom()
+        self.status = self.deleteSucsessMessage
+        # update the table rows before we start with rendering
+        super(ContentsPage, self).update()
+
+    @button.buttonAndHandler(_('Rename'), name='rename')
+    def handlerRename(self, action):
+        changed = False
+        errorMessages = {}
+        renameCol = self.columnByName.get('renameColumn')
+        if renameCol:
+            for item in list(self.values):
+                if item in self.selectedItems:
+                    errorMsg = None
+                    oldName = renameCol.getItemValue(item)
+                    newName = renameCol.getRenameValue(item)
+                    if newName is not None and oldName != newName:
+                        try:
+                            renamer = IContainerItemRenamer(self.context)
+                            renamer.renameItem(oldName, newName)
+                            changed = True
+                        except DuplicationError:
+                            errorMsg = self.renameDuplicationMessage
+                            changed = True
+                        except ItemNotFoundError:
+                            errorMsg = self.renameItemNotFoundMessage
+                            changed = True
+                    elif newName is None:
+                        errorMsg = _('No name given')
+                    elif newName is not None and oldName == newName:
+                        errorMsg = _('No new name given')
+                    if errorMsg is not None:
+                        key = renameCol.getItemKey(item)
+                        errorMessages[key] = zope.i18n.translate(
+                            errorMsg, context=self.request)
+        if changed:
+            self.status = self.renameErrorMessage
+            # update the table rows before we start with rendering
+            super(ContentsPage, self).update()
+            # and set error message back to the new rename column
+            renameCol = self.columnByName.get('renameColumn')
+            if renameCol:
+                renameCol.errorMessages = errorMessages


Property changes on: z3c.contents/trunk/src/z3c/contents/browser.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/src/z3c/contents/column.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/column.py	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/column.py	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,67 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import base64
+import binascii
+import zope.i18nmessageid
+from zope.traversing import api
+
+from z3c.table import column
+
+_ = zope.i18nmessageid.MessageFactory('z3c')
+
+
+class RenameColumn(column.NameColumn):
+    """Rename column."""
+
+    weight = 20
+    errorMessages = {}
+
+    @property
+    def isExecuted(self):
+        renameAction = self.table.actions.get('rename')
+        if renameAction and renameAction.isExecuted():
+            return True
+
+    def getSortKey(self, item):
+        return api.getName(item)
+
+    def getItemValue(self, item):
+        return api.getName(item)
+
+    def getItemKey(self, item):
+        name = self.getItemValue(item)
+        base64Name = base64.urlsafe_b64encode(name.encode('utf-8'))
+        return '%s-%s-rename' % (self.id, base64Name)
+
+    def getRenameValue(self, item):
+        key = self.getItemKey(item)
+        return self.request.get(key)
+
+    def renderCell(self, item):
+        key = self.getItemKey(item)
+        value = self.getItemValue(item)
+        newName = self.getRenameValue(item)
+        if newName is None:
+            newName = self.getItemValue(item)
+        if self.isExecuted and item in self.table.selectedItems:
+            msg = self.errorMessages.get(key, u'')
+            return u'%s&nbsp;<input type="text" name="%s" value="%s" />%s' % (
+                value, key, newName, msg)
+        else:
+            return api.getName(item)


Property changes on: z3c.contents/trunk/src/z3c/contents/column.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/src/z3c/contents/contents.pt
===================================================================
--- z3c.contents/trunk/src/z3c/contents/contents.pt	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/contents.pt	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,5 @@
+<div metal:use-macro="macro:form">
+  <div metal:fill-slot="main">
+    <tal:block replace="structure view/renderTable">table</tal:block>
+  </div>
+</div>


Property changes on: z3c.contents/trunk/src/z3c/contents/contents.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/src/z3c/contents/interfaces.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/interfaces.py	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/interfaces.py	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,26 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.i18nmessageid
+from z3c.table import interfaces
+
+_ = zope.i18nmessageid.MessageFactory('z3c')
+
+
+class IContentsPage(interfaces.ITable):
+    """Container management page"""


Property changes on: z3c.contents/trunk/src/z3c/contents/interfaces.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/src/z3c/contents/testing.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/testing.py	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/testing.py	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+
+class Content(object):
+    """Sample content which is pickable for copy test."""
+    def __init__(self, title, number):
+        self.title = title
+        self.number = number
+
+    def __repr__(self):
+        return u'<%s %s %s>' % (self.__class__.__name__, self.title,
+            self.number)


Property changes on: z3c.contents/trunk/src/z3c/contents/testing.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.contents/trunk/src/z3c/contents/tests.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/tests.py	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/tests.py	2008-02-13 11:59:17 UTC (rev 83793)
@@ -0,0 +1,94 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import unittest
+import zope.component
+import zope.interface
+from zope.annotation.interfaces import IAnnotations
+from zope.copypastemove import ContainerItemRenamer
+from zope.copypastemove import ObjectMover
+from zope.copypastemove import ObjectCopier
+from zope.copypastemove import PrincipalClipboard
+from zope.copypastemove.interfaces import IContainerItemRenamer
+from zope.copypastemove.interfaces import IObjectMover
+from zope.copypastemove.interfaces import IObjectCopier
+from zope.copypastemove.interfaces import IPrincipalClipboard
+from zope.publisher.browser import TestRequest
+from zope.testing import doctest
+from zope.app.container.interfaces import IContainer
+from zope.app.container.interfaces import IContained
+from zope.app.testing import setup
+
+import z3c.testing
+from z3c.macro import tales
+import z3c.form.testing 
+import z3c.table.testing 
+
+
+class PrincipalAnnotations(dict):
+    zope.interface.implements(IAnnotations)
+    data = {}
+    def __new__(class_, context=None):
+        try:
+            annotations = class_.data[str(context)]
+        except KeyError:
+            annotations = dict.__new__(class_)
+            class_.data[str(context)] = annotations
+        return annotations
+    def __init__(self, context):
+        pass
+    def __repr__(self):
+        return "<%s.PrincipalAnnotations object>" % __name__
+
+
+def setUp(test):
+    test.globs = {'root': setup.placefulSetUp(True)}
+
+    from zope.app.pagetemplate import metaconfigure
+    metaconfigure.registerType('macro', tales.MacroExpression)
+
+    zope.component.provideAdapter(ObjectCopier, (IContained,), IObjectCopier)
+    zope.component.provideAdapter(ObjectMover, (IContained,), IObjectMover)
+    zope.component.provideAdapter(ContainerItemRenamer, (IContainer,), 
+        IContainerItemRenamer)
+
+    zope.component.provideAdapter(PrincipalClipboard, (IAnnotations,),
+        IPrincipalClipboard)
+    # use None as principal
+    zope.component.provideAdapter(PrincipalAnnotations, (None,),
+        IAnnotations)
+
+    # dublin core stub adapter
+    zope.component.provideAdapter(z3c.table.testing.DublinCoreAdapterStub)
+
+
+def tearDown(test):
+    setup.placefulTearDown()
+
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('README.txt',
+            setUp=setUp, tearDown=tearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+            ),
+        ))
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: z3c.contents/trunk/src/z3c/contents/tests.py
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Checkins mailing list