[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>
+ – 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>
+ – 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>
+ – 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>
+ – 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 <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 <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>
+ – 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 <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>
+ – 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 <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 <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 <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