[Checkins] SVN: z3c.table/ Move implementation form private repos
to z3c
Roger Ineichen
roger at projekt01.ch
Wed Feb 13 06:47:36 EST 2008
Log message for revision 83790:
Move implementation form private repos to z3c
Changed:
A z3c.table/branches/
A z3c.table/tags/
A z3c.table/trunk/
A z3c.table/trunk/AUTHOR.txt
A z3c.table/trunk/CHANGES.txt
A z3c.table/trunk/LICENSE.txt
A z3c.table/trunk/README.txt
A z3c.table/trunk/TODO.txt
A z3c.table/trunk/bootstrap.py
A z3c.table/trunk/buildout.cfg
A z3c.table/trunk/externals/
A z3c.table/trunk/setup.py
A z3c.table/trunk/src/
A z3c.table/trunk/src/z3c/
A z3c.table/trunk/src/z3c/__init__.py
A z3c.table/trunk/src/z3c/table/
A z3c.table/trunk/src/z3c/table/README.txt
A z3c.table/trunk/src/z3c/table/__init__.py
A z3c.table/trunk/src/z3c/table/column.py
A z3c.table/trunk/src/z3c/table/column.txt
A z3c.table/trunk/src/z3c/table/configure.zcml
A z3c.table/trunk/src/z3c/table/interfaces.py
A z3c.table/trunk/src/z3c/table/table.py
A z3c.table/trunk/src/z3c/table/testing.py
A z3c.table/trunk/src/z3c/table/tests.py
-=-
Added: z3c.table/trunk/AUTHOR.txt
===================================================================
--- z3c.table/trunk/AUTHOR.txt (rev 0)
+++ z3c.table/trunk/AUTHOR.txt 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1 @@
+Roger Ineichen (roger <at> projekt01 <dot> ch)
Property changes on: z3c.table/trunk/AUTHOR.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/CHANGES.txt
===================================================================
--- z3c.table/trunk/CHANGES.txt (rev 0)
+++ z3c.table/trunk/CHANGES.txt 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,8 @@
+=======
+CHANGES
+=======
+
+Version 0.5.0 (unreleased)
+--------------------------
+
+- Initial Release
Property changes on: z3c.table/trunk/CHANGES.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/LICENSE.txt
===================================================================
--- z3c.table/trunk/LICENSE.txt (rev 0)
+++ z3c.table/trunk/LICENSE.txt 2008-02-13 11:47:35 UTC (rev 83790)
@@ -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.table/trunk/LICENSE.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/README.txt
===================================================================
--- z3c.table/trunk/README.txt (rev 0)
+++ z3c.table/trunk/README.txt 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1 @@
+This package provides an modular table rendering implementation for Zope3.
Property changes on: z3c.table/trunk/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/TODO.txt
===================================================================
--- z3c.table/trunk/TODO.txt (rev 0)
+++ z3c.table/trunk/TODO.txt 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,7 @@
+====
+TODO
+====
+
+- implement batch template
+
+- Implement LinkColumn
Property changes on: z3c.table/trunk/TODO.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/bootstrap.py
===================================================================
--- z3c.table/trunk/bootstrap.py (rev 0)
+++ z3c.table/trunk/bootstrap.py 2008-02-13 11:47:35 UTC (rev 83790)
@@ -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.table/trunk/bootstrap.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/buildout.cfg
===================================================================
--- z3c.table/trunk/buildout.cfg (rev 0)
+++ z3c.table/trunk/buildout.cfg 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,19 @@
+[buildout]
+develop = .
+ externals/z3c.batching
+parts = test checker coverage
+
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.table [test]
+
+
+[checker]
+recipe = lovely.recipe:importchecker
+path = src/z3c/table
+
+
+[coverage]
+recipe = zc.recipe.egg
+eggs = z3c.coverage
Added: z3c.table/trunk/setup.py
===================================================================
--- z3c.table/trunk/setup.py (rev 0)
+++ z3c.table/trunk/setup.py 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,69 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Setup
+
+$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.table',
+ version='0.5.0',
+ author = "Stephan Richter, Roger Ineichen and the Zope Community",
+ author_email = "zope3-dev at zope.org",
+ description = "Modular table rendering implementation for Zope3",
+ long_description=(
+ read('README.txt')
+ + '\n\n' +
+ read('CHANGES.txt')
+ ),
+ license = "ZPL 2.1",
+ keywords = "zope3 z3c table content provider",
+ 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.table',
+ 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',
+ 'zope.interface',
+ 'zope.contentprovider',
+ 'z3c.template',
+ 'z3c.batching',
+ ],
+ zip_safe = False,
+)
\ No newline at end of file
Property changes on: z3c.table/trunk/setup.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/src/z3c/__init__.py
===================================================================
--- z3c.table/trunk/src/z3c/__init__.py (rev 0)
+++ z3c.table/trunk/src/z3c/__init__.py 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+ import pkg_resources
+ pkg_resources.declare_namespace(__name__)
+except ImportError:
+ import pkgutil
+ __path__ = pkgutil.extend_path(__path__, __name__)
Property changes on: z3c.table/trunk/src/z3c/__init__.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/src/z3c/table/README.txt
===================================================================
--- z3c.table/trunk/src/z3c/table/README.txt (rev 0)
+++ z3c.table/trunk/src/z3c/table/README.txt 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,1156 @@
+=========
+Z3C Table
+=========
+
+The goal of this package is to offer a modular table rendering library. We use
+the content provider pattern and the column are implemented as adapters which
+will give us a powerfull base conept.
+
+
+Some important concepts we use
+------------------------------
+
+- separate impleentation in update render parts, This allows to manipulate
+ data after update call before we render.
+
+- allo to use page templates if needed
+
+- allow to use the rendered batch outside the existing table HTML part. This
+ could be done if you use a view which takes care and offers table and batch
+ varaiables as a you know from model - view - controller patterns.
+ If you use viewlets, it gets a little bit harder to separate the table and
+ batch into different components. But this is also possible. You can implement
+ a table as named adapter for context, request and implement content a
+ provider or viewlets for batch and table. With this separation you can call
+ the batch before the table because you can get the named table adapter and
+ update the data. After update and render the batch you can later access the
+ table content provider and get the named adapter again and only render the
+ table. This supports not calling the table adapters update method more then
+ once.
+
+
+Note
+----
+
+As you probably know, batching is only possible after sorting columns. This is
+a nightmare if it comes to performance. The reason is, all data need to get
+sorted before the batch can start at the given position. And sorting can most
+the time only be done by touching each object. This means you have to take care
+if you are using a large set of data, even if you use batching.
+
+
+Sample data setup
+-----------------
+
+Let's create a sample container which we can use as our iterable context:
+
+ >>> from zope.app.container import btree
+ >>> class Container(btree.BTreeContainer):
+ ... """Sample container."""
+ >>> container = Container()
+
+and create a sample content object which we use as container item:
+
+ >>> class Content(object):
+ ... """Sample content."""
+ ... def __init__(self, title, number):
+ ... self.title = title
+ ... self.number = number
+
+Now setup some items:
+
+ >>> container[u'first'] = Content('First', 1)
+ >>> container[u'second'] = Content('Second', 2)
+ >>> container[u'third'] = Content('Third', 3)
+
+
+Table
+-----
+
+Create a test request and represent the table:
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> from z3c.table import table
+ >>> request = TestRequest()
+ >>> plainTable = table.Table(container, request)
+
+Now we can update and render the table:
+
+ >>> plainTable.update()
+ >>> plainTable.render()
+ u''
+
+
+Column Adapter
+--------------
+
+Now we can register a column for our table:
+
+ >>> import zope.component
+ >>> from z3c.table import interfaces
+ >>> from z3c.table import column
+
+ >>> class TitleColumn(column.Column):
+ ...
+ ... weight = 10
+ ...
+ ... def renderHeadCell(self):
+ ... return u'Title'
+ ...
+ ... def renderCell(self, item):
+ ... return u'Title: %s' % item.title
+
+Now we can register our column adapter.
+
+ >>> zope.component.provideAdapter(TitleColumn,
+ ... (None, None, interfaces.ITable), provides=interfaces.IColumn,
+ ... name='firstColumn')
+
+Now we can render the table again:
+
+ >>> plainTable.update()
+ >>> print plainTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Title</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Title: First</td>
+ </tr>
+ <tr>
+ <td>Title: Second</td>
+ </tr>
+ <tr>
+ <td>Title: Third</td>
+ </tr>
+ </tbody>
+ </table>
+
+We can also use the predefined name column:
+
+ >>> zope.component.provideAdapter(column.NameColumn,
+ ... (None, None, interfaces.ITable), provides=interfaces.IColumn,
+ ... name='secondColumn')
+
+Now we will get a nother additional column:
+
+ >>> plainTable.update()
+ >>> print plainTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Title</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>first</td>
+ <td>Title: First</td>
+ </tr>
+ <tr>
+ <td>second</td>
+ <td>Title: Second</td>
+ </tr>
+ <tr>
+ <td>third</td>
+ <td>Title: Third</td>
+ </tr>
+ </tbody>
+ </table>
+
+
+Colspan
+-------
+
+Now let's show how we can define a colspan condition of 2 for an column:
+
+ >>> class ColspanColumn(column.NameColumn):
+ ...
+ ... weight = 999
+ ...
+ ... def getColspan(self, item):
+ ... # colspan condition
+ ... if item.__name__ == 'first':
+ ... return 2
+ ... else:
+ ... return 0
+ ...
+ ... def renderHeadCell(self):
+ ... return u'Colspan'
+ ...
+ ... def renderCell(self, item):
+ ... return u'colspan: %s' % item.title
+
+Now we register this column adapter as colspanColumn:
+
+ >>> zope.component.provideAdapter(ColspanColumn,
+ ... (None, None, interfaces.ITable), provides=interfaces.IColumn,
+ ... name='colspanColumn')
+
+
+Now you can see that the colspan of the ColspanAdapter is larger then the table
+columns. This wil raise a VlaueError:
+
+ >>> plainTable.update()
+ Traceback (most recent call last):
+ ...
+ ValueError: Colspan for column '<ColspanColumn u'colspanColumn'>' larger then table.
+
+But if we set the column as first row, it weill render the colspan correct:
+
+ >>> class CorrectColspanColumn(ColspanColumn):
+ ... """Colspan with correct weight."""
+ ...
+ ... weight = 0
+
+Register and render the table again:
+
+ >>> zope.component.provideAdapter(CorrectColspanColumn,
+ ... (None, None, interfaces.ITable), provides=interfaces.IColumn,
+ ... name='colspanColumn')
+
+ >>> plainTable.update()
+ >>> print plainTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Colspan</th>
+ <th>Name</th>
+ <th>Title</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td colspan="2">colspan: First</td>
+ <td>Title: First</td>
+ </tr>
+ <tr>
+ <td>colspan: Second</td>
+ <td>second</td>
+ <td>Title: Second</td>
+ </tr>
+ <tr>
+ <td>colspan: Third</td>
+ <td>third</td>
+ <td>Title: Third</td>
+ </tr>
+ </tbody>
+ </table>
+
+
+Setup columns
+-------------
+
+The existing implementation allows us to define a table in a class without
+to use the modular adapter pattern for columns.
+
+First we need to define a column which cna render a value for our items:
+
+ >>> class SimpleColumn(column.Column):
+ ...
+ ... weight = 0
+ ...
+ ... def renderCell(self, item):
+ ... return item.title
+
+Let's define our table which defines the columns explicit. you can also see,
+that we do not return the columns in the correct order:
+
+ >>> class PrivateTable(table.Table):
+ ...
+ ... def setUpColumns(self):
+ ... firstColumn = TitleColumn(self.context, self.request, self)
+ ... firstColumn.__name__ = u'title'
+ ... firstColumn.weight = 1
+ ... secondColumn = SimpleColumn(self.context, self.request, self)
+ ... secondColumn.__name__ = u'simple'
+ ... secondColumn.weight = 2
+ ... secondColumn.header = u'The second column'
+ ... return [secondColumn, firstColumn]
+
+Now we can create, update and render the table and see that this renders a nice
+table too:
+
+ >>> privateTable = PrivateTable(container, request)
+ >>> privateTable.update()
+ >>> print privateTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Title</th>
+ <th>The second column</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Title: First</td>
+ <td>First</td>
+ </tr>
+ <tr>
+ <td>Title: Second</td>
+ <td>Second</td>
+ </tr>
+ <tr>
+ <td>Title: Third</td>
+ <td>Third</td>
+ </tr>
+ </tbody>
+ </table>
+
+
+Cascading Style Sheet
+---------------------
+
+Our table and column implementation supports css class assignment. Let's define
+a table and columns with some css class values:
+
+ >>> class CSSTable(table.Table):
+ ...
+ ... cssClasses = {'table': 'table',
+ ... 'thead': 'thead',
+ ... 'tbody': 'tbody',
+ ... 'th': 'th',
+ ... 'tr': 'tr',
+ ... 'td': 'td'}
+ ...
+ ... def setUpColumns(self):
+ ... firstColumn = TitleColumn(self.context, self.request, self)
+ ... firstColumn.__name__ = u'title'
+ ... firstColumn.__parent__ = self
+ ... firstColumn.weight = 1
+ ... firstColumn.cssClasses = {'th':'thCol', 'td':'tdCol'}
+ ... secondColumn = SimpleColumn(self.context, self.request, self)
+ ... secondColumn.__name__ = u'simple'
+ ... secondColumn.__parent__ = self
+ ... secondColumn.weight = 2
+ ... secondColumn.header = u'The second column'
+ ... return [secondColumn, firstColumn]
+
+Now let's see if we got the cs class assigned which we defined in the table and
+column. Note that the ``th`` and ``td`` got CSS declarations from the table and
+from the column.
+
+ >>> cssTable = CSSTable(container, request)
+ >>> cssTable.update()
+ >>> print cssTable.render()
+ <table class="table">
+ <thead class="thead">
+ <tr class="tr">
+ <th class="thCol th">Title</th>
+ <th class="th">The second column</th>
+ </tr>
+ </thead>
+ <tbody class="tbody">
+ <tr class="tr">
+ <td class="tdCol td">Title: First</td>
+ <td class="td">First</td>
+ </tr>
+ <tr class="tr">
+ <td class="tdCol td">Title: Second</td>
+ <td class="td">Second</td>
+ </tr>
+ <tr class="tr">
+ <td class="tdCol td">Title: Third</td>
+ <td class="td">Third</td>
+ </tr>
+ </tbody>
+ </table>
+
+
+Alternating table
+-----------------
+
+We offer built in support for alternating table rows based on even and odd CSS
+classes. Let's define a table including other CSS classes. For even/odd support,
+we only need to define the ``cssClassEven`` and ``cssClassOdd`` CSS classes:
+
+ >>> class AlternatingTable(table.Table):
+ ...
+ ... cssClasses = {'table': 'table',
+ ... 'thead': 'thead',
+ ... 'tbody': 'tbody',
+ ... 'th': 'th',
+ ... 'tr': 'tr',
+ ... 'td': 'td'}
+ ...
+ ... cssClassEven = u'even'
+ ... cssClassOdd = u'odd'
+ ...
+ ... def setUpColumns(self):
+ ... firstColumn = TitleColumn(self.context, self.request, self)
+ ... firstColumn.__name__ = u'title'
+ ... firstColumn.__parent__ = self
+ ... firstColumn.weight = 1
+ ... firstColumn.cssClasses = {'th':'thCol', 'td':'tdCol'}
+ ... secondColumn = SimpleColumn(self.context, self.request, self)
+ ... secondColumn.__name__ = u'simple'
+ ... secondColumn.__parent__ = self
+ ... secondColumn.weight = 2
+ ... secondColumn.header = u'The second column'
+ ... return [secondColumn, firstColumn]
+
+Now update and render the new table. As you can see the given ``tr`` class get
+used additional to the even and odd classes:
+
+ >>> alternatingTable = AlternatingTable(container, request)
+ >>> alternatingTable.update()
+ >>> print alternatingTable.render()
+ <table class="table">
+ <thead class="thead">
+ <tr class="tr">
+ <th class="thCol th">Title</th>
+ <th class="th">The second column</th>
+ </tr>
+ </thead>
+ <tbody class="tbody">
+ <tr class="even tr">
+ <td class="tdCol td">Title: First</td>
+ <td class="td">First</td>
+ </tr>
+ <tr class="odd tr">
+ <td class="tdCol td">Title: Second</td>
+ <td class="td">Second</td>
+ </tr>
+ <tr class="even tr">
+ <td class="tdCol td">Title: Third</td>
+ <td class="td">Third</td>
+ </tr>
+ </tbody>
+ </table>
+
+
+Sorting Table
+-------------
+
+Another table feature is the support for sorting data given from columns. Since
+sorting table data is an important feature, we offer this by default. But it
+only gets used if there is a sortOn value set. You can set this value at class
+level by adding a defaultSortOn value or set it as a request value. We show you
+how to do this later. We also need a columns which allows us to do a better
+sort sample. Our new sorting column will use the content items number value
+for sorting:
+
+ >>> class NumberColumn(column.Column):
+ ...
+ ... header = u'Number'
+ ... weight = 20
+ ...
+ ... def getSortKey(self, item):
+ ... return item.number
+ ...
+ ... def renderCell(self, item):
+ ... return 'number: %s' % item.number
+
+
+Now let's setup a table:
+
+ >>> class SortingTable(table.Table):
+ ...
+ ... def setUpColumns(self):
+ ... firstColumn = TitleColumn(self.context, self.request, self)
+ ... firstColumn.__name__ = u'title'
+ ... firstColumn.__parent__ = self
+ ... secondColumn = NumberColumn(self.context, self.request, self)
+ ... secondColumn.__name__ = u'number'
+ ... secondColumn.__parent__ = self
+ ... return [firstColumn, secondColumn]
+
+We also need some more container items which we can use for sorting:
+
+ >>> container[u'fourth'] = Content('Fourth', 4)
+ >>> container[u'zero'] = Content('Zero', 0)
+
+And render them without set a sortOn value:
+
+ >>> sortingTable = SortingTable(container, request)
+ >>> sortingTable.update()
+ >>> print sortingTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Title</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Title: First</td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td>Title: Fourth</td>
+ <td>number: 4</td>
+ </tr>
+ <tr>
+ <td>Title: Second</td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td>Title: Third</td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td>Title: Zero</td>
+ <td>number: 0</td>
+ </tr>
+ </tbody>
+ </table>
+
+As you can see this table doesn't provide any explicit order. Let's find out
+the index of our column which we like to sort on:
+
+ >>> sortOnId = sortingTable.rows[0][1][1].id
+ >>> sortOnId
+ u'table-number-1'
+
+And let's ues this id as ``sortOn`` value:
+
+ >>> sortingTable.sortOn = sortOnId
+
+An important thing is to update the table after set an sort on value:
+
+ >>> sortingTable.update()
+ >>> print sortingTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Title</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Title: Zero</td>
+ <td>number: 0</td>
+ </tr>
+ <tr>
+ <td>Title: First</td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td>Title: Second</td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td>Title: Third</td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td>Title: Fourth</td>
+ <td>number: 4</td>
+ </tr>
+ </tbody>
+ </table>
+
+We can also reverse the sort order:
+
+ >>> sortingTable.sortOrder = 'reverse'
+ >>> sortingTable.update()
+ >>> print sortingTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Title</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Title: Fourth</td>
+ <td>number: 4</td>
+ </tr>
+ <tr>
+ <td>Title: Third</td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td>Title: Second</td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td>Title: First</td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td>Title: Zero</td>
+ <td>number: 0</td>
+ </tr>
+ </tbody>
+ </table>
+
+The table implementation is also able to get the sort criteria given from a
+request. Let's setup such a request:
+
+ >>> sorterRequest = TestRequest(form={'table-sortOn': 'table-number-1',
+ ... 'table-sortOrder':'descending'})
+
+and another time, update and render. As you can see the new table get sorted
+by the second column and ordered in reverse order:
+
+ >>> requestSortedTable = SortingTable(container, sorterRequest)
+ >>> requestSortedTable.update()
+ >>> print requestSortedTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Title</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Title: Fourth</td>
+ <td>number: 4</td>
+ </tr>
+ <tr>
+ <td>Title: Third</td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td>Title: Second</td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td>Title: First</td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td>Title: Zero</td>
+ <td>number: 0</td>
+ </tr>
+ </tbody>
+ </table>
+
+
+Class based Table setup
+-----------------------
+
+There is a more elegant way to define table rows at class level. We offer
+a method which you can use if you need to define some columns called
+``addTable``. Before we define the table. let's define some cell renderer:
+
+ >>> def headCellRenderer():
+ ... return u'My items'
+
+ >>> def cellRenderer(item):
+ ... return u'%s item' % item.title
+
+Now we can define our table and use the custom cell renderer:
+
+ >>> class AddColumnTable(table.Table):
+ ...
+ ... cssClasses = {'table': 'table',
+ ... 'thead': 'thead',
+ ... 'tbody': 'tbody',
+ ... 'th': 'th',
+ ... 'tr': 'tr',
+ ... 'td': 'td'}
+ ...
+ ... cssClassEven = u'even'
+ ... cssClassOdd = u'odd'
+ ...
+ ... def setUpColumns(self):
+ ... return [
+ ... column.addColumn(self, TitleColumn, u'title',
+ ... cellRenderer=cellRenderer,
+ ... headCellRenderer=headCellRenderer,
+ ... weight=1),
+ ... column.addColumn(self, SimpleColumn, name=u'simple',
+ ... weight=2, header=u'The second column',
+ ... cssClasses = {'th':'thCol', 'td':'tdCol'})
+ ... ]
+
+ >>> addColumnTable = AddColumnTable(container, request)
+ >>> addColumnTable.update()
+ >>> print addColumnTable.render()
+ <table class="table">
+ <thead class="thead">
+ <tr class="tr">
+ <th class="th">My items</th>
+ <th class="thCol th">The second column</th>
+ </tr>
+ </thead>
+ <tbody class="tbody">
+ <tr class="even tr">
+ <td class="td">First item</td>
+ <td class="tdCol td">First</td>
+ </tr>
+ <tr class="odd tr">
+ <td class="td">Fourth item</td>
+ <td class="tdCol td">Fourth</td>
+ </tr>
+ <tr class="even tr">
+ <td class="td">Second item</td>
+ <td class="tdCol td">Second</td>
+ </tr>
+ <tr class="odd tr">
+ <td class="td">Third item</td>
+ <td class="tdCol td">Third</td>
+ </tr>
+ <tr class="even tr">
+ <td class="td">Zero item</td>
+ <td class="tdCol td">Zero</td>
+ </tr>
+ </tbody>
+ </table>
+
+As you can see the table columns provide all attributes we set in the addColumn
+method:
+
+ >>> titleColumn = addColumnTable.rows[0][0][1]
+ >>> titleColumn
+ <TitleColumn u'title'>
+
+ >>> titleColumn.__name__
+ u'title'
+
+ >>> titleColumn.__parent__
+ <AddColumnTable None>
+
+ >>> titleColumn.colspan
+ 0
+
+ >>> titleColumn.weight
+ 1
+
+ >>> titleColumn.header
+ u''
+
+ >>> titleColumn.cssClasses
+ {}
+
+and the second column
+
+ >>> simpleColumn = addColumnTable.rows[0][1][1]
+ >>> simpleColumn
+ <SimpleColumn u'simple'>
+
+ >>> simpleColumn.__name__
+ u'simple'
+
+ >>> simpleColumn.__parent__
+ <AddColumnTable None>
+
+ >>> simpleColumn.colspan
+ 0
+
+ >>> simpleColumn.weight
+ 2
+
+ >>> simpleColumn.header
+ u'The second column'
+
+ >>> simpleColumn.cssClasses
+ {'td': 'tdCol', 'th': 'thCol'}
+
+
+Batching
+--------
+
+Or table implements a concept for batching by default. If you set the attribute
+``startBatchingAt`` to a size smaller then the rows generated based on the given
+items, our table starts to generate a batch. Let's define a new Table:
+
+ >>> class BatchingTable(table.Table):
+ ...
+ ... def setUpColumns(self):
+ ... return [
+ ... column.addColumn(self, TitleColumn, u'title',
+ ... cellRenderer=cellRenderer,
+ ... headCellRenderer=headCellRenderer,
+ ... weight=1),
+ ... column.addColumn(self, NumberColumn, name=u'number',
+ ... weight=2, header=u'Number')
+ ... ]
+
+Now we can create our table:
+
+ >>> batchingTable = BatchingTable(container, request)
+
+And add some more items to our container:
+
+ >>> container[u'sixt'] = Content('Sixt', 6)
+ >>> container[u'seventh'] = Content('Seventh', 7)
+ >>> container[u'eighth'] = Content('Eighth', 8)
+ >>> container[u'ninth'] = Content('Ninth', 9)
+ >>> container[u'tenth'] = Content('Tenth', 10)
+ >>> container[u'eleventh'] = Content('Eleventh', 11)
+ >>> container[u'twelfth '] = Content('Twelfth', 12)
+ >>> container[u'thirteenth'] = Content('Thirteenth', 13)
+ >>> container[u'fourteenth'] = Content('Fourteenth', 14)
+ >>> container[u'fifteenth '] = Content('Fifteenth', 15)
+ >>> container[u'sixteenth'] = Content('Sixteenth', 16)
+ >>> container[u'seventeenth'] = Content('Seventeenth', 17)
+ >>> container[u'eighteenth'] = Content('Eighteenth', 18)
+ >>> container[u'nineteenth'] = Content('Nineteenth', 19)
+ >>> container[u'twentieth'] = Content('Twentieth', 20)
+
+Now let's show the full table without batching:
+
+ >>> batchingTable.update()
+ >>> print batchingTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>My items</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Eighteenth item</td>
+ <td>number: 18</td>
+ </tr>
+ <tr>
+ <td>Eighth item</td>
+ <td>number: 8</td>
+ </tr>
+ <tr>
+ <td>Eleventh item</td>
+ <td>number: 11</td>
+ </tr>
+ <tr>
+ <td>Fifteenth item</td>
+ <td>number: 15</td>
+ </tr>
+ <tr>
+ <td>First item</td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td>Fourteenth item</td>
+ <td>number: 14</td>
+ </tr>
+ <tr>
+ <td>Fourth item</td>
+ <td>number: 4</td>
+ </tr>
+ <tr>
+ <td>Nineteenth item</td>
+ <td>number: 19</td>
+ </tr>
+ <tr>
+ <td>Ninth item</td>
+ <td>number: 9</td>
+ </tr>
+ <tr>
+ <td>Second item</td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td>Seventeenth item</td>
+ <td>number: 17</td>
+ </tr>
+ <tr>
+ <td>Seventh item</td>
+ <td>number: 7</td>
+ </tr>
+ <tr>
+ <td>Sixt item</td>
+ <td>number: 6</td>
+ </tr>
+ <tr>
+ <td>Sixteenth item</td>
+ <td>number: 16</td>
+ </tr>
+ <tr>
+ <td>Tenth item</td>
+ <td>number: 10</td>
+ </tr>
+ <tr>
+ <td>Third item</td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td>Thirteenth item</td>
+ <td>number: 13</td>
+ </tr>
+ <tr>
+ <td>Twelfth item</td>
+ <td>number: 12</td>
+ </tr>
+ <tr>
+ <td>Twentieth item</td>
+ <td>number: 20</td>
+ </tr>
+ <tr>
+ <td>Zero item</td>
+ <td>number: 0</td>
+ </tr>
+ </tbody>
+ </table>
+
+As you can see, the table is not nice ordered and it uses all items. If we like
+to use tha batch, we need to set the startBatchingAt size to a lower value then
+it is set by default. The default value which a batch is used is set to ``50```:
+
+ >>> batchingTable.startBatchingAt
+ 50
+
+We will set the size to ``10`` for now:
+
+ >>> batchingTable.startBatchingAt = 5
+ >>> batchingTable.startBatchingAt
+ 5
+
+There is also a ``batchSize`` value which we need to set to ``5``. By deault
+the value get initialized by the ``batchSize`` value:
+
+ >>> batchingTable.batchSize
+ 50
+
+ >>> batchingTable.batchSize = 5
+ >>> batchingTable.batchSize
+ 5
+
+Now we can update and render the table again. But you will see that we only get
+a table size of 5 rows whihc is correct. But the order doesn't depend on the
+numbers we see in cells:
+
+ >>> batchingTable.update()
+ >>> print batchingTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>My items</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Eighteenth item</td>
+ <td>number: 18</td>
+ </tr>
+ <tr>
+ <td>Eighth item</td>
+ <td>number: 8</td>
+ </tr>
+ <tr>
+ <td>Eleventh item</td>
+ <td>number: 11</td>
+ </tr>
+ <tr>
+ <td>Fifteenth item</td>
+ <td>number: 15</td>
+ </tr>
+ <tr>
+ <td>First item</td>
+ <td>number: 1</td>
+ </tr>
+ </tbody>
+ </table>
+
+I think we should order the table by the second column before we show the next
+batch values. We do this by simply set the ``defaultSortOn``:
+
+ >>> batchingTable.sortOn = u'table-number-1'
+
+Now we shuld see a nice ordered and batched table:
+
+ >>> batchingTable.update()
+ >>> print batchingTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>My items</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Zero item</td>
+ <td>number: 0</td>
+ </tr>
+ <tr>
+ <td>First item</td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td>Second item</td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td>Third item</td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td>Fourth item</td>
+ <td>number: 4</td>
+ </tr>
+ </tbody>
+ </table>
+
+The batch concept allows us to choose from all batches and render the rows
+for this batched items. We can do this by set any batch as rows. as you can see
+we have ``4`` batched row data available:
+
+ >>> len(batchingTable.rows.batches)
+ 4
+
+We can set such a batch as row values, then this batch data are used for
+rendering. But take care, if we update the table, our rows get overriden
+and reset to the previous values. this means you can set any bath as rows
+data and only render them. This is possbile since the update method sorted all
+items and all batch contain ready to use data. This concept could be important
+if you need to cache batches etc.
+
+ >>> batchingTable.rows = batchingTable.rows.batches[1]
+ >>> print batchingTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>My items</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Sixt item</td>
+ <td>number: 6</td>
+ </tr>
+ <tr>
+ <td>Seventh item</td>
+ <td>number: 7</td>
+ </tr>
+ <tr>
+ <td>Eighth item</td>
+ <td>number: 8</td>
+ </tr>
+ <tr>
+ <td>Ninth item</td>
+ <td>number: 9</td>
+ </tr>
+ <tr>
+ <td>Tenth item</td>
+ <td>number: 10</td>
+ </tr>
+ </tbody>
+ </table>
+
+And like described above, if you call ``update`` our batch to rows setup get
+reset:
+
+ >>> batchingTable.update()
+ >>> print batchingTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>My items</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Zero item</td>
+ <td>number: 0</td>
+ </tr>
+ <tr>
+ <td>First item</td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td>Second item</td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td>Third item</td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td>Fourth item</td>
+ <td>number: 4</td>
+ </tr>
+ </tbody>
+ </table>
+
+This means you can probably update all batches, cache them and use them alter.
+but this is not usfull for normal usage in a page without an enhanced concept
+which is not a part of this implementation. This also means, there must be
+another way to set the batch index. Yes there is, there are two other ways how
+we can set the batch position. We can set a batch position by set the
+``batchStart`` value in our table or we can use a request variable. Let's show
+the first one first:
+
+ >>> batchingTable.batchStart = 6
+ >>> batchingTable.update()
+ >>> print batchingTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>My items</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Seventh item</td>
+ <td>number: 7</td>
+ </tr>
+ <tr>
+ <td>Eighth item</td>
+ <td>number: 8</td>
+ </tr>
+ <tr>
+ <td>Ninth item</td>
+ <td>number: 9</td>
+ </tr>
+ <tr>
+ <td>Tenth item</td>
+ <td>number: 10</td>
+ </tr>
+ <tr>
+ <td>Eleventh item</td>
+ <td>number: 11</td>
+ </tr>
+ </tbody>
+ </table>
+
+We can also set the batch position by using the batchStart value in a request.
+Note that we need the table ``prefix`` and column ``__name__`` like we use in
+the sorting concept:
+
+ >>> batchingRequest = TestRequest(form={'table-batchStart': '11',
+ ... 'table-batchSize': '5',
+ ... 'table-sortOn': 'table-number-1'})
+ >>> requestBatchingTable = BatchingTable(container, batchingRequest)
+
+Note; our table needs to start batching at smaller amount of items then we
+have by default otherwise we don't get a batch:
+
+ >>> requestBatchingTable.startBatchingAt = 5
+ >>> requestBatchingTable.update()
+ >>> print requestBatchingTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>My items</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Twelfth item</td>
+ <td>number: 12</td>
+ </tr>
+ <tr>
+ <td>Thirteenth item</td>
+ <td>number: 13</td>
+ </tr>
+ <tr>
+ <td>Fourteenth item</td>
+ <td>number: 14</td>
+ </tr>
+ <tr>
+ <td>Fifteenth item</td>
+ <td>number: 15</td>
+ </tr>
+ <tr>
+ <td>Sixteenth item</td>
+ <td>number: 16</td>
+ </tr>
+ </tbody>
+ </table>
Property changes on: z3c.table/trunk/src/z3c/table/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/src/z3c/table/__init__.py
===================================================================
--- z3c.table/trunk/src/z3c/table/__init__.py (rev 0)
+++ z3c.table/trunk/src/z3c/table/__init__.py 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1 @@
+# make a package
Property changes on: z3c.table/trunk/src/z3c/table/__init__.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/src/z3c/table/column.py
===================================================================
--- z3c.table/trunk/src/z3c/table/column.py (rev 0)
+++ z3c.table/trunk/src/z3c/table/column.py 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,258 @@
+##############################################################################
+#
+# 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.interface
+import zope.component
+import zope.location
+import zope.i18nmessageid
+from zope.dublincore.interfaces import IZopeDublinCore
+from zope.security.interfaces import Unauthorized
+from zope.traversing import api
+
+from z3c.table import interfaces
+
+_ = zope.i18nmessageid.MessageFactory('z3c')
+
+
+def addColumn(self, class_, name, cellRenderer=None, headCellRenderer=None,
+ colspan= None, weight=None, header=None, cssClasses=None, **kws):
+ if not interfaces.IColumn.implementedBy(class_):
+ raise ValueError('class_ %s must implement IColumn.' % class_)
+ column = class_(self.context, self.request, self)
+ column.__parent__ = self
+ column.__name__ = name
+ if cellRenderer is not None:
+ # overload method
+ column.renderCell = cellRenderer
+ if headCellRenderer is not None:
+ # overload method
+ column.renderHeadCell = headCellRenderer
+ if colspan is not None:
+ column.colspan = colspan
+ if weight is not None:
+ column.weight = weight
+ if header is not None:
+ column.header = header
+ if cssClasses is not None:
+ column.cssClasses = cssClasses
+ for name, value in kws.items():
+ setattr(column, name, value)
+ return column
+
+
+def safeGetAttr(obj, attr, default):
+ try:
+ return getattr(obj, attr, default)
+ except Unauthorized:
+ return default
+
+
+class Column(zope.location.Location):
+ """Column provider."""
+
+ zope.interface.implements(interfaces.IColumn)
+
+ # variables will be set by table
+ id = None
+
+ # customize this part if needed
+ colspan = 0
+ weight = 0
+ header = u''
+ cssClasses = {}
+
+ def __init__(self, context, request, table):
+ self.__parent__ = context
+ self.context = context
+ self.request = request
+ self.table = table
+
+ def update(self):
+ pass
+
+ def getColspan(self, item):
+ """Returns the colspan value."""
+ return self.colspan
+
+ def getSortKey(self, item):
+ """Returns the sort key used for column sorting."""
+ return self.renderCell(item)
+
+ def renderHeadCell(self):
+ """Header cell content."""
+ return self.header
+
+ def renderCell(self, item):
+ """Cell content."""
+ raise NotImplementedError('Subclass must implement renderCell')
+
+ def __repr__(self):
+ return '<%s %r>' % (self.__class__.__name__, self.__name__)
+
+
+class NoneCell(Column):
+ """None cell is used for mark a previous colspan."""
+
+ zope.interface.implements(interfaces.INoneCell)
+
+ def getColspan(self, item):
+ return 0
+
+ def renderHeadCell(self):
+ return u''
+
+ def renderCell(self, item):
+ return u''
+
+
+# predefined columns
+
+class NameColumn(Column):
+ """Name column."""
+
+ header = _('Name')
+
+ def renderCell(self, item):
+ return api.getName(item)
+
+
+class RadioColumn(Column):
+ """Radio column."""
+
+ header = _('X')
+ selectedItem = None
+
+ def getSortKey(self, item):
+ return api.getName(item)
+
+ def getItemKey(self, item):
+ return '%s-selectedItem' % self.id
+
+ def getItemValue(self, item):
+ return api.getName(item)
+
+ def update(self):
+ items = [item for item in self.table.values
+ if self.getItemValue(item) in self.request.get(
+ self.getItemKey(item), [])]
+ if len(items):
+ self.selectedItem = items.pop()
+ self.table.selectedItems = [self.selectedItem]
+
+ def renderCell(self, item):
+ selected = u''
+ if item == self.selectedItem:
+ selected='checked="checked"'
+ return u'<input type="radio" name="%s" value="%s" %s />' %(
+ self.getItemKey(item), self.getItemValue(item), selected)
+
+
+class CheckBoxColumn(Column):
+ """Checkbox column."""
+
+ header = _('X')
+ weight = 10
+ selectedItems = []
+
+ def getSortKey(self, item):
+ return api.getName(item)
+
+ def getItemKey(self, item):
+ return '%s-selectedItems' % self.id
+
+ def getItemValue(self, item):
+ return api.getName(item)
+
+ def update(self):
+ self.selectedItems = [item for item in self.table.values
+ if self.getItemValue(item)
+ in self.request.get(
+ self.getItemKey(item), [])]
+ self.table.selectedItems = self.selectedItems
+
+ def renderCell(self, item):
+ selected = u''
+ if item in self.selectedItems:
+ selected='checked="checked"'
+ return u'<input type="checkbox" name="%s" value="%s" %s />' %(
+ self.getItemKey(item), self.getItemValue(item),
+ selected)
+
+
+class GetAttrColumn(Column):
+ """Get attribute column."""
+
+ attrName = None
+ defaultValue = u''
+
+ def getValue(self, obj):
+ if obj is not None and self.attrName is not None:
+ return safeGetAttr(obj, self.attrName, self.defaultValue)
+ return self.defaultValue
+
+
+class FormatterColumn(Column):
+ """Formatter column."""
+
+ formatterCategory = u'dateTime'
+ formatterLength = u'medium'
+ formatterName = None
+ formatterCalendar = u'gregorian'
+
+ def getFormatter(self):
+ return self.request.locale.dates.getFormatter(
+ self.formatterCategory, self.formatterLength, self.formatterName,
+ self.formatterCalendar)
+
+
+class CreatedColumn(FormatterColumn, GetAttrColumn):
+ """Created date column."""
+
+ header = _('Created')
+ weight = 100
+
+ formatterCategory = u'dateTime'
+ formatterLength = u'short'
+ attrName = 'created'
+
+ def renderCell(self, item):
+ formatter = self.getFormatter()
+ dc = IZopeDublinCore(item, None)
+ value = self.getValue(dc)
+ if value:
+ value = formatter.format(value)
+ return value
+
+
+class ModifiedColumn(FormatterColumn, GetAttrColumn):
+ """Created date column."""
+
+ header = _('Modified')
+ weight = 110
+
+ formatterCategory = u'dateTime'
+ formatterLength = u'short'
+ attrName = 'modified'
+
+ def renderCell(self, item):
+ formatter = self.getFormatter()
+ dc = IZopeDublinCore(item, None)
+ value = self.getValue(dc)
+ if value:
+ value = formatter.format(value)
+ return value
Property changes on: z3c.table/trunk/src/z3c/table/column.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/src/z3c/table/column.txt
===================================================================
--- z3c.table/trunk/src/z3c/table/column.txt (rev 0)
+++ z3c.table/trunk/src/z3c/table/column.txt 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,425 @@
+======
+Column
+======
+
+Let's show the different columns we offer by default. But first take a look at
+the README.txt which explains the Table and Column concepts.
+
+
+Sample data setup
+-----------------
+
+Let's create a sample container which we can use as our iterable context:
+
+ >>> from zope.app.container import btree
+ >>> class Container(btree.BTreeContainer):
+ ... """Sample container."""
+ >>> container = Container()
+
+and create a sample content object which we use as container item:
+
+ >>> class Content(object):
+ ... """Sample content."""
+ ... def __init__(self, title, number):
+ ... self.title = title
+ ... self.number = number
+
+Now setup some items:
+
+ >>> 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)
+
+Let's also create a simple number sortable cloumn:
+
+ >>> from z3c.table import column
+ >>> class NumberColumn(column.Column):
+ ...
+ ... header = u'Number'
+ ... weight = 20
+ ...
+ ... def getSortKey(self, item):
+ ... return item.number
+ ...
+ ... def renderCell(self, item):
+ ... return 'number: %s' % item.number
+
+
+NameColumn
+----------
+
+Let's define a table using the NameColumn:
+
+ >>> from z3c.table import table
+ >>> class NameTable(table.Table):
+ ...
+ ... def setUpColumns(self):
+ ... return [
+ ... column.addColumn(self, column.NameColumn, u'name',
+ ... weight=1),
+ ... column.addColumn(self, NumberColumn, name=u'number',
+ ... weight=2, header=u'Number')
+ ... ]
+
+Now create, update and render our table and you can see that the NameColumn
+renders the name of the item using the zope.traversing.api.getName() concept:
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
+ >>> nameTable = NameTable(container, request)
+ >>> nameTable.update()
+ >>> print nameTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>first</td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td>fourth</td>
+ <td>number: 4</td>
+ </tr>
+ <tr>
+ <td>second</td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td>third</td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td>zero</td>
+ <td>number: 0</td>
+ </tr>
+ </tbody>
+ </table>
+
+
+RadioColumn
+-----------
+
+Let's define a table using the RadioColumn:
+
+ >>> class RadioTable(table.Table):
+ ...
+ ... def setUpColumns(self):
+ ... return [
+ ... column.addColumn(self, column.RadioColumn, u'radioColumn',
+ ... weight=1),
+ ... column.addColumn(self, NumberColumn, name=u'number',
+ ... weight=2, header=u'Number')
+ ... ]
+
+Now create, update and render our table:
+
+ >>> request = TestRequest()
+ >>> radioTable = RadioTable(container, request)
+ >>> radioTable.update()
+ >>> print radioTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>X</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><input type="radio" name="table-radioColumn-0-selectedItem" value="first" /></td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td><input type="radio" name="table-radioColumn-0-selectedItem" value="fourth" /></td>
+ <td>number: 4</td>
+ </tr>
+ <tr>
+ <td><input type="radio" name="table-radioColumn-0-selectedItem" value="second" /></td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td><input type="radio" name="table-radioColumn-0-selectedItem" value="third" /></td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td><input type="radio" name="table-radioColumn-0-selectedItem" value="zero" /></td>
+ <td>number: 0</td>
+ </tr>
+ </tbody>
+ </table>
+
+As you can see, we can force to render the radio input field as selected with a
+given request value:
+
+ >>> radioRequest = TestRequest(form={'table-radioColumn-0-selectedItem': 'third'})
+ >>> radioTable = RadioTable(container, radioRequest)
+ >>> radioTable.update()
+ >>> print radioTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>X</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><input type="radio" name="table-radioColumn-0-selectedItem" value="first" /></td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td><input type="radio" name="table-radioColumn-0-selectedItem" value="fourth" /></td>
+ <td>number: 4</td>
+ </tr>
+ <tr>
+ <td><input type="radio" name="table-radioColumn-0-selectedItem" value="second" /></td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td><input type="radio" name="table-radioColumn-0-selectedItem" value="third" checked="checked" /></td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td><input type="radio" name="table-radioColumn-0-selectedItem" value="zero" /></td>
+ <td>number: 0</td>
+ </tr>
+ </tbody>
+ </table>
+
+
+CheckBoxColumn
+--------------
+
+Let's define a table using the RadioColumn:
+
+ >>> from z3c.table import table
+ >>> class CheckBoxTable(table.Table):
+ ...
+ ... def setUpColumns(self):
+ ... return [
+ ... column.addColumn(self, column.CheckBoxColumn, u'checkBoxColumn',
+ ... weight=1),
+ ... column.addColumn(self, NumberColumn, name=u'number',
+ ... weight=2, header=u'Number')
+ ... ]
+
+Now create, update and render our table:
+
+
+ >>> request = TestRequest()
+ >>> checkBoxTable = CheckBoxTable(container, request)
+ >>> checkBoxTable.update()
+ >>> print checkBoxTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>X</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="first" /></td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="fourth" /></td>
+ <td>number: 4</td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="second" /></td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="third" /></td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="zero" /></td>
+ <td>number: 0</td>
+ </tr>
+ </tbody>
+ </table>
+
+And again you can set force to render the checkbox input field as selected with
+a given request value:
+
+ >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':
+ ... ['first', 'third']})
+ >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)
+ >>> checkBoxTable.update()
+ >>> print checkBoxTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>X</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
+ <td>number: 1</td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="fourth" /></td>
+ <td>number: 4</td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="second" /></td>
+ <td>number: 2</td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="third" checked="checked" /></td>
+ <td>number: 3</td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="zero" /></td>
+ <td>number: 0</td>
+ </tr>
+ </tbody>
+ </table>
+
+If you select a row, you can also give them an additional CSS style. This could
+be used in combination with alternating ``even`` and ``odd`` styles:
+
+ >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':
+ ... ['first', 'third']})
+ >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)
+ >>> checkBoxTable.cssClasses = {'tr': 'tr'}
+ >>> checkBoxTable.cssClassSelected = u'selected'
+ >>> checkBoxTable.cssClassEven = u'even'
+ >>> checkBoxTable.cssClassOdd = u'odd'
+ >>> checkBoxTable.update()
+ >>> print checkBoxTable.render()
+ <table>
+ <thead>
+ <tr class="tr">
+ <th>X</th>
+ <th>Number</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="selected even tr">
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
+ <td>number: 1</td>
+ </tr>
+ <tr class="odd tr">
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="fourth" /></td>
+ <td>number: 4</td>
+ </tr>
+ <tr class="even tr">
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="second" /></td>
+ <td>number: 2</td>
+ </tr>
+ <tr class="selected odd tr">
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="third" checked="checked" /></td>
+ <td>number: 3</td>
+ </tr>
+ <tr class="even tr">
+ <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="zero" /></td>
+ <td>number: 0</td>
+ </tr>
+ </tbody>
+ </table>
+
+
+CreatedColumn
+-------------
+
+Let's define a table using the CreatedColumn:
+
+ >>> from z3c.table import table
+ >>> class CreatedColumnTable(table.Table):
+ ...
+ ... def setUpColumns(self):
+ ... return [
+ ... column.addColumn(self, column.CreatedColumn, u'createdColumn',
+ ... weight=1),
+ ... ]
+
+Now create, update and render our table. Note, we use a dublin core stub
+adapter which only returns ``01/01/01 01:01`` as created date:
+
+ >>> request = TestRequest()
+ >>> createdColumnTable = CreatedColumnTable(container, request)
+ >>> createdColumnTable.update()
+ >>> print createdColumnTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Created</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>01/01/01 01:01</td>
+ </tr>
+ <tr>
+ <td>01/01/01 01:01</td>
+ </tr>
+ <tr>
+ <td>01/01/01 01:01</td>
+ </tr>
+ <tr>
+ <td>01/01/01 01:01</td>
+ </tr>
+ <tr>
+ <td>01/01/01 01:01</td>
+ </tr>
+ </tbody>
+ </table>
+
+
+ModifiedColumn
+--------------
+
+Let's define a table using the CreatedColumn:
+
+ >>> from z3c.table import table
+ >>> class ModifiedColumnTable(table.Table):
+ ...
+ ... def setUpColumns(self):
+ ... return [
+ ... column.addColumn(self, column.ModifiedColumn,
+ ... u'modifiedColumn', weight=1),
+ ... ]
+
+Now create, update and render our table. Note, we use a dublin core stub
+adapter which only returns ``02/02/02 02:02`` as modified date:
+
+ >>> request = TestRequest()
+ >>> modifiedColumnTable = ModifiedColumnTable(container, request)
+ >>> modifiedColumnTable.update()
+ >>> print modifiedColumnTable.render()
+ <table>
+ <thead>
+ <tr>
+ <th>Modified</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>02/02/02 02:02</td>
+ </tr>
+ <tr>
+ <td>02/02/02 02:02</td>
+ </tr>
+ <tr>
+ <td>02/02/02 02:02</td>
+ </tr>
+ <tr>
+ <td>02/02/02 02:02</td>
+ </tr>
+ <tr>
+ <td>02/02/02 02:02</td>
+ </tr>
+ </tbody>
+ </table>
Property changes on: z3c.table/trunk/src/z3c/table/column.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/src/z3c/table/configure.zcml
===================================================================
--- z3c.table/trunk/src/z3c/table/configure.zcml (rev 0)
+++ z3c.table/trunk/src/z3c/table/configure.zcml 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,7 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ i18n_domain="z3c">
+
+
+
+</configure>
Property changes on: z3c.table/trunk/src/z3c/table/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/src/z3c/table/interfaces.py
===================================================================
--- z3c.table/trunk/src/z3c/table/interfaces.py (rev 0)
+++ z3c.table/trunk/src/z3c/table/interfaces.py 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,228 @@
+##############################################################################
+#
+# 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.interface
+import zope.schema
+import zope.i18nmessageid
+
+from zope.contentprovider.interfaces import IContentProvider
+
+_ = zope.i18nmessageid.MessageFactory('z3c')
+
+
+class ITable(IContentProvider):
+ """Table provider"""
+
+ columnCounter = zope.schema.Int(
+ title=_(u'Column counter'),
+ description=_(u'Column counter'),
+ default=0)
+
+ columnIndexById = zope.interface.Attribute(
+ 'Dict of column index number by id')
+
+ columnByName = zope.interface.Attribute('Dict of columns by name')
+
+ columns = zope.interface.Attribute('Sequence of columns')
+
+ rows = zope.interface.Attribute('Sequence of rows')
+
+ selectedItems = zope.interface.Attribute('Sequence of selected items')
+
+ # customize this part if needed
+ prefix = zope.schema.BytesLine(
+ title=_('Prefix'),
+ description=_('The prefix of the table used to uniquely identify it.'),
+ default='table')
+
+ # css classes
+ cssClasses = zope.interface.Attribute(
+ 'Dict of element name and CSS classes')
+
+ # additional (row) css
+ cssClassEven = zope.schema.TextLine(
+ title=u'Even css row class',
+ description=(u'CSS class for even rows.'),
+ default=u'even',
+ required=False)
+
+ cssClassOdd = zope.schema.TextLine(
+ title=u'Odd css row class',
+ description=(u'CSS class for odd rows.'),
+ default=u'odd',
+ required=False)
+
+ cssClassSelected = zope.schema.TextLine(
+ title=u'Selected css row class',
+ description=(u'CSS class for selected rows.'),
+ default=u'selected',
+ required=False)
+
+ # sort attributes
+ sortOn = zope.schema.Int(
+ title=_(u'Sort on table index'),
+ description=_(u'Sort on table index'),
+ default=0)
+
+ sortOrder = zope.schema.TextLine(
+ title=_(u'Sort order'),
+ description=_(u'Row sort order'),
+ default=u'ascending')
+
+ reverseSortOrderNames = zope.schema.List(
+ title=u'Selected css row class',
+ description=(u'CSS class for selected rows.'),
+ value_type=zope.schema.TextLine(
+ title=_(u'Reverse sort order name'),
+ description=_(u'Reverse sort order name')
+ ),
+ default=[u'descending', u'reverse', u'down'],
+ required=False)
+
+ # batch attributes
+ batchStart = zope.schema.Int(
+ title=_(u'Batch start index'),
+ description=_(u'Index the batch starts with'),
+ default=0)
+
+ batchSize = zope.schema.Int(
+ title=_(u'Batch size'),
+ description=_(u'The batch size'),
+ default=50)
+
+ startBatchingAt = zope.schema.Int(
+ title=_(u'Batch start size'),
+ description=_(u'The minimal size the batch starts to get used'),
+ default=50)
+
+ values = zope.interface.Attribute('Iterable table row data sequence.')
+
+ def getCSSClass(element, cssClass=None):
+ """Returns the css class if any or an empty string."""
+
+ def setUpColumns():
+ """Setup table column renderer."""
+
+ def updateColumns():
+ """Update columns."""
+
+ def orderColumns():
+ """Order columns."""
+
+ def setUpRow(item):
+ """Setup table row."""
+
+ def setUpRows():
+ """Setup table rows."""
+
+ def getSortOn():
+ """Returns sort on column id."""
+
+ def getSortOrder():
+ """Returns sort order criteria."""
+
+ def sortRows():
+ """Sort rows."""
+
+ def getBatchSize():
+ """Returns the batch size."""
+
+ def getBatchStart():
+ """Returns the batch start index."""
+
+ def batchRows():
+ """Batch rows."""
+
+ def isSelectedRow(row):
+ """Returns True for selected row."""
+
+ def renderTable():
+ """Render the table."""
+
+ def renderHead():
+ """Render the thead."""
+
+ def renderHeadRow():
+ """Render the table header rows."""
+
+ def renderHeadCell(column):
+ """Setup the table header rows."""
+
+ def renderBody():
+ """Render the table body."""
+
+ def renderRows():
+ """Render the table body rows."""
+
+ def renderRow(row, cssClass=None):
+ """Render the table body rows."""
+
+ def renderCell(item, column, colspan=0):
+ """Render a single table body cell."""
+
+ def render():
+ """Plain render method without keyword arguments."""
+
+
+class ISequenceTable(ITable):
+ """Sequence table adapts a sequence as context.
+
+ This table can be used for adapting a z3c.indexer.search.ResultSet or
+ z3c.batching.batch.Batch instance as context. Batch which wraps a
+ ResultSet sequence.
+ """
+
+
+class IColumn(zope.interface.Interface):
+ """Column provider"""
+
+ id = zope.schema.TextLine(
+ title = _(u'Id'),
+ description = _(u'The column id'),
+ default = None)
+
+ # customize this part if needed
+ colspan = zope.schema.Int(
+ title = _(u'Colspan'),
+ description = _(u'The colspan value'),
+ default = 0)
+
+ weight = zope.schema.Int(
+ title = _(u'Weight'),
+ description = _(u'The column weight'),
+ default = 0)
+
+ header = zope.schema.TextLine(
+ title = _(u'Header name'),
+ description = _(u'The header name'),
+ default = u'')
+
+ cssClasses = zope.interface.Attribute('Dict of element name and CSS classes')
+
+ def getColspan(item):
+ """Colspan value based on the given item."""
+
+ def renderHeadCell():
+ """Render the column header label."""
+
+ def renderCell(item):
+ """Render the column content."""
+
+
+class INoneCell(IColumn):
+ """None cell used for colspan."""
Property changes on: z3c.table/trunk/src/z3c/table/interfaces.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/src/z3c/table/table.py
===================================================================
--- z3c.table/trunk/src/z3c/table/table.py (rev 0)
+++ z3c.table/trunk/src/z3c/table/table.py 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,324 @@
+##############################################################################
+#
+# 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"
+
+from xml.sax.saxutils import quoteattr
+
+import zope.interface
+import zope.component
+import zope.location
+
+from z3c.batching.batch import Batch
+from z3c.table import interfaces
+from z3c.table import column
+
+
+def getWeight(column):
+ try:
+ return int(column.weight)
+ except AttributeError:
+ return 0
+
+
+def getSortMethod(idx):
+ def getSortKey(item):
+ sublist = item[idx]
+ def getCloumnSortKey(sublist):
+ return sublist[1].getSortKey(sublist[0])
+ return getCloumnSortKey(sublist)
+ return getSortKey
+
+
+def nameColumn(column, name):
+ """Give a column a __name__."""
+ column.__name__ = name
+ return column
+
+
+class Table(zope.location.Location):
+ """Generic usable table implementation."""
+
+ zope.interface.implements(interfaces.ITable)
+
+ # private variables will be set in update call
+ columnCounter = 0
+ columnIndexById = {}
+ columnByName = {}
+ columns = None
+ rows = None
+ selectedItems = []
+
+ # customize this part if needed
+ prefix = 'table'
+
+ # css classes
+ cssClasses = {}
+ # additional (row) css
+ cssClassEven = u''
+ cssClassOdd = u''
+ cssClassSelected = u''
+
+ # sort attributes
+ sortOn = 0
+ sortOrder = u'ascending'
+ reverseSortOrderNames = [u'descending', u'reverse', u'down']
+
+ # batch attributes
+ batchStart = 0
+ batchSize = 50
+ startBatchingAt = 50
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def getCSSClass(self, element, cssClass=None):
+ klass = self.cssClasses.get(element)
+ if klass and cssClass:
+ klass = '%s %s' % (cssClass, klass)
+ elif cssClass:
+ klass = cssClass
+ return klass and ' class=%s' % quoteattr(klass) or ''
+
+ def renderBatch(self):
+ return u'implement render batch'
+
+ @property
+ def values(self):
+ return self.context.values()
+
+# setup
+
+ def setUpColumns(self):
+ cols = list(zope.component.getAdapters((self.context, self.request, self),
+ interfaces.IColumn))
+ # use the adapter name as column name
+ return [nameColumn(col, name) for name, col in cols]
+
+ def updateColumns(self):
+ for col in self.columns:
+ col.update()
+
+ def orderColumns(self):
+ self.columns = sorted(self.columns, key=getWeight)
+ for col in self.columns:
+ self.columnByName[col.__name__] = col
+ idx = self.columnCounter
+ col.id = '%s-%s-%s' % (self.prefix, col.__name__, idx)
+ self.columnIndexById[col.id] = idx
+ self.columnCounter += 1
+
+ def setUpRow(self, item):
+ cols = []
+ append = cols.append
+ colspanCounter = 0
+ countdown = len(self.columns)
+ counter = 0
+ for col in self.columns:
+ countdown -= 1
+ colspan = 0
+ if colspanCounter == 0:
+ colspan = colspanCounter = col.getColspan(item)
+ # adjust colspan because we define 0, 2, 3, etc.
+ if colspanCounter > 0:
+ colspanCounter -=1
+
+ if colspan == 0 and colspanCounter > 0:
+ # override col if colspan is 0 and colspan coutner not 0
+ colspanCounter -=1
+ colspan = 0
+ # now we are ready to setup dummy colspan cells
+ col = column.NoneCell(self.context, self.request, self)
+
+ # we reached the end of the table and have still colspan
+ if (countdown - colspan) < 0:
+ raise ValueError(
+ "Colspan for column '%s' larger then table." % col)
+
+ append((item, col, colspan))
+ return cols
+
+ def setUpRows(self):
+ return [self.setUpRow(item) for item in self.values]
+
+# sort
+
+ def getSortOn(self):
+ """Returns sort on column id."""
+ return self.request.get(self.prefix +'-sortOn', self.sortOn)
+
+ def getSortOrder(self):
+ """Returns sort order criteria."""
+ return self.request.get(self.prefix +'-sortOrder',
+ self.sortOrder)
+
+ def sortRows(self):
+ if self.sortOn is not None and self.rows and self.columns:
+ sortOnIdx = self.columnIndexById.get(self.sortOn, 0)
+ sortKeyGetter = getSortMethod(sortOnIdx)
+ rows = sorted(self.rows, key=sortKeyGetter)
+ if self.sortOrder in self.reverseSortOrderNames:
+ rows.reverse()
+ self.rows = rows
+
+# batch
+
+ def getBatchSize(self):
+ return int(self.request.get(self.prefix +'-batchSize',
+ self.batchSize))
+
+ def getBatchStart(self):
+ return int(self.request.get(self.prefix +'-batchStart',
+ self.batchStart))
+
+ def batchRows(self):
+ if len(self.rows) > self.startBatchingAt:
+ self.rows = Batch(self.rows, start=self.batchStart,
+ size=self.batchSize)
+
+ def isSelectedRow(self, row):
+ item, col, colspan = row[0]
+ if item in self.selectedItems:
+ return True
+ return False
+
+# render
+
+ def renderTable(self):
+ if self.columns:
+ cssClass = self.getCSSClass('table')
+ head = self.renderHead()
+ body = self.renderBody()
+ return '<table%s>%s%s\n</table>' % (cssClass, head, body)
+ return u''
+
+ def renderHead(self):
+ cssClass = self.getCSSClass('thead')
+ rStr = self.renderHeadRow()
+ if rStr:
+ return '\n <thead%s>%s\n </thead>' % (cssClass, rStr)
+ return u''
+
+ def renderHeadRow(self):
+ cssClass = self.getCSSClass('tr')
+ cells = [self.renderHeadCell(col) for col in self.columns]
+ if cells:
+ return u'\n <tr%s>%s\n </tr>' % (cssClass, u''.join(cells))
+ return u''
+
+ def renderHeadCell(self, column):
+ cssClass = column.cssClasses.get('th')
+ cssClass = self.getCSSClass('th', cssClass)
+ if interfaces.INoneCell.providedBy(column):
+ return u''
+ return u'\n <th%s>%s</th>' % (cssClass, column.renderHeadCell())
+
+ def renderBody(self):
+ cssClass = self.getCSSClass('tbody')
+ rStr = self.renderRows()
+ if rStr:
+ return '\n <tbody%s>%s\n </tbody>' % (cssClass, rStr)
+ return u''
+
+ def renderRows(self):
+ counter = 0
+ rows = []
+ cssClasses = (self.cssClassEven, self.cssClassOdd)
+ append = rows.append
+ for row in self.rows:
+ append(self.renderRow(row, cssClasses[counter % 2]))
+ counter +=1
+ return u''.join(rows)
+
+ def renderRow(self, row, cssClass=None):
+ isSelected = self.isSelectedRow(row)
+ if isSelected and self.cssClassSelected and cssClass:
+ cssClass = '%s %s' % (self.cssClassSelected, cssClass)
+ elif isSelected and self.cssClassSelected:
+ cssClass = self.cssClassSelected
+ cssClass = self.getCSSClass('tr', cssClass)
+
+ cells = [self.renderCell(item, col, colspan)
+ for item, col, colspan in row]
+ if cells:
+ return u'\n <tr%s>%s\n </tr>' % (cssClass, u''.join(cells))
+ return u''
+
+ def renderCell(self, item, column, colspan=0):
+ cssClass = column.cssClasses.get('td')
+ cssClass = self.getCSSClass('td', cssClass)
+ colspanStr = colspan and ' colspan="%s"' % colspan or ''
+ if interfaces.INoneCell.providedBy(column):
+ return u''
+ return u'\n <td%s%s>%s</td>' % (cssClass, colspanStr,
+ column.renderCell(item))
+
+ def update(self):
+ # reset values
+ self.columnCounter = 0
+ self.columnByIndex = {}
+ self.selectedItems = []
+
+ # use batch values from request or the existing ones
+ self.batchSize = self.getBatchSize()
+ self.batchStart = self.getBatchStart()
+ # use srting values from request or the existing ones
+ self.sortOn = self.getSortOn()
+ self.sortOrder = self.getSortOrder()
+
+ # setup columns
+ self.columns = self.setUpColumns()
+
+ # order columns
+ self.orderColumns()
+
+ # update columns
+ self.updateColumns()
+
+ # setup headers bsaed on columns
+ self.rows = self.setUpRows()
+
+ # sort items on columns
+ self.sortRows()
+
+ # batch sorted rows
+ self.batchRows()
+
+ def render(self):
+
+ # allow to use a template for rendering the table, this will allow
+ # to position the batch before and after the table
+
+ return self.renderTable()
+
+ def __repr__(self):
+ return '<%s %r>' % (self.__class__.__name__, self.__name__)
+
+
+class SequenceTable(Table):
+ """Sequence table adapts a sequence as context.
+
+ This table can be used for adapting a z3c.indexer.search.ResultSet or
+ z3c.batching.batch.Batch instance as context. Batch which wraps a
+ ResultSet sequence.
+ """
+
+ zope.interface.implements(interfaces.ISequenceTable)
+
+ @property
+ def values(self):
+ return self.context
Property changes on: z3c.table/trunk/src/z3c/table/table.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/src/z3c/table/testing.py
===================================================================
--- z3c.table/trunk/src/z3c/table/testing.py (rev 0)
+++ z3c.table/trunk/src/z3c/table/testing.py 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,54 @@
+##############################################################################
+#
+# 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 datetime
+import zope.interface
+import zope.component
+from zope.dublincore.interfaces import IZopeDublinCore
+from zope.security import checker
+from zope.app.testing import setup
+
+
+class DublinCoreAdapterStub(object):
+ """Dublin core adapter stub."""
+
+ zope.interface.implements(IZopeDublinCore)
+ zope.component.adapts(zope.interface.Interface)
+
+ __Security_checker__ = checker.Checker(
+ {"created": "zope.Public",
+ "modified": "zope.Public",
+ "title": "zope.Public",
+ },
+ {"title": "zope.app.dublincore.change"})
+
+ def __init__(self, context):
+ pass
+ title = 'faux title'
+ size = 1024
+ created = datetime.datetime(2001, 1, 1, 1, 1, 1)
+ modified = datetime.datetime(2002, 2, 2, 2, 2, 2)
+
+
+def setUp(test):
+ test.globs = {'root': setup.placefulSetUp(True)}
+ zope.component.provideAdapter(DublinCoreAdapterStub)
+
+
+def tearDown(test):
+ setup.placefulTearDown()
Property changes on: z3c.table/trunk/src/z3c/table/testing.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.table/trunk/src/z3c/table/tests.py
===================================================================
--- z3c.table/trunk/src/z3c/table/tests.py (rev 0)
+++ z3c.table/trunk/src/z3c/table/tests.py 2008-02-13 11:47:35 UTC (rev 83790)
@@ -0,0 +1,135 @@
+##############################################################################
+#
+# 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 datetime
+import zope.interface
+import zope.component
+from zope.dublincore.interfaces import IZopeDublinCore
+from zope.testing import doctest
+from zope.publisher.browser import TestRequest
+from zope.security import checker
+from zope.app.testing import setup
+
+import z3c.testing
+from z3c.table import testing
+from z3c.table import interfaces
+from z3c.table import table
+from z3c.table import column
+
+
+# table
+class TestTable(z3c.testing.InterfaceBaseTest):
+
+ def getTestInterface(self):
+ return interfaces.ITable
+
+ def getTestClass(self):
+ return table.Table
+
+ def getTestPos(self):
+ return ({}, TestRequest())
+
+
+# column
+class TestColumn(z3c.testing.InterfaceBaseTest):
+
+ def getTestInterface(self):
+ return interfaces.IColumn
+
+ def getTestClass(self):
+ return column.Column
+
+ def getTestPos(self):
+ t = table.Table(None, TestRequest())
+ return ({}, TestRequest(), t)
+
+
+class TestNoneCell(z3c.testing.InterfaceBaseTest):
+
+ def getTestInterface(self):
+ return interfaces.INoneCell
+
+ def getTestClass(self):
+ return column.NoneCell
+
+ def getTestPos(self):
+ t = table.Table(None, TestRequest())
+ return ({}, TestRequest(), t)
+
+
+class TestNameColumn(z3c.testing.InterfaceBaseTest):
+
+ def getTestInterface(self):
+ return interfaces.IColumn
+
+ def getTestClass(self):
+ return column.NameColumn
+
+ def getTestPos(self):
+ t = table.Table(None, TestRequest())
+ return ({}, TestRequest(), t)
+
+
+class TestRadioColumn(z3c.testing.InterfaceBaseTest):
+
+ def getTestInterface(self):
+ return interfaces.IColumn
+
+ def getTestClass(self):
+ return column.RadioColumn
+
+ def getTestPos(self):
+ t = table.Table(None, TestRequest())
+ return ({}, TestRequest(), t)
+
+
+class TestCheckBoxColumn(z3c.testing.InterfaceBaseTest):
+
+ def getTestInterface(self):
+ return interfaces.IColumn
+
+ def getTestClass(self):
+ return column.CheckBoxColumn
+
+ def getTestPos(self):
+ t = table.Table(None, TestRequest())
+ return ({}, TestRequest(), t)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocFileSuite('README.txt',
+ setUp=testing.setUp, tearDown=testing.tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ doctest.DocFileSuite('column.txt',
+ setUp=testing.setUp, tearDown=testing.tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ unittest.makeSuite(TestTable),
+ unittest.makeSuite(TestColumn),
+ unittest.makeSuite(TestNoneCell),
+ unittest.makeSuite(TestNameColumn),
+ unittest.makeSuite(TestRadioColumn),
+ unittest.makeSuite(TestCheckBoxColumn),
+ ))
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Property changes on: z3c.table/trunk/src/z3c/table/tests.py
___________________________________________________________________
Name: svn:eol-style
+ native
More information about the Checkins
mailing list