r343 - / python-zc.table python-zc.table/branches
python-zc.table/branches/upstream
python-zc.table/branches/upstream/current
python-zc.table/branches/upstream/current/src
python-zc.table/branches/upstream/current/src/zc
python-zc.table/branches/upstream/current/src/zc/table
python-zc.table/branches/upstream/current/src/zc/table/resources
python-zc.table/branches/upstream/current/src/zc.table.egg-info
Brian Sutherland
jinty-guest at costa.debian.org
Wed Oct 25 11:07:24 UTC 2006
Author: jinty-guest
Date: 2006-10-25 11:07:23 +0000 (Wed, 25 Oct 2006)
New Revision: 343
Added:
python-zc.table/
python-zc.table/branches/
python-zc.table/branches/upstream/
python-zc.table/branches/upstream/current/
python-zc.table/branches/upstream/current/Makefile
python-zc.table/branches/upstream/current/PKG-INFO
python-zc.table/branches/upstream/current/ZopePublicLicense.txt
python-zc.table/branches/upstream/current/setup.py
python-zc.table/branches/upstream/current/src/
python-zc.table/branches/upstream/current/src/zc.table.egg-info/
python-zc.table/branches/upstream/current/src/zc.table.egg-info/PKG-INFO
python-zc.table/branches/upstream/current/src/zc.table.egg-info/SOURCES.txt
python-zc.table/branches/upstream/current/src/zc.table.egg-info/dependency_links.txt
python-zc.table/branches/upstream/current/src/zc.table.egg-info/namespace_packages.txt
python-zc.table/branches/upstream/current/src/zc.table.egg-info/not-zip-safe
python-zc.table/branches/upstream/current/src/zc.table.egg-info/requires.txt
python-zc.table/branches/upstream/current/src/zc.table.egg-info/top_level.txt
python-zc.table/branches/upstream/current/src/zc/
python-zc.table/branches/upstream/current/src/zc/__init__.py
python-zc.table/branches/upstream/current/src/zc/table/
python-zc.table/branches/upstream/current/src/zc/table/README.txt
python-zc.table/branches/upstream/current/src/zc/table/SETUP.cfg
python-zc.table/branches/upstream/current/src/zc/table/TODO.txt
python-zc.table/branches/upstream/current/src/zc/table/__init__.py
python-zc.table/branches/upstream/current/src/zc/table/column.py
python-zc.table/branches/upstream/current/src/zc/table/column.txt
python-zc.table/branches/upstream/current/src/zc/table/configure.zcml
python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.py
python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.txt
python-zc.table/branches/upstream/current/src/zc/table/interfaces.py
python-zc.table/branches/upstream/current/src/zc/table/resources/
python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows.gif
python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_down.gif
python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_up.gif
python-zc.table/branches/upstream/current/src/zc/table/resources/sorting.js
python-zc.table/branches/upstream/current/src/zc/table/table.py
python-zc.table/branches/upstream/current/src/zc/table/testing.py
python-zc.table/branches/upstream/current/src/zc/table/tests.py
python-zc.table/branches/upstream/current/src/zc/table/zc.table-configure.zcml
python-zc.table/tags/
Log:
[svn-inject] Installing original source of python-zc.table
Added: python-zc.table/branches/upstream/current/Makefile
===================================================================
--- python-zc.table/branches/upstream/current/Makefile (rev 0)
+++ python-zc.table/branches/upstream/current/Makefile 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,19 @@
+PYVERS=2.4
+
+all:
+ # nothing for now
+
+clean: $(PYVERS:%=clean-python%)
+ rm -rf dist
+ rm -rf build
+ rm -rf src/*.egg-info
+
+clean-python%:
+ python$* setup.py clean
+
+.PHONY: dist
+dist: clean $(PYVERS:%=build-python%-egg)
+ python setup.py sdist
+
+build-python%-egg:
+ python$* setup.py bdist_egg
Property changes on: python-zc.table/branches/upstream/current/Makefile
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/PKG-INFO
===================================================================
--- python-zc.table/branches/upstream/current/PKG-INFO (rev 0)
+++ python-zc.table/branches/upstream/current/PKG-INFO 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,13 @@
+Metadata-Version: 1.0
+Name: zc.table
+Version: 0.5.1
+Summary: This is a Zope 3 extension that helps with the construction of (HTML) tables.
+Features include dynamic HTML table generation, batching and sorting.
+
+Home-page: UNKNOWN
+Author: Zope Project
+Author-email: zope3-dev at zope.org
+License: ZPL
+Description: UNKNOWN
+Keywords: zope zope3
+Platform: UNKNOWN
Added: python-zc.table/branches/upstream/current/ZopePublicLicense.txt
===================================================================
--- python-zc.table/branches/upstream/current/ZopePublicLicense.txt (rev 0)
+++ python-zc.table/branches/upstream/current/ZopePublicLicense.txt 2006-10-25 11:07:23 UTC (rev 343)
@@ -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: python-zc.table/branches/upstream/current/ZopePublicLicense.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/setup.py
===================================================================
--- python-zc.table/branches/upstream/current/setup.py (rev 0)
+++ python-zc.table/branches/upstream/current/setup.py 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,27 @@
+from setuptools import setup, find_packages
+
+setup(
+ name="zc.table",
+ version="0.5.1",
+ install_requires=['zc.resourcelibrary >= 0.5'],
+ dependency_links=['http://download.zope.org/distribution/',],
+ packages=find_packages('src', exclude=["*.tests", "*.ftests"]),
+
+ package_dir= {'':'src'},
+
+ namespace_packages=['zc'],
+ package_data = {
+ '': ['*.txt', '*.zcml', '*.gif', '*.js'],
+ 'zc.table':['resources/*'],
+ },
+
+ zip_safe=False,
+ author='Zope Project',
+ author_email='zope3-dev at zope.org',
+ description="""\
+This is a Zope 3 extension that helps with the construction of (HTML) tables.
+Features include dynamic HTML table generation, batching and sorting.
+""",
+ license='ZPL',
+ keywords="zope zope3",
+ )
Property changes on: python-zc.table/branches/upstream/current/setup.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/__init__.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/__init__.py (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/__init__.py 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,5 @@
+# this is a namespace package
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ pass
Property changes on: python-zc.table/branches/upstream/current/src/zc/__init__.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/README.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/README.txt (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/README.txt 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,1266 @@
+======
+Tables
+======
+
+Tables are general purpose UI constructs designed to simplify presenting
+tabular information. A table has a column set which collects columns and
+manages configuration data.
+
+We must register a faux resource directory in preparation::
+
+ >>> import zope.interface
+ >>> import zope.component
+ >>> import zope.publisher.interfaces
+ >>> @zope.component.adapter(zope.publisher.interfaces.IRequest)
+ ... @zope.interface.implementer(zope.interface.Interface)
+ ... def dummyResource(request):
+ ... return lambda:'/@@/zc.table'
+ ...
+ >>> zope.component.provideAdapter(dummyResource, name='zc.table')
+
+Columns
+=======
+
+``Columns`` have methods to render a header and the contents of a cell based on
+the item that occupies that cell. Here's a very simple example::
+
+ >>> from zope import interface
+ >>> from zc.table import interfaces
+ >>> class GetItemColumn:
+ ... interface.implements(interfaces.IColumn)
+ ... def __init__(self, title, name, attr):
+ ... self.title = title
+ ... self.name = name
+ ... self.attr = attr # This isn't part of IColumn
+ ... def renderHeader(self, formatter):
+ ... return self.title
+ ... def renderCell(self, item, formatter):
+ ... return str(getattr(item, self.attr))
+
+Note that the methods do not provide the <th> and <td> tags.
+
+The title is for display, while the name is for identifying the column within
+a collection of columns: a column name must be unique within a collection of
+columns used for a table formatter.
+
+`renderHeader` takes a formatter--the table formatter introduced in the section
+immediately below this one. It has the responsibility of rendering the
+contents of the header for the column. `renderCell` takes the item to be
+rendered and the formatter, and is responsible for returning the cell contents
+for the given item.
+
+The formatter is passed because it contains references to a number of useful
+values. The context and request are particularly important.
+
+Columns may also support sorting by implementing the ISortableColumn interface.
+This interface is comprised of two methods, `sort` and `reversesort`. Both
+take the same rather large set of arguments: items, formatter, start, stop,
+and sorters. At least two values should be unsurprising: the `items` are the
+items to be sorted, the `formatter` is the table formatter. The `start` and
+`stop` values are the values that are needed for the rendering, so some
+implementations may be able to optimize to only give precise results for the
+given range. The `sorters` are optional sub-sorters--callables with signatures
+identical to `sort` and `reversesort` that are a further sort refinement that
+an implementation may optionally ignore. If a column has two or
+more values that will sort identically, the column might take advantage of any
+sub-sorters to further sort the data.
+
+The columns.py file has a number of useful base column classes. The
+columns.txt file discusses some of them. For our examples here, we will use
+the relatively simple and versatile zc.table.column.GetterColumn. It is
+instantiated with two required values and two optional values::
+
+ title - (required) the title of the column.
+
+ getter - (required) a callable that is passed the item and the table
+ formatter; returns the value used in the cell.
+
+ cell_formatter - (optional) a callable that is passed the result of getter,
+ the item, and the table formatter; returns the formatted
+ HTML. defaults to a function that returns the result of
+ trying to convert the result to unicode.
+
+ name - (optional) the name of the column. The title is used if a name is
+ not specified.
+
+It includes a reasonably simple implementation of ISortableColumn but does
+not declare the interface itself. It tries to sort on the basis of the getter
+value and can be customized simply by overriding the `getSortKey` method.
+
+Let's import the GetterColumn and create some columns that we'll use later,
+and then verify that one of the columns fully implements IColumn. We'll also
+then declare that all three of them provide ISortableColumn and verify one of
+them::
+
+ >>> from zc.table.column import GetterColumn
+ >>> columns = (
+ ... GetterColumn(u'First', lambda i,f: i.a, subsort=True),
+ ... GetterColumn(u'Second', lambda i,f: i.b, subsort=True),
+ ... GetterColumn(u'Third', lambda i,f: i.c, subsort=True),
+ ... )
+ >>> import zope.interface.verify
+ >>> zope.interface.verify.verifyObject(interfaces.IColumn, columns[0])
+ True
+ >>> for c in columns:
+ ... interface.directlyProvides(c, interfaces.ISortableColumn)
+ ...
+ >>> zope.interface.verify.verifyObject(
+ ... interfaces.ISortableColumn, columns[0])
+ True
+
+Formatters
+==========
+
+When a sequence of objects are to be turned into an HTML table, a
+table.Formatter is used. The table package includes a simple implementation
+of IFormatter as well as a few important variations.
+
+The default Formatter is instantiated with three required arguments--
+`context`, `request`, and `items`--and a long string of optional arguments
+we'll discuss in a moment. The first two required arguments are reminiscent
+of browser views--and in fact, a table formatter is a specialized browser
+view. The `context` is the object for which the table formatter is being
+rendered, and can be important to various columns; and the `request` is the
+current request. The `items` are the full set of items on which the table will
+give a view.
+
+The first three optional arguments affect the display::
+
+ visible_column_names=None, batch_start=0, batch_size=0
+
+visible_column_names are a list of column names that should be displayed; note
+that even if a column is not visible, it may still affect other behavior such
+as sorting, discussed for a couple of Formatter subclasses below.
+
+batch_start is the item position the table should begin to render. batch_size
+is the number of items the table should render; 0 means all.
+
+The next optional argument, `prefix=None`, is particularly important when a
+table formatter is used within a form: it sets a prefix for any form fields
+and XML identifiers generated for the table or a contained element::
+
+The last optional argument is the full set of columns for the table (not just
+the ones curently visible). It is optional because it may be set instead as
+a subclass attribute: the value itself is required on instances.
+
+Lets create some data to format and instantiate the default Formatter.
+Our formatters won't need the context, so we'll fake it. As an
+exercise, we'll hide the second column.
+
+ >>> class DataItem:
+ ... def __init__(self, a, b, c):
+ ... self.a = a
+ ... self.b = b
+ ... self.c = c
+
+ >>> items = [DataItem('a0', 'b0', 'c0'),
+ ... DataItem('a2', 'b2', 'c2'),
+ ... DataItem('a1', 'b1', 'c1'),
+ ... ]
+ >>> from zc.table import table
+ >>> import zope.publisher.browser
+ >>> request = zope.publisher.browser.TestRequest()
+ >>> context = None
+ >>> formatter = table.Formatter(
+ ... context, request, items, visible_column_names=('First', 'Third'),
+ ... columns=columns)
+ >>> zope.interface.verify.verifyObject(
+ ... interfaces.IFormatter, formatter)
+ True
+
+The simplest way to use a table formatter is to call it, asking the formatter
+to render the entire table::
+
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+If you want more control over the output then you may want to call methods
+on the formatter that generate various parts of the output piecemeal. In
+particular, getRows, getHeaders, and getCells exist only for this sort of use.
+Here is an example of getRows in use to generate even and odd rows and a
+column with cells in a special class:
+
+ >>> html = '<table class="my_class">\n'
+ >>> html += '<tr class="header">\n'+ formatter.renderHeaders() + '</tr>\n'
+ >>> for index, row in enumerate(formatter.getRows()):
+ ... if index % 2:
+ ... html += '<tr class="even">'
+ ... else:
+ ... html += '<tr class="odd">'
+ ... for index, cell in enumerate(row):
+ ... if index == 0:
+ ... html += '<td class="first_column">'
+ ... else:
+ ... html += '<td>'
+ ... html += cell + '<td>'
+ ... html += '</tr>\n'
+ >>> html += '</table>'
+ >>> print html
+ <table class="my_class">
+ <tr class="header">
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ <tr class="odd"><td class="first_column">a0<td><td>c0<td></tr>
+ <tr class="even"><td class="first_column">a2<td><td>c2<td></tr>
+ <tr class="odd"><td class="first_column">a1<td><td>c1<td></tr>
+ </table>
+
+However, the formatter provides some simple support for style sheets, since it
+is the most common form of customization. Each formatter has an attribute
+called ``cssClasses``, which is a mapping from HTML elements to CSS
+classes. As you saw above, by default there are no CSS classes registered for
+the formatter. Let's now register one for the "table" element:
+
+ >>> formatter.cssClasses['table'] = 'list'
+ >>> print formatter()
+ <table class="list">
+ ...
+ </table>
+
+This can be done for every element used in the table. Of course, you can also
+unregister the class again:
+
+ >>> del formatter.cssClasses['table']
+ >>> print formatter()
+ <table>
+ ...
+ </table>
+
+If you are going to be doing a lot of this sort of thing (or if this approach
+is more your style), a subclass of Formatter might be in order--but that
+is jumping the gun a bit. See the section about subclasses below.
+
+Columns are typically defined for a class and reused across requests.
+Therefore, they have the request that columns need. They also have an
+`annotations` attribute that allows columns to stash away information that
+they need across method calls--for instance, an adapter that every single
+cell in a column--and maybe even across multiple columns--will need.
+
+ >>> formatter.annotations
+ {}
+
+Batching
+========
+
+As discussed above, ``Formatter`` instances can also batch. In order to
+batch, `items` must minimally be iterable and ideally support a slice syntax.
+batch_size and batch_start, introduced above, are the formatter values to use.
+Typically these are passed in on instantiation, but we'll change the attributes
+on the existing formatter.
+
+ >>> formatter.batch_size = 1
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ >>> formatter.batch_start=1
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+Fancy Columns
+=============
+
+It is easy to make columns be more sophisticated. For example, if we wanted
+a column that held content that was especially wide, we could do this::
+
+ >>> class WideColumn(GetterColumn):
+ ... def renderHeader(self, formatter):
+ ... return '<div style="width:200px">%s</div>' % (
+ ... super(WideColumn, self).renderHeader(formatter),)
+ >>> fancy_columns = (
+ ... WideColumn(u'First', lambda i,f: i.a),
+ ... GetterColumn(u'Second', lambda i,f: i.b),
+ ... GetterColumn(u'Third', lambda i,f: i.c),
+ ... )
+ >>> formatter = table.Formatter(
+ ... context, request, items, visible_column_names=('First', 'Third'),
+ ... columns=fancy_columns)
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ <div style="width:200px">First</div>
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+This level of control over the way columns are rendered allows for creating
+advanced column types.
+
+Formatter Subclasses
+====================
+
+The Formatter is useful, but lacks some features you may need. The
+factoring is such that, typically, overriding just a few methods can easily
+provide what you need. The table module provides a few examples of these
+subclasses. While the names are sometimes a bit unwieldy, the functionality is
+useful.
+
+AlternatingRowFormatter
+-----------------------
+
+The AlternatingRowFormatter is the simplest subclass, offering an
+odd-even row formatter that's very easy to use::
+
+ >>> formatter = table.AlternatingRowFormatter(
+ ... context, request, items, ('First', 'Third'), columns=columns)
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="odd">
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ <tr class="even">
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr class="odd">
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+If you want different classes other than "even" and "odd" then simply
+define `row_classes` on your instance: the default is a tuple of "even" and
+"odd", but "green" and "red" will work as well:
+
+ >>> formatter.row_classes = ("red", "green")
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="green">
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ <tr class="red">
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr class="green">
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+Note that this formatter also plays nicely with the other CSS classes defined
+by the formatter:
+
+ >>> formatter.cssClasses['tr'] = 'list'
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr class="list">
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="list green">
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ <tr class="list red">
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr class="list green">
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+
+SortingFormatter
+----------------
+
+``SortingFormatter`` supports ``ISortableColumn`` instances by asking them to
+sort using the ``ISortableColumn`` interface described above. Instantiating
+one takes a new final optional argument, ``sort_on``, which is a sequence of
+tuple pairs of (column name string, reverse sort boolean) in which the first
+pair is the primary sort. Here's an example. Notice that we are sorting on
+the hidden column--this is acceptable, and not even all that unlikely to
+encounter.
+
+ >>> formatter = table.SortingFormatter(
+ ... context, request, items, ('First', 'Third'), columns=columns,
+ ... sort_on=(('Second', True),))
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+Sorting can also be done on multiple columns. This has the effect of
+subsorting. It is up to a column to support the subsorting: it is not a
+required behavior. The default GetterColumns we have been using it support it
+at the expense of possibly doing a lot of wasted work; the behavior will come
+in handy for some examples, though.
+
+First, we'll add some data items that have the same value in the "First"
+column. Then we'll configure the sort to sort with "First" being the primary
+key and "Third" being the secondary key (you can provide more than two if you
+wish). Note that, unlike some of the values examined up to this point, the
+sort columns will only be honored when passed to the class on instanciation.
+ >>> big_items = items[:]
+ >>> big_items.append(DataItem('a1', 'b1', 'c9'))
+ >>> big_items.append(DataItem('a1', 'b1', 'c7'))
+ >>> big_items.append(DataItem('a1', 'b1', 'c8'))
+ >>> formatter = table.SortingFormatter(
+ ... context, request, big_items, ('First', 'Third'), columns=columns,
+ ... sort_on=(('First', True), ('Third', False)))
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c7
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c8
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c9
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+If the direction of the primary sort is changed, it doesn't effect the sub
+sort::
+
+ >>> formatter = table.SortingFormatter(
+ ... context, request, big_items, ('First', 'Third'), columns=columns,
+ ... sort_on=(('First', False), ('Third', False)))
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c7
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c8
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c9
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+When batching sorted tables, the sorting is applied first, then the batching::
+
+ >>> formatter = table.SortingFormatter(
+ ... context, request, items, ('First', 'Third'), columns=columns,
+ ... batch_start=1, sort_on=(('Second', True),))
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+StandaloneSortFormatter and FormSortFormatter
+---------------------------------------------
+
+The sorting table formatter takes care of the sorting back end, but it's
+convenient to encapsulate a bit of the front end logic as well, to provide
+columns with clickable headers for sorting and so on without having to write
+the code every time you need the behavior. Two subclasses of
+SortingFormatter provide this capability. The
+StandaloneSortFormatter is useful for tables that are not parts of a
+form, while the FormSortFormatter is designed to fit within a form.
+
+Both versions look at the request to examine what the user has requested be
+sorted, and draw UI on the sortable column headers to enable sorting. The
+standalone version uses javascript to put the information in the url, and
+the form version puts the information in a hidden field.
+
+Let's take a look at the output of one of these formatters. First there will
+be no sorting information.
+
+ >>> request = zope.publisher.browser.TestRequest()
+ >>> formatter = table.FormSortFormatter(
+ ... context, request, items, ('First', 'Third'), columns=columns)
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ <span class="zc-table-sortable"
+ onclick="javascript: onSortClickForm(
+ 'First', 'sort_on')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ First</span>...
+ </th>
+ <th>
+ <span class="zc-table-sortable"
+ onclick="javascript: onSortClickForm(
+ 'Third', 'sort_on')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ Third</span>...
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ ...
+
+Setting a prefix also affects the value used to store the sorting information.
+
+ >>> formatter = table.FormSortFormatter(
+ ... context, request, items, ('First', 'Third'),
+ ... prefix='slot.first', columns=columns)
+ >>> sort_on_name = table.getSortOnName(formatter.prefix)
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ <span class="zc-table-sortable"
+ onclick="javascript: onSortClickForm(
+ 'First', 'slot.first.sort_on')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ First</span>...
+ </th>
+ <th>
+ <span class="zc-table-sortable"
+ onclick="javascript: onSortClickForm(
+ 'Third', 'slot.first.sort_on')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ Third</span>...
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ ...
+
+Now we'll add information in the request about the sort, and use a prefix.
+The value given in the request indicates that the form should be sorted by
+the second column in reverse order.
+
+ >>> request.form[sort_on_name] = ['Second', 'Second']
+ >>> formatter = table.FormSortFormatter(
+ ... context, request, items, ('First', 'Third'),
+ ... prefix='slot.first', columns=columns)
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ <span class="zc-table-sortable"
+ onclick="javascript: onSortClickForm(
+ 'First', 'slot.first.sort_on')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ First</span>...
+ </th>
+ <th>
+ <span class="zc-table-sortable"
+ onclick="javascript: onSortClickForm(
+ 'Third', 'slot.first.sort_on')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ Third</span>...
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ ...
+
+Note that sort_on value explicitly passed to a FormSortFormatter is only an
+initial value: if the request contains sort information, then the sort_on
+value is ignored. This is correct behavior because the initial sort_on value
+is recorded in the form, and does not need to be repeated.
+
+For instance, if we re-use the big_items collection from above and pass a
+sort_on but modify the request to effectively get a sort_on of
+(('First', True), ('Third', False)), then the code will look something like
+this--notice that we draw arrows indicating the direction of the primary
+search.
+
+ >>> request = zope.publisher.browser.TestRequest()
+ >>> request.form[sort_on_name] = ['Third', 'First', 'First'] # LIFO
+ >>> formatter = table.FormSortFormatter(
+ ... context, request, big_items, ('First', 'Third'), columns=columns,
+ ... prefix='slot.first', sort_on=(('Second', False), ('Third', True)))
+ >>> interfaces.IColumnSortedItems.providedBy(formatter.items)
+ True
+ >>> zope.interface.verify.verifyObject(interfaces.IColumnSortedItems,
+ ... formatter.items)
+ True
+ >>> formatter.items.sort_on
+ [['First', True], ['Third', False]]
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ <span class="zc-table-sortable"
+ onclick="javascript: onSortClickForm(
+ 'First', 'slot.first.sort_on')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ First</span>...
+ <img src="/@@/zc.table/sort_arrows_up.gif".../>
+ </th>
+ <th>
+ <span class="zc-table-sortable"
+ onclick="javascript: onSortClickForm(
+ 'Third', 'slot.first.sort_on')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ Third</span>...
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c7
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c8
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c9
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ ...
+
+The standalone non-form version uses almost all the same code but doesn't
+draw the hidden field and calls a different JavaScript function (which puts the
+sorting information in the query string rather than in a form field). Here's a
+quick copy of the example above, modified to use the standalone version.
+Because of the way the query string is used, more than two instances of a
+column name may appear in the form field, so this is emulated in the example.
+
+Because the standalone version doesn't have a form to record the initial
+sort_on values, they are honored even if sort_on values exist in the request.
+This is in direct contrast to the form-based formatter discussed immediately
+above.
+
+ >>> request = zope.publisher.browser.TestRequest()
+ >>> request.form[sort_on_name] = [
+ ... 'Third', 'First', 'Second', 'Third', 'Second', 'Third', 'First']
+ ... # == First True, Third False, Second True
+ >>> formatter = table.StandaloneSortFormatter(
+ ... context, request, big_items, ('First', 'Third'), columns=columns,
+ ... prefix='slot.first', sort_on=(('Second', False), ('Third', True)))
+ >>> formatter.items.sort_on
+ [['First', True], ['Third', False], ['Second', False]]
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ <span class="zc-table-sortable"
+ onclick="javascript: onSortClickStandalone(
+ 'First', 'slot.first.sort_on')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ First</span> <img src="/@@/zc.table/sort_arrows_up.gif".../>
+ </th>
+ <th>
+ <span class="zc-table-sortable"
+ onclick="javascript: onSortClickStandalone(
+ 'Third', 'slot.first.sort_on')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ Third</span>...
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c7
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c8
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c9
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+The sorting code is to be able to accept iterators as items, and only iterate
+through them as much as necessary to accomplish the tasks. This needs to
+support multiple simultaneous iterations. Another goal is to use the slice
+syntax to let sort implementations be guided as to where precise sorting is
+needed, in case n-best or other approaches can be used.
+
+There is some trickiness about this in the implementation, and this part of
+the document tries to explore some of the edge cases that have proved
+problematic in the field.
+
+In particular, we should examine using an iterator in sorted and unsorted
+configurations within a sorting table formatter, with batching.
+
+Unsorted:
+
+ >>> formatter = table.SortingFormatter(
+ ... context, request, iter(items), ('First', 'Third'),
+ ... columns=columns, batch_size=2)
+ >>> formatter.items[0] is not None # artifically provoke error :-(
+ True
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a0
+ </td>
+ <td>
+ c0
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+Sorted:
+
+ >>> formatter = table.SortingFormatter(
+ ... context, request, iter(items), ('First', 'Third'),
+ ... columns=columns, sort_on=(('Second', True),), batch_size=2)
+ >>> formatter.items[0] is not None # artifically provoke error :-(
+ True
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ First
+ </th>
+ <th>
+ Third
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ a2
+ </td>
+ <td>
+ c2
+ </td>
+ </tr>
+ <tr>
+ <td>
+ a1
+ </td>
+ <td>
+ c1
+ </td>
+ </tr>
+ </tbody>
+ </table>
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/SETUP.cfg
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/SETUP.cfg (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/SETUP.cfg 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,3 @@
+<data-files zopeskel/etc/package-includes>
+ zc.table-*.zcml
+</data-files>
Added: python-zc.table/branches/upstream/current/src/zc/table/TODO.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/TODO.txt (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/TODO.txt 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,5 @@
+add tests for binding
+add .getGuts call (with different name)
+register utility and change clients to use it
+make sorting UI more discoverable
+add .renderExtra (poss. with different name) and change clients to use it
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/TODO.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/__init__.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/__init__.py (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/__init__.py 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,18 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""HTML Table support
+
+$Id: __init__.py 1335 2005-04-19 05:21:10Z gary $
+"""
+from zc.table.table import Formatter
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/__init__.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/column.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/column.py (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/column.py 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,306 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Useful predefined columns
+
+$Id: column.py 4318 2005-12-06 03:41:37Z gary $
+"""
+import warnings
+from xml.sax.saxutils import quoteattr
+
+from zope import interface, component, schema, i18n
+from zope.app.form.browser.submit import Update
+from zope.app.form.interfaces import IInputWidget
+from zope.app.form.interfaces import WidgetInputError, WidgetsError
+
+from zc.table import interfaces
+
+class Column(object):
+ interface.implements(interfaces.IColumn)
+ title = None
+ name = None
+
+ def __init__(self, title=None, name=None):
+ if title is not None:
+ self.title = title
+
+ self.name = name or title
+
+ def renderHeader(self, formatter):
+ return i18n.translate(
+ self.title, context=formatter.request, default=self.title)
+
+ def renderCell(self, item, formatter):
+ raise NotImplementedError('Subclasses must provide their '
+ 'own renderCell method.')
+
+class SortingColumn(Column):
+ interface.implements(interfaces.ISortableColumn)
+
+ # sort and reversesort are part of ISortableColumn, not IColumn, but are
+ # put here to provide a reasonable default implementation.
+
+ def __init__(self, title=None, name=None, subsort=False):
+ self.subsort = subsort
+ super(SortingColumn, self).__init__(title, name)
+
+ def _sort(self, items, formatter, start, stop, sorters, multiplier):
+ if self.subsort and sorters:
+ items = sorters[0](items, formatter, start, stop, sorters[1:])
+ else:
+ items = list(items) # don't mutate original
+ getSortKey = self.getSortKey
+ items.sort(
+ lambda a, b: multiplier*cmp(getSortKey(a, formatter),
+ getSortKey(b, formatter)))
+ return items
+
+ def sort(self, items, formatter, start, stop, sorters):
+ return self._sort(items, formatter, start, stop, sorters, 1)
+
+ def reversesort(self, items, formatter, start, stop, sorters):
+ return self._sort(items, formatter, start, stop, sorters, -1)
+
+ # this is a convenience to override if you just want to keep the basic
+ # implementation but change the comparison values.
+
+ def getSortKey(self, item, formatter):
+ raise NotImplementedError
+
+class GetterColumn(SortingColumn):
+ """Column for simple use cases.
+
+ title - the title of the column
+ getter - a callable that is passed the item and the table formatter;
+ returns the value used in the cell
+ cell_formatter - a callable that is passed the result of getter, the
+ item, and the table formatter; returns the formatted HTML
+ """
+ interface.implementsOnly(interfaces.IColumn)
+
+ def __init__(self, title=None, getter=None, cell_formatter=None,
+ name=None, subsort=False):
+ if getter is not None:
+ self.getter = getter
+
+ if cell_formatter is not None:
+ self.cell_formatter = cell_formatter
+
+ super(GetterColumn, self).__init__(title, name, subsort=subsort)
+
+ def getter(self, item, formatter):
+ return item
+
+ def cell_formatter(self, value, item, formatter):
+ return unicode(value)
+
+ def renderCell(self, item, formatter):
+ value = self.getter(item, formatter)
+ return self.cell_formatter(value, item, formatter)
+
+ # this is a convenience to override if you just want to keep the basic
+ # implementation but change the comparison values.
+
+ def getSortKey(self, item, formatter):
+ return self.getter(item, formatter)
+
+
+class MailtoColumn(GetterColumn):
+ def renderCell(self, item, formatter):
+ email = super(MailtoColumn, self).renderCell(item, formatter)
+ return '<a href="mailto:%s">%s</a>' % (email, email)
+
+class FieldEditColumn(Column):
+ """Columns that supports field/widget update
+
+ Note that fields are only bound if bind == True.
+ """
+
+ def __init__(self, title=None, prefix=None, field=None,
+ idgetter=None, getter=None, setter=None, name='', bind=False,
+ widget_class=None, widget_extra=None):
+ super(FieldEditColumn, self).__init__(title, name)
+ assert prefix is not None # this is required
+ assert field is not None # this is required
+ assert idgetter is not None # this is required
+ self.prefix = prefix
+ self.field = field
+ self.idgetter = idgetter
+ if getter is None:
+ getter = field.get
+ self.get = getter
+ if setter is None:
+ setter = field.set
+ self.set = setter
+ self.bind = bind
+ self.widget_class = widget_class
+ self.widget_extra = widget_extra
+
+ def makeId(self, item):
+ return ''.join(self.idgetter(item).encode('base64').split())
+
+ def input(self, items, request):
+ if not hasattr(request, 'form'):
+ warnings.warn(
+ 'input should be called with a request, not a formatter',
+ DeprecationWarning, 2)
+ request = request.request
+ data = {}
+ errors = []
+ bind = self.bind
+ if not bind:
+ widget = component.getMultiAdapter(
+ (self.field, request), IInputWidget)
+ for item in items:
+ if bind:
+ widget = component.getMultiAdapter(
+ (self.field.bind(item), request), IInputWidget)
+ id = self.makeId(item)
+ # this is wrong: should use formatter prefix. column should not
+ # have a prefix. This requires a rewrite; this entire class
+ # will be deprecated.
+ widget.setPrefix(self.prefix + '.' + id)
+ if widget.hasInput():
+ try:
+ data[id] = widget.getInputValue()
+ except WidgetInputError, v:
+ errors.append(v)
+
+ if errors:
+ raise WidgetsError(errors)
+ return data
+
+ def update(self, items, data):
+ changed = False
+ for item in items:
+ id = self.makeId(item)
+ v = data.get(id, self)
+ if v is self:
+ continue
+ if self.get(item) != v:
+ self.set(item, v)
+ changed = True
+ return changed
+
+ def renderCell(self, item, formatter):
+ id = self.makeId(item)
+ request = formatter.request
+ field = self.field
+ if self.bind:
+ field = field.bind(item)
+ widget = component.getMultiAdapter((field, request), IInputWidget)
+ widget.setPrefix(self.prefix + '.' + id)
+ if self.widget_extra is not None:
+ widget.extra = self.widget_extra
+ if self.widget_class is not None:
+ widget.cssClass = self.widget_class
+ ignoreStickyValues = getattr(formatter, 'ignoreStickyValues', False)
+ if ignoreStickyValues or not widget.hasInput():
+ widget.setRenderedValue(self.get(item))
+ return widget()
+
+
+class SelectionColumn(FieldEditColumn):
+ title = ''
+
+ def __init__(self, idgetter, field=None, prefix=None, getter=None,
+ setter=None, title=None, name='', hide_header=False):
+ if field is None:
+ field = schema.Bool()
+ if not prefix:
+ if field.__name__:
+ prefix = field.__name__ + '_selection_column'
+ else:
+ prefix = 'selection_column'
+ if getter is None:
+ getter = lambda item: False
+ if setter is None:
+ setter = lambda item, value: None
+ if title is None:
+ title = field.title or ""
+ self.hide_header = hide_header
+ super(SelectionColumn, self).__init__(field=field, prefix=prefix,
+ getter=getter, setter=setter,
+ idgetter=idgetter, title=title,
+ name=name)
+
+ def renderHeader(self, formatter):
+ if self.hide_header:
+ return ''
+ return super(SelectionColumn, self).renderHeader(formatter)
+
+ def getSelected(self, items, request):
+ """Return the items which were selected."""
+ data = self.input(items, request)
+ return [item for item in items if data.get(self.makeId(item))]
+
+class SubmitColumn(Column):
+ def __init__(self, title=None, prefix=None, idgetter=None, action=None,
+ labelgetter=None, condition=None,
+ extra=None, cssClass=None, renderer=None, name=''):
+ super(SubmitColumn, self).__init__(title, name)
+ # hacked together. :-/
+ assert prefix is not None # this is required
+ assert idgetter is not None # this is required
+ assert labelgetter is not None # this is required
+ assert action is not None # this is required
+ self.prefix = prefix
+ self.idgetter = idgetter
+ self.action = action
+ self.renderer=renderer
+ self.condition = condition
+ self.extra = extra
+ self.cssClass = cssClass
+ self.labelgetter = labelgetter
+
+ def makeId(self, item):
+ return ''.join(self.idgetter(item).encode('base64').split())
+
+ def input(self, items, request):
+ for item in items:
+ id = self.makeId(item)
+ identifier = '%s.%s' % (self.prefix, id)
+ if identifier in request.form:
+ if self.condition is None or self.condition(item):
+ return id
+ break
+
+ def update(self, items, data):
+ if data:
+ for item in items:
+ id = self.makeId(item)
+ if id == data:
+ self.action(item)
+ return True
+ return False
+
+ def renderCell(self, item, formatter):
+ if self.condition is None or self.condition(item):
+ id = self.makeId(item)
+ identifier = '%s.%s' % (self.prefix, id)
+ if self.renderer is not None:
+ return self.renderer(
+ item, identifier, formatter, self.extra, self.cssClass)
+ label = self.labelgetter(item, formatter)
+ label = i18n.translate(
+ label, context=formatter.request, default=label)
+ val = "<input type='submit' name=%s value=%s %s" % (
+ quoteattr(identifier),
+ quoteattr(label),
+ self.extra and quoteattr(self.extra) or '')
+ if self.cssClass:
+ val = "%s class=%s />" % (val, quoteattr(self.cssClass))
+ else:
+ val += " />"
+ return val
+ return ''
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/column.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/column.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/column.txt (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/column.txt 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,405 @@
+Useful column types
+===================
+
+We provide a number of pre-defined column types to use in table
+definitions.
+
+Field Edit Columns
+------------------
+
+Field edit columns provide support for tables of input widgets.
+To define a field edit column, you need to provide:
+
+ - title, the label to be displayed
+
+ - a prefix, which is used to distinguish a columns inputs
+ from those of other columns
+
+ - a field that describes the type of data in the column
+
+ - an id getter
+
+ - an optional data getter, and
+
+ - an optional data setter
+
+The id getter has to compute a string that uniquely identified an
+item.
+
+Let's look at a simple example. We have a collection of contacts,
+with names and email addresses:
+
+ >>> import re
+ >>> from zope import schema, interface
+ >>> class IContact(interface.Interface):
+ ... name = schema.TextLine()
+ ... email = schema.TextLine(
+ ... constraint=re.compile('\w+@\w+([.]\w+)+$').match)
+
+ >>> class Contact:
+ ... interface.implements(IContact)
+ ... def __init__(self, id, name, email):
+ ... self.id = id
+ ... self.name = name
+ ... self.email = email
+
+ >>> contacts = (
+ ... Contact('1', 'Bob Smith', 'bob at zope.com'),
+ ... Contact('2', 'Sally Baker', 'sally at zope.com'),
+ ... Contact('3', 'Jethro Tul', 'jethro at zope.com'),
+ ... Contact('4', 'Joe Walsh', 'joe at zope.com'),
+ ... )
+
+We'll define columns that allow us to display and edit name and
+email addresses.
+
+ >>> from zc.table import column
+ >>> columns = (
+ ... column.FieldEditColumn(
+ ... "Name", "test", IContact["name"],
+ ... lambda contact: contact.id,
+ ... ),
+ ... column.FieldEditColumn(
+ ... "Email address", "test", IContact["email"],
+ ... lambda contact: contact.id,
+ ... ),
+ ... )
+
+Now, with this, we can create a table with input widgets. The columns don't
+need a context other than the items themselves, so we ignore that part of the
+table formatter instantiation:
+
+ >>> from zc import table
+ >>> import zope.publisher.browser
+ >>> request = zope.publisher.browser.TestRequest()
+ >>> context = None
+ >>> formatter = table.Formatter(
+ ... context, request, contacts, columns=columns)
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ Name
+ </th>
+ <th>
+ Email address
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <input class="textType" id="test.MQ==.name" name="test.MQ==.name"
+ size="20" type="text" value="Bob Smith" />
+ </td>
+ <td>
+ <input class="textType" id="test.MQ==.email" name="test.MQ==.email"
+ size="20" type="text" value="bob at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.Mg==.name" name="test.Mg==.name"
+ size="20" type="text" value="Sally Baker" />
+ </td>
+ <td>
+ <input class="textType" id="test.Mg==.email" name="test.Mg==.email"
+ size="20" type="text" value="sally at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.Mw==.name" name="test.Mw==.name"
+ size="20" type="text" value="Jethro Tul" />
+ </td>
+ <td>
+ <input class="textType" id="test.Mw==.email" name="test.Mw==.email"
+ size="20" type="text" value="jethro at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.NA==.name" name="test.NA==.name"
+ size="20" type="text" value="Joe Walsh" />
+ </td>
+ <td>
+ <input class="textType" id="test.NA==.email" name="test.NA==.email"
+ size="20" type="text" value="joe at zope.com" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+Note that the input names include base64 encodings of the item ids.
+
+If the request has input for a value, then this will override item data:
+
+ >>> request.form["test.NA==.email"] = u'walsh at zope.com'
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ Name
+ </th>
+ <th>
+ Email address
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <input class="textType" id="test.MQ==.name" name="test.MQ==.name"
+ size="20" type="text" value="Bob Smith" />
+ </td>
+ <td>
+ <input class="textType" id="test.MQ==.email" name="test.MQ==.email"
+ size="20" type="text" value="bob at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.Mg==.name" name="test.Mg==.name"
+ size="20" type="text" value="Sally Baker" />
+ </td>
+ <td>
+ <input class="textType" id="test.Mg==.email" name="test.Mg==.email"
+ size="20" type="text" value="sally at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.Mw==.name" name="test.Mw==.name"
+ size="20" type="text" value="Jethro Tul" />
+ </td>
+ <td>
+ <input class="textType" id="test.Mw==.email" name="test.Mw==.email"
+ size="20" type="text" value="jethro at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.NA==.name" name="test.NA==.name"
+ size="20" type="text" value="Joe Walsh" />
+ </td>
+ <td>
+ <input class="textType" id="test.NA==.email" name="test.NA==.email"
+ size="20" type="text" value="walsh at zope.com" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+and the contact data is unchanged:
+
+ >>> contacts[3].email
+ 'joe at zope.com'
+
+Field edit columns provide methods for getting and validating input
+data, and fpr updating the undelying data:
+
+ >>> data = columns[1].input(contacts, request)
+ >>> data
+ {'NA==': u'walsh at zope.com'}
+
+The data returned is a mapping from item id to input value. Items
+that don't have input are ignored. The data can be used with the
+update function to update the underlying data:
+
+ >>> columns[1].update(contacts, data)
+ True
+
+ >>> contacts[3].email
+ u'walsh at zope.com'
+
+Note that the update function returns a boolean value indicating
+whether any changes were made:
+
+ >>> columns[1].update(contacts, data)
+ False
+
+The input function also validates input. If there are any errors, a
+WidgetsError will be raised:
+
+ >>> request.form["test.NA==.email"] = u'walsh'
+ >>> data = columns[1].input(contacts, request)
+ Traceback (most recent call last):
+ ...
+ WidgetsError: WidgetInputError: ('email', u'', walsh)
+
+Custom getters and setters
+--------------------------
+
+Normally, the given fields getter and setter is used, however, custom
+getters and setters can be provided. Let's look at an example of
+a bit table:
+
+ >>> data = [0, 0], [1, 1], [2, 2], [3, 3]
+
+ >>> def setbit(data, bit, value):
+ ... value = bool(value) << bit
+ ... mask = 1 << bit
+ ... data[1] = ((data[1] | mask) ^ mask) | value
+ >>> columns = (
+ ... column.FieldEditColumn(
+ ... "Bit 0", "test", schema.Bool(__name__='0'),
+ ... lambda data: str(data[0]),
+ ... getter = lambda data: 1&(data[1]),
+ ... setter = lambda data, v: setbit(data, 0, v),
+ ... ),
+ ... column.FieldEditColumn(
+ ... "Bit 1", "test", schema.Bool(__name__='1'),
+ ... lambda data: str(data[0]),
+ ... getter = lambda data: 2&(data[1]),
+ ... setter = lambda data, v: setbit(data, 1, v),
+ ... ),
+ ... )
+
+ >>> context = None # not needed
+ >>> request = zope.publisher.browser.TestRequest()
+ >>> formatter = table.Formatter(
+ ... context, request, data, columns=columns)
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ Bit 0
+ </th>
+ <th>
+ Bit 1
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <input class="hiddenType" id="test.MA==.0.used"
+ name="test.MA==.0.used" type="hidden" value="" />
+ <input class="checkboxType" id="test.MA==.0"
+ name="test.MA==.0" type="checkbox" value="on" />
+ </td>
+ <td>
+ <input class="hiddenType" id="test.MA==.1.used"
+ name="test.MA==.1.used" type="hidden" value="" />
+ <input class="checkboxType" id="test.MA==.1"
+ name="test.MA==.1" type="checkbox" value="on" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="hiddenType" id="test.MQ==.0.used"
+ name="test.MQ==.0.used" type="hidden" value="" />
+ <input class="checkboxType" checked="checked" id="test.MQ==.0"
+ name="test.MQ==.0" type="checkbox" value="on" />
+ </td>
+ <td>
+ <input class="hiddenType" id="test.MQ==.1.used"
+ name="test.MQ==.1.used" type="hidden" value="" />
+ <input class="checkboxType" id="test.MQ==.1"
+ name="test.MQ==.1" type="checkbox" value="on" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="hiddenType" id="test.Mg==.0.used"
+ name="test.Mg==.0.used" type="hidden" value="" />
+ <input class="checkboxType" id="test.Mg==.0"
+ name="test.Mg==.0" type="checkbox" value="on" />
+ </td>
+ <td>
+ <input class="hiddenType" id="test.Mg==.1.used"
+ name="test.Mg==.1.used" type="hidden" value="" />
+ <input class="checkboxType" checked="checked" id="test.Mg==.1"
+ name="test.Mg==.1" type="checkbox" value="on" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="hiddenType" id="test.Mw==.0.used"
+ name="test.Mw==.0.used" type="hidden" value="" />
+ <input class="checkboxType" checked="checked" id="test.Mw==.0"
+ name="test.Mw==.0" type="checkbox" value="on" />
+ </td>
+ <td>
+ <input class="hiddenType" id="test.Mw==.1.used"
+ name="test.Mw==.1.used" type="hidden" value="" />
+ <input class="checkboxType" checked="checked" id="test.Mw==.1"
+ name="test.Mw==.1" type="checkbox" value="on" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ >>> request.form["test.Mw==.1.used"] = ""
+ >>> request.form["test.MA==.1.used"] = ""
+ >>> request.form["test.MA==.1"] = "on"
+
+ >>> input = columns[1].input(data, request)
+ >>> from zope.testing.doctestunit import pprint
+ >>> pprint(input)
+ {'MA==': True,
+ 'Mw==': False}
+
+ >>> columns[1].update(data, input)
+ True
+
+ >>> data
+ ([0, 2], [1, 1], [2, 2], [3, 1])
+
+Column names
+============
+
+When defining columns, you can supply separate names and titles. You
+would do this, for example, to use a blank title:
+
+ >>> columns = (
+ ... column.FieldEditColumn(
+ ... "", "test", schema.Bool(__name__='0'),
+ ... lambda data: str(data[0]),
+ ... getter = lambda data: 1&(data[1]),
+ ... setter = lambda data, v: setbit(data, 0, v),
+ ... name = "0",
+ ... ),
+ ... column.FieldEditColumn(
+ ... "", "test", schema.Bool(__name__='1'),
+ ... lambda data: str(data[0]),
+ ... getter = lambda data: 2&(data[1]),
+ ... setter = lambda data, v: setbit(data, 1, v),
+ ... name = "1",
+ ... ),
+ ... )
+
+ >>> formatter = table.Formatter(
+ ... context, request, data[0:1], columns=columns)
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ <BLANKLINE>
+ </th>
+ <th>
+ <BLANKLINE>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <input class="hiddenType" id="test.MA==.0.used"
+ name="test.MA==.0.used" type="hidden" value="" />
+ <input class="checkboxType" id="test.MA==.0"
+ name="test.MA==.0" type="checkbox" value="on" />
+ </td>
+ <td>
+ <input class="hiddenType" id="test.MA==.1.used"
+ name="test.MA==.1.used" type="hidden" value="" />
+ <input class="checkboxType" checked="checked" id="test.MA==.1"
+ name="test.MA==.1" type="checkbox" value="on" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/column.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/configure.zcml
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/configure.zcml (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/configure.zcml 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,9 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ >
+
+ <resourceLibrary name="zc.table">
+ <directory source="resources" include="sorting.js"/>
+ </resourceLibrary>
+
+</configure>
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.py (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.py 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,201 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+import re
+from xml.sax.saxutils import quoteattr
+
+from zope import interface, component, i18n
+import zope.schema.interfaces
+import zope.formlib.interfaces
+import zope.formlib.form
+
+from zope.app.form.interfaces import IInputWidget, IDisplayWidget
+from zope.app.form.interfaces import WidgetInputError, WidgetsError
+
+from zc.table import column
+
+isSafe = re.compile(r'[\w +/]*$').match
+def toSafe(string):
+ # We don't want to use base64 unless we have to,
+ # because it makes testing and reading html more difficult. We make this
+ # safe because all base64 strings will have a trailing '=', and our
+ # `isSafe` regex does not allow '=' at all. The only downside to the
+ # approach is that a 'safe' string generated by the `toSafe` base64 code
+ # will not pass the `isSafe` test, so the function is not idempotent. The
+ # simpler version (without the `isSafe` check) was not idempotent either,
+ # so no great loss.
+ if not isSafe(string):
+ string = ''.join(string.encode('base64').split())
+ return string
+
+class BaseColumn(column.Column):
+
+ ###### subclass helper API (not expected to be overridden) ######
+
+ def getPrefix(self, item, formatter):
+ prefix = self.getId(item, formatter)
+ if formatter.prefix:
+ prefix = '%s.%s' % (formatter.prefix, prefix)
+ return prefix
+
+ @property
+ def key(self):
+ return '%s.%s.%s' % (
+ self.__class__.__module__,
+ self.__class__.__name__,
+ self.name)
+
+ def setAnnotation(self, name, value, formatter):
+ formatter.annotations[self.key + name] = value
+
+ def getAnnotation(self, name, formatter, default=None):
+ return formatter.annotations.get(self.key + name, default)
+
+ ###### subclass customization API ######
+
+ def getId(self, item, formatter):
+ return toSafe(str(item))
+
+class FieldColumn(BaseColumn):
+ """Column that supports field/widget update
+
+ Note that fields are only bound if bind == True.
+ """
+
+ __slots__ = ('title', 'name', 'field') # to emphasize that this should not
+ # have thread-local attributes such as request
+
+ def __init__(self, field, title=None, name=''):
+ if zope.schema.interfaces.IField.providedBy(field):
+ field = zope.formlib.form.FormField(field)
+ else:
+ assert zope.formlib.interfaces.IFormField.providedBy(field)
+ self.field = field
+ if title is None:
+ title = self.field.field.title
+ if not name and self.field.__name__:
+ name = self.field.__name__
+ super(FieldColumn, self).__init__(title, name)
+
+ ###### subclass helper API (not expected to be overridden) ######
+
+ def getInputWidget(self, item, formatter):
+ form_field = self.field
+ field = form_field.field
+ request = formatter.request
+ prefix = self.getPrefix(item, formatter)
+ context = self.getFieldContext(item, formatter)
+ if context is not None:
+ field = form_field.field.bind(context)
+ if form_field.custom_widget is None:
+ if field.readonly or form_field.for_display:
+ iface = IDisplayWidget
+ else:
+ iface = IInputWidget
+ widget = component.getMultiAdapter((field, request), iface)
+ else:
+ widget = form_field.custom_widget(field, request)
+ if form_field.prefix: # this should not be necessary AFAICT
+ prefix = '%s.%s' % (prefix, form_field.prefix)
+ widget.setPrefix(prefix)
+ return widget
+
+ def getRenderWidget(self, item, formatter, ignore_request=False):
+ widget = self.getInputWidget(item, formatter)
+ if (ignore_request or
+ IDisplayWidget.providedBy(widget) or
+ not widget.hasInput()):
+ widget.setRenderedValue(self.get(item, formatter))
+ return widget
+
+ ###### subclass customization API ######
+
+ def get(self, item, formatter):
+ return self.field.field.get(item)
+
+ def set(self, item, value, formatter):
+ self.field.field.set(item, value)
+
+ def getFieldContext(self, item, formatter):
+ return None
+
+ ###### main API: input, update, and custom renderCell ######
+
+ def input(self, items, formatter):
+ data = {}
+ errors = []
+ for item in items:
+ widget = self.getInputWidget(item, formatter)
+ if widget.hasInput():
+ try:
+ data[self.getId(item, formatter)] = widget.getInputValue()
+ except WidgetInputError, v:
+ errors.append(v)
+ if errors:
+ raise WidgetsError(errors)
+ return data
+
+ def update(self, items, data, formatter):
+ changed = False
+ for item in items:
+ id = self.getId(item, formatter)
+ v = data.get(id, self)
+ if v is not self and self.get(item, formatter) != v:
+ self.set(item, v, formatter)
+ changed = True
+ if changed:
+ self.setAnnotation('changed', changed, formatter)
+ return changed
+
+ def renderCell(self, item, formatter):
+ ignore_request = self.getAnnotation('changed', formatter)
+ return self.getRenderWidget(
+ item, formatter, ignore_request)()
+
+class SubmitColumn(BaseColumn):
+
+ ###### subclass helper API (not expected to be overridden) ######
+
+ def getIdentifier(self, item, formatter):
+ return '%s.%s' % (self.getPrefix(item, formatter), self.name)
+
+ def renderWidget(self, item, formatter, **kwargs):
+ res = ['%s=%s' % (k, quoteattr(v)) for k, v in kwargs.items()]
+ lbl = self.getLabel(item, formatter)
+ res[0:0] = [
+ 'input',
+ 'type="submit"',
+ 'name=%s' % quoteattr(self.getIdentifier(item, formatter)),
+ 'value=%s' % quoteattr(lbl)]
+ return '<%s />' % (' '.join(res))
+
+ ###### customization API (expected to be overridden) ######
+
+ def getLabel(self, item, formatter):
+ return super(SubmitColumn, self).renderHeader(formatter) # title
+
+ ###### basic API ######
+
+ def input(self, items, formatter):
+ for item in items:
+ if self.getIdentifier(item, formatter) in formatter.request.form:
+ return item
+
+ def update(self, items, item, formatter):
+ raise NotImplementedError
+
+ def renderCell(self, item, formatter):
+ return self.renderWidget(item, formatter)
+
+ def renderHeader(self, formatter):
+ return ''
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.txt (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.txt 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,364 @@
+The FieldColumn is intended to be a replacement for the FieldEditColumn. It
+has the following goals.
+
+- Support the standard column pattern of instantiating a single module global
+ column set, and using formatters to store and access information about a
+ single given rendering. The formatter has annotations, prefix, context, and
+ request, all of which are desired or even essential for various derived
+ columns. Generally, the formatter has state that the columns need.
+
+- Support display widgets, in addition to input widgets.
+
+- Support formlib form fields, to support custom widgets and other formlib
+ field features.
+
+It also has an important design difference. Rather than relying on functions
+passed at initialization for column customization, the field column returns to
+a standard subclass design. The column expects that any of the following
+methods may be overridden:
+
+- getId(item, formatter): given an item whose value is to be displayed in the
+ column, return a form-friendly string identifier, unique to the item. We
+ define 'form-friendly' as containing characters that are alphanumeric or a
+ space, a plus, a slash, or an equals sign (that is, the standard characters
+ used in MIME-based base64, excluding new lines).
+
+- get(item, formatter): return the current value for the item.
+
+- set(item, value, formatter): set the given value for the item.
+
+- getFieldContext(item, formatter): return a context to which a field should
+ be bound, or None.
+
+- renderCell(item, formatter): the standard rendering of a cell
+
+- renderHeader(formatter): the standard rendering of a column header.
+
+Without subclassing, the column uses field.get and field.set to get and set
+values, returns None for bound context, and just renders the field's widget for
+the cell, and the title for the header. Let's look at the default behavior.
+We'll use a modified version of the examples given for the FieldEditColumn in
+column.txt.
+
+To create a FieldColumn, you must minimally provide a schema field or form
+field. If title is not provided, it is the field's title, or empty if the
+field has no title. A explicit title of an empty string is acceptable and will
+be honored. If name is not provided, it is the field's __name__.
+
+Let's look at a simple example. We have a collection of contacts,
+with names and email addresses:
+
+ >>> import re
+ >>> from zope import schema, interface
+ >>> class IContact(interface.Interface):
+ ... name = schema.TextLine(title=u'Name')
+ ... email = schema.TextLine(
+ ... title=u'Email Address',
+ ... constraint=re.compile('\w+@\w+([.]\w+)+$').match)
+
+ >>> class Contact:
+ ... interface.implements(IContact)
+ ... def __init__(self, id, name, email):
+ ... self.id = id
+ ... self.name = name
+ ... self.email = email
+
+ >>> contacts = (
+ ... Contact('1', 'Bob Smith', 'bob at zope.com'),
+ ... Contact('2', 'Sally Baker', 'sally at zope.com'),
+ ... Contact('3', 'Jethro Tul', 'jethro at zope.com'),
+ ... Contact('4', 'Joe Walsh', 'joe at zope.com'),
+ ... )
+
+We'll define columns that allow us to display and edit name and
+email addresses.
+
+ >>> from zc.table import fieldcolumn
+ >>> class ContactColumn(fieldcolumn.FieldColumn):
+ ... def getId(self, item, formatter):
+ ... return fieldcolumn.toSafe(item.id)
+ >>> columns = (ContactColumn(IContact["name"]),
+ ... ContactColumn(IContact["email"]))
+
+Now, with this, we can create a table with input widgets. The columns don't
+need a context other than the items themselves, so we ignore that part of the
+table formatter instantiation:
+
+ >>> from zc import table
+ >>> import zope.publisher.browser
+ >>> request = zope.publisher.browser.TestRequest()
+ >>> context = None
+ >>> formatter = table.Formatter(
+ ... context, request, contacts, columns=columns, prefix='test')
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ Name
+ </th>
+ <th>
+ Email Address
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <input class="textType" id="test.1.name" name="test.1.name"
+ size="20" type="text" value="Bob Smith" />
+ </td>
+ <td>
+ <input class="textType" id="test.1.email" name="test.1.email"
+ size="20" type="text" value="bob at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.2.name" name="test.2.name"
+ size="20" type="text" value="Sally Baker" />
+ </td>
+ <td>
+ <input class="textType" id="test.2.email" name="test.2.email"
+ size="20" type="text" value="sally at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.3.name" name="test.3.name"
+ size="20" type="text" value="Jethro Tul" />
+ </td>
+ <td>
+ <input class="textType" id="test.3.email" name="test.3.email"
+ size="20" type="text" value="jethro at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.4.name" name="test.4.name"
+ size="20" type="text" value="Joe Walsh" />
+ </td>
+ <td>
+ <input class="textType" id="test.4.email" name="test.4.email"
+ size="20" type="text" value="joe at zope.com" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+Note that the input names do not include base64 encodings of the item ids
+because they already match the necessary constraints.
+
+If the request has input for a value, then this will override item data:
+
+ >>> request.form["test.4.email"] = u'walsh at zope.com'
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ Name
+ </th>
+ <th>
+ Email Address
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <input class="textType" id="test.1.name" name="test.1.name"
+ size="20" type="text" value="Bob Smith" />
+ </td>
+ <td>
+ <input class="textType" id="test.1.email" name="test.1.email"
+ size="20" type="text" value="bob at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.2.name" name="test.2.name"
+ size="20" type="text" value="Sally Baker" />
+ </td>
+ <td>
+ <input class="textType" id="test.2.email" name="test.2.email"
+ size="20" type="text" value="sally at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.3.name" name="test.3.name"
+ size="20" type="text" value="Jethro Tul" />
+ </td>
+ <td>
+ <input class="textType" id="test.3.email" name="test.3.email"
+ size="20" type="text" value="jethro at zope.com" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="textType" id="test.4.name" name="test.4.name"
+ size="20" type="text" value="Joe Walsh" />
+ </td>
+ <td>
+ <input class="textType" id="test.4.email" name="test.4.email"
+ size="20" type="text" value="walsh at zope.com" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+and the contact data is unchanged:
+
+ >>> contacts[3].email
+ 'joe at zope.com'
+
+Field edit columns provide methods for getting and validating input
+data, and for updating the undelying data:
+
+ >>> data = columns[1].input(contacts, formatter)
+ >>> data
+ {'4': u'walsh at zope.com'}
+
+The data returned is a mapping from item id to input value. Items
+that don't have input are ignored. The data can be used with the
+update function to update the underlying data:
+
+ >>> columns[1].update(contacts, data, formatter)
+ True
+
+ >>> contacts[3].email
+ u'walsh at zope.com'
+
+Note that the update function returns a boolean value indicating
+whether any changes were made:
+
+ >>> columns[1].update(contacts, data, formatter)
+ False
+
+The input function also validates input. If there are any errors, a
+WidgetsError will be raised:
+
+ >>> request.form["test.4.email"] = u'walsh'
+ >>> data = columns[1].input(contacts, formatter)
+ Traceback (most recent call last):
+ ...
+ WidgetsError: WidgetInputError: ('email', u'Email Address', walsh)
+
+Custom getters and setters
+--------------------------
+
+Normally, the given fields getter and setter is used, however, custom
+getters and setters can be provided. Let's look at an example of
+a bit table:
+
+ >>> data = [0, 0], [1, 1], [2, 2], [3, 3]
+
+ >>> class BitColumn(fieldcolumn.FieldColumn):
+ ... def __init__(self, field, bit, title=None, name=''):
+ ... super(BitColumn, self).__init__(field, title, name)
+ ... self.bit = bit
+ ... def getId(self, item, formatter):
+ ... return str(item[0])
+ ... def get(self, item, formatter):
+ ... return (1 << self.bit)&(item[1])
+ ... def set(self, item, value, formatter):
+ ... value = bool(value) << self.bit
+ ... mask = 1 << self.bit
+ ... item[1] = ((item[1] | mask) ^ mask) | value
+
+ >>> columns = (
+ ... BitColumn(schema.Bool(__name__='0', title=u'Bit 0'), 0),
+ ... BitColumn(schema.Bool(__name__='1', title=u'Bit 1'), 1))
+
+ >>> context = None # not needed
+ >>> request = zope.publisher.browser.TestRequest()
+ >>> formatter = table.Formatter(
+ ... context, request, data, columns=columns, prefix='test')
+ >>> print formatter()
+ <table>
+ <thead>
+ <tr>
+ <th>
+ Bit 0
+ </th>
+ <th>
+ Bit 1
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <input class="hiddenType" id="test.0.0.used"
+ name="test.0.0.used" type="hidden" value="" />
+ <input class="checkboxType" id="test.0.0"
+ name="test.0.0" type="checkbox" value="on" />
+ </td>
+ <td>
+ <input class="hiddenType" id="test.0.1.used"
+ name="test.0.1.used" type="hidden" value="" />
+ <input class="checkboxType" id="test.0.1"
+ name="test.0.1" type="checkbox" value="on" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="hiddenType" id="test.1.0.used"
+ name="test.1.0.used" type="hidden" value="" />
+ <input class="checkboxType" checked="checked" id="test.1.0"
+ name="test.1.0" type="checkbox" value="on" />
+ </td>
+ <td>
+ <input class="hiddenType" id="test.1.1.used"
+ name="test.1.1.used" type="hidden" value="" />
+ <input class="checkboxType" id="test.1.1"
+ name="test.1.1" type="checkbox" value="on" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="hiddenType" id="test.2.0.used"
+ name="test.2.0.used" type="hidden" value="" />
+ <input class="checkboxType" id="test.2.0"
+ name="test.2.0" type="checkbox" value="on" />
+ </td>
+ <td>
+ <input class="hiddenType" id="test.2.1.used"
+ name="test.2.1.used" type="hidden" value="" />
+ <input class="checkboxType" checked="checked" id="test.2.1"
+ name="test.2.1" type="checkbox" value="on" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <input class="hiddenType" id="test.3.0.used"
+ name="test.3.0.used" type="hidden" value="" />
+ <input class="checkboxType" checked="checked" id="test.3.0"
+ name="test.3.0" type="checkbox" value="on" />
+ </td>
+ <td>
+ <input class="hiddenType" id="test.3.1.used"
+ name="test.3.1.used" type="hidden" value="" />
+ <input class="checkboxType" checked="checked" id="test.3.1"
+ name="test.3.1" type="checkbox" value="on" />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ >>> request.form["test.3.1.used"] = ""
+ >>> request.form["test.0.1.used"] = ""
+ >>> request.form["test.0.1"] = "on"
+
+ >>> input = columns[1].input(data, formatter)
+ >>> from zope.testing.doctestunit import pprint
+ >>> pprint(input)
+ {'0': True,
+ '3': False}
+
+ >>> columns[1].update(data, input, formatter)
+ True
+
+ >>> data
+ ([0, 2], [1, 1], [2, 2], [3, 1])
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/fieldcolumn.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/interfaces.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/interfaces.py (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/interfaces.py 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,267 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""table package interfaces
+
+$Id: interfaces.py 4318 2005-12-06 03:41:37Z gary $
+"""
+import re
+
+from zope import interface, schema
+
+pythonLikeNameConstraint = re.compile('^[a-zA-Z_]\w*$').match
+
+class IColumn(interface.Interface):
+
+ name = schema.BytesLine(
+ title = u'Name',
+ description = (u'Name used for column in options of a table '
+ u'configuration. Must be unique within any set of '
+ u'columns passed to a table formatter.'),
+ constraint = pythonLikeNameConstraint,
+ )
+
+ title = schema.TextLine(
+ title = u'Title',
+ description = u'The title of the column, used in configuration '
+ u'dialogs.',
+ )
+
+ def renderHeader(formatter):
+ """Renders a table header.
+
+ 'formatter' - The IFormatter that is using the IColumn.
+
+ Returns html_fragment, not including any surrounding <th> tags.
+ """
+
+ def renderCell(item, formatter):
+ """Renders a table cell.
+
+ 'item' - the object on this row.
+ 'formatter' - The IFormatter that is using the IColumn.
+
+ Returns html_fragment, not including any surrounding <td> tags.
+ """
+
+class ISortableColumn(interface.Interface):
+
+ def sort(items, formatter, start, stop, sorters):
+ """Return a list of items in sorted order.
+
+ Formatter is passed to aid calculation of sort parameters. Start and
+ stop are passed in order to provide a hint as to the range needed, if
+ the algorithm can optimize. Sorters are a list of zero or more
+ sub-sort callables with the same signature which may be used if
+ desired to sub-sort values with equivalent sort values according
+ to this column.
+
+ The original items sequence should not be mutated."""
+
+ def reversesort(items, formatter, start, stop, sorters):
+ """Return a list of items in reverse sorted order.
+
+ Formatter is passed to aid calculation of sort parameters. Start and
+ stop are passed in order to provide a hint as to the range needed, if
+ the algorithm can optimize. Sorters are a list of zero or more
+ sub-sort callables with the same signature as this method, which may
+ be used if desired to sub-sort values with equivalent sort values
+ according to this column.
+
+ The original items sequence should not be mutated."""
+
+class IColumnSortedItems(interface.Interface):
+ """items that support sorting by column. setFormatter must be called
+ with the formatter to be used before methods work. This is typically done
+ in a formatter's __init__"""
+
+ sort_on = interface.Attribute(
+ """list of (colmun name, reversed boolean) beginning with the primary
+ sort column.""")
+
+ def __getitem__(key):
+ """given index or slice, returns requested item(s) based on sort order.
+ """
+
+ def __iter__():
+ """iterates over items in sort order"""
+
+ def __len__():
+ """returns len of sorted items"""
+
+ def setFormatter(formatter):
+ "tell the items about the formatter before using any of the methods"
+
+
+class IFormatter(interface.Interface):
+
+ annotations = schema.Dict(
+ title=u"Annotations",
+ description=
+ u"""Stores arbitrary application data under package-unique keys.
+
+ By "package-unique keys", we mean keys that are are unique by
+ virtue of including the dotted name of a package as a prefix. A
+ package name is used to limit the authority for picking names for
+ a package to the people using that package.
+
+ For example, when implementing annotations for a zc.foo package, the
+ key would be (or at least begin with) the following::
+
+ "zc.foo"
+ """)
+
+ request = schema.Field(
+ title = u'Request',
+ description = u'The request object.',
+ )
+
+ context = schema.Field(
+ title=u'Context',
+ description=u'The (Zope ILocation) context for which the table '
+ u'formatter is rendering')
+
+ items = schema.List(
+ title=u'Items',
+ description=u'The items that will be rendered by __call__. items '
+ 'preferably support a way to get a slice (__getitem__ or the '
+ 'deprecated getslice) or alternatively may merely be iterable. '
+ 'see getItems.')
+
+ columns = schema.Tuple(
+ title = u'All the columns that make up this table.',
+ description = u'All columns that may ever be a visible column. A non-'
+ u'visible column may still have an effect on operations such as '
+ u'sorting. The names of all columns must be unique within the '
+ u'sequence.',
+ unique = True,
+ )
+
+ visible_columns = schema.Tuple(
+ title = u'The visible columns that make up this table.',
+ description = u'The columns to display when rendering this table.',
+ unique = True,
+ )
+
+ batch_size = schema.Int(
+ title = u'Number of rows per page',
+ description = u'The number of rows to show at a time. '
+ u'Set to 0 for no batching.',
+ default = 20,
+ min = 0,
+ )
+
+ batch_start = schema.Int(
+ title = u'Batch Start',
+ description = u'The starting index for batching.',
+ default = 0,
+ )
+
+ prefix = schema.BytesLine(
+ title = u'Prefix',
+ description = u'The prefix for all form names',
+ constraint = pythonLikeNameConstraint,
+ )
+
+ columns_by_name = schema.Dict(
+ title=u'Columns by Name',
+ description=u'A mapping of column name to column object')
+
+ cssClasses = schema.Dict(
+ title=u'CSS Classes',
+ description=u'A mapping from an HTML element to a CSS class',
+ key_type=schema.TextLine(title=u'The HTML element name'),
+ value_type=schema.TextLine(title=u'The CSS class name'))
+
+ def __call__():
+ """Render a complete HTML table from self.items."""
+
+ def renderHeaderRow():
+ """Render an HTML table header row from the column headers.
+
+ Uses renderHeaders."""
+
+ def renderHeaders():
+ """Render the individual HTML headers from the columns.
+
+ Uses renderHeader."""
+
+ def renderHeader(column):
+ """Render a header for the given column.
+
+ Uses getHeader."""
+
+ def getHeader(column):
+ """Render header contents for the given column.
+
+ Includes appropriate code for enabling ISortableColumn.
+
+ Uses column.renderHeader"""
+
+ def getHeaders():
+ """Retrieve a sequence of rendered column header contents.
+
+ Uses getHeader.
+
+ Available for more low-level use of a table; not used by the other
+ table code."""
+
+ def renderRows():
+ """Render HTML rows for the self.items.
+
+ Uses renderRow and getItems."""
+
+ def getRows():
+ """Retrieve a sequence of sequences of rendered cell contents.
+
+ Uses getCells and getItems.
+
+ Available for more low-level use of a table; not used by the other
+ table code."""
+
+ def getCells(item):
+ """Retrieve a sequence rendered cell contents for the item.
+
+ Uses getCell.
+
+ Available for more low-level use of a table; not used by the other
+ table code."""
+
+ def getCell(item, column):
+ """Render the cell contents for the item and column."""
+
+ def renderRow(item):
+ """Render a row for the given item.
+
+ Uses renderCells."""
+
+ def renderCells(item):
+ """Render the cells--the contents of a row--for the given item.
+
+ Uses renderCell."""
+
+ def renderCell(item, column):
+ """Render the cell for the item and column.
+
+ Uses getCell."""
+
+ def getItems():
+ """Returns the items to be rendered from the full set of self.items.
+
+ Should be based on batch_start and batch_size, if set.
+ """
+
+class IFormatterFactory(interface.Interface):
+ """When called returns a table formatter.
+
+ Takes the same arguments as zc.table.table.Formatter"""
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/interfaces.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows.gif
===================================================================
(Binary files differ)
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_down.gif
===================================================================
(Binary files differ)
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_down.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_up.gif
===================================================================
(Binary files differ)
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/resources/sort_arrows_up.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: python-zc.table/branches/upstream/current/src/zc/table/resources/sorting.js
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/resources/sorting.js (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/resources/sorting.js 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,19 @@
+function onSortClickStandalone(column_name, sort_on_name) {
+ window.location = addFieldToUrl(
+ window.location.href, sort_on_name+':list='+column_name);
+}
+
+function addFieldToUrl(url, field) {
+ if (url.indexOf('?') == -1)
+ sep = '?';
+ else
+ sep = '&';
+ return url + sep + field;
+}
+
+function onSortClickForm(column_name, sort_on_name) {
+ field = document.getElementById(sort_on_name);
+ if (field.value) field.value += ' ';
+ field.value += column_name;
+ field.form.submit();
+}
Added: python-zc.table/branches/upstream/current/src/zc/table/table.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/table.py (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/table.py 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,493 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Table formatting and configuration
+
+$Id: table.py 4428 2005-12-13 23:35:48Z gary $
+"""
+from xml.sax.saxutils import quoteattr
+
+from zope import interface, component
+import zope.cachedescriptors.property
+
+from zc.table import interfaces
+import zc.resourcelibrary
+
+class Formatter(object):
+ interface.implements(interfaces.IFormatter)
+ items = None
+
+ def __init__(self, context, request, items, visible_column_names=None,
+ batch_start=None, batch_size=None, prefix=None, columns=None):
+ self.context = context
+ self.request = request
+ self.annotations = {}
+ self.setItems(items)
+ if columns is None:
+ assert self.columns is not None
+ else:
+ self.columns = columns
+ if not visible_column_names:
+ self.visible_columns = self.columns
+ else:
+ self.visible_columns = [
+ self.columns_by_name[nm] for nm in visible_column_names]
+ self.batch_start = batch_start
+ self.batch_size = batch_size
+ self.prefix = prefix
+ self.cssClasses = {}
+
+ def setItems(self, items):
+ self.items = items
+
+ @zope.cachedescriptors.property.Lazy
+ def columns_by_name(self):
+ res = {}
+ for col in self.columns:
+ assert col.name not in res
+ res[col.name] = col
+ return res
+
+ def _getCSSClass(self, element):
+ klass = self.cssClasses.get(element)
+ return klass and ' class=%s' % quoteattr(klass) or ''
+
+ def __call__(self):
+ return '\n<table%s>\n%s</table>\n%s' % (
+ self._getCSSClass('table'), self.renderContents(),
+ self.renderExtra())
+
+ def renderExtra(self):
+ zc.resourcelibrary.need('zc.table')
+ return ''
+
+ def renderContents(self):
+ return ' <thead%s>\n%s </thead>\n <tbody>\n%s </tbody>\n' % (
+ self._getCSSClass('thead'), self.renderHeaderRow(),
+ self.renderRows())
+
+ def renderHeaderRow(self):
+ return ' <tr%s>\n%s </tr>\n' %(
+ self._getCSSClass('tr'), self.renderHeaders())
+
+ def renderHeaders(self):
+ return ''.join(
+ [self.renderHeader(col) for col in self.visible_columns])
+
+ def renderHeader(self, column):
+ return ' <th%s>\n %s\n </th>\n' % (
+ self._getCSSClass('th'), self.getHeader(column))
+
+ def getHeaders(self):
+ return [self.getHeader(column) for column in self.visible_columns]
+
+ def getHeader(self, column):
+ return column.renderHeader(self)
+
+ def renderRows(self):
+ return ''.join([self.renderRow(item) for item in self.getItems()])
+
+ def getRows(self):
+ for item in self.getItems():
+ yield [column.renderCell(item, self)
+ for column in self.visible_columns]
+
+ def renderRow(self, item):
+ return ' <tr%s>\n%s </tr>\n' % (
+ self._getCSSClass('tr'), self.renderCells(item))
+
+ def renderCells(self, item):
+ return ''.join(
+ [self.renderCell(item, col) for col in self.visible_columns])
+
+ def renderCell(self, item, column):
+ return ' <td%s>\n %s\n </td>\n' % (
+ self._getCSSClass('td'), self.getCell(item, column),)
+
+ def getCells(self, item):
+ return [self.getCell(item, column) for column in self.visible_columns]
+
+ def getCell(self, item, column):
+ return column.renderCell(item, self)
+
+ def getItems(self):
+ batch_start = self.batch_start or 0
+ batch_size = self.batch_size or 0
+ if not self.batch_size:
+ if not batch_start: # ok, no work to be done.
+ for i in self.items:
+ yield i
+ raise StopIteration
+ batch_end = None
+ else:
+ batch_end = batch_start + batch_size
+ try:
+ for i in self.items[batch_start:batch_end]:
+ yield i
+ except (AttributeError, TypeError):
+ for i, item in enumerate(self.items):
+ if batch_end is not None and i >= batch_end:
+ return
+ if i >= batch_start:
+ yield item
+
+# sorting helpers
+
+class ColumnSortedItems(object):
+ # not intended to be persistent!
+ """a wrapper for items that sorts lazily based on ISortableColumns.
+
+ Given items, a list of (column name, reversed boolean) pairs beginning
+ with the primary sort column, and the formatter, supports iteration, len,
+ and __getitem__ access including slices.
+ """
+ interface.implements(interfaces.IColumnSortedItems)
+
+ formatter = None
+
+ def __init__(self, items, sort_on):
+ self._items = items
+ self.sort_on = sort_on # tuple of (column name, reversed) pairs
+ self._cache = []
+ self._iterable = None
+
+ @property
+ def items(self):
+ if getattr(self._items, '__getitem__', None) is not None:
+ return self._items
+ else:
+ return self._iter()
+
+ def _iter(self):
+ # this design is intended to handle multiple simultaneous iterations
+ ix = 0
+ cache = self._cache
+ iterable = self._iterable
+ if iterable is None:
+ iterable = self._iterable = iter(self._items)
+ while True:
+ try:
+ yield cache[ix]
+ except IndexError:
+ next = iterable.next() # let StopIteration fall through
+ cache.append(next)
+ yield next
+ ix += 1
+
+ def setFormatter(self, formatter):
+ self.formatter = formatter
+
+ @property
+ def sorters(self):
+ res = []
+ for nm, reversed in self.sort_on:
+ column = self.formatter.columns_by_name[nm]
+ if reversed:
+ res.append(column.reversesort)
+ else:
+ res.append(column.sort)
+ return res
+
+ def __getitem__(self, key):
+ if isinstance(key, slice):
+ start = slice.start
+ stop = slice.stop
+ stride = slice.step
+ else:
+ start = stop = key
+ stride = 1
+
+ items = self.items
+ if not self.sort_on:
+ try:
+ return items.__getitem__(key)
+ except (AttributeError, TypeError):
+ if stride != 1:
+ raise NotImplemented
+ res = []
+ for ix, val in enumerate(items):
+ if ix >= start:
+ res.append(val)
+ if ix >= stop:
+ break
+
+ if isinstance(key, slice):
+ return res
+ elif res:
+ return res[0]
+ else:
+ raise IndexError, 'list index out of range'
+
+ items = self.sorters[0](
+ items, self.formatter, start, stop, self.sorters[1:])
+
+ if isinstance(key, slice):
+ return items[start:stop:stride]
+ else:
+ return items[key]
+
+ def __nonzero__(self):
+ try:
+ iter(self.items).next()
+ except StopIteration:
+ return False
+ return True
+
+ def __iter__(self):
+ if not self.sort_on:
+ return iter(self.items)
+ else:
+ sorters = self.sorters
+ return iter(sorters[0](
+ self.items, self.formatter, 0, None, sorters[1:]))
+
+ def __len__(self):
+ return len(self.items)
+
+def getRequestSortOn(request, sort_on_name):
+ """get the sorting values from the request.
+
+ Returns a list of (name, reversed) pairs.
+ """
+ # useful for code that wants to get the sort on values themselves
+ sort_on = None
+ sorting = request.form.get(sort_on_name)
+ if sorting:
+ offset = 0
+ res = {}
+ for ix, name in enumerate(sorting):
+ val = res.get(name)
+ if val is None:
+ res[name] = [ix + offset, name, False]
+ else:
+ val[0] = ix + offset
+ val[2] = not val[2]
+ if res:
+ res = res.values()
+ res.sort()
+ res.reverse()
+ sort_on = [[nm, reverse] for ix, nm, reverse in res]
+ return sort_on
+
+def getMungedSortOn(request, sort_on_name, sort_on):
+ """get the sorting values from the request.
+
+ optionally begins with sort_on values. Returns a list of (name, reversed)
+ pairs.
+ """
+ res = getRequestSortOn(request, sort_on_name)
+ if res is None:
+ res = sort_on
+ elif sort_on:
+ for nm, reverse in sort_on:
+ for ix, (res_nm, res_reverse) in enumerate(res):
+ if nm == res_nm:
+ res[ix][1] = not (res_reverse ^ reverse)
+ break
+ else:
+ res.append([nm, reverse])
+ return res
+
+def getSortOnName(prefix=None):
+ """convert the table prefix to the 'sort on' name used in forms"""
+ # useful for code that wants to get the sort on values themselves
+ sort_on_name = 'sort_on'
+ if prefix is not None:
+ if not prefix.endswith('.'):
+ prefix += '.'
+ sort_on_name = prefix + sort_on_name
+ return sort_on_name
+
+
+class SortingFormatterMixin(object):
+ """automatically munges sort_on values with sort settings in the request.
+ """
+
+ def __init__(self, context, request, items, visible_column_names=None,
+ batch_start=None, batch_size=None, prefix=None, columns=None,
+ sort_on=None, ignore_request=False):
+ if not ignore_request:
+ sort_on = getMungedSortOn(request, getSortOnName(prefix), sort_on)
+ else:
+ sort_on = sort_on
+ if sort_on or getattr(items, '__getitem__', None) is None:
+ items = ColumnSortedItems(items, sort_on)
+
+ super(SortingFormatterMixin, self).__init__(
+ context, request, items, visible_column_names,
+ batch_start, batch_size, prefix, columns)
+
+ if sort_on:
+ items.setFormatter(self)
+
+ def setItems(self, items):
+ if (interfaces.IColumnSortedItems.providedBy(self.items) and
+ not interfaces.IColumnSortedItems.providedBy(items)):
+ items = ColumnSortedItems(items, self.items.sort_on)
+ if interfaces.IColumnSortedItems.providedBy(items):
+ items.setFormatter(self)
+ self.items = items
+
+class AbstractSortFormatterMixin(object):
+ """provides sorting UI: concrete classes must declare script_name."""
+
+ script_name = None # Must be defined in subclass
+
+ def getHeader(self, column):
+ contents = column.renderHeader(self)
+ if (interfaces.ISortableColumn.providedBy(column)):
+ contents = self._addSortUi(contents, column)
+ return contents
+
+ def _addSortUi(self, header, column):
+ columnName = column.name
+ resource_path = component.getAdapter(self.request, name='zc.table')()
+ if (interfaces.IColumnSortedItems.providedBy(self.items) and
+ self.items.sort_on):
+ sortColumnName, sortReversed = self.items.sort_on[0]
+ else:
+ sortColumnName = sortReversed = None
+ if columnName == sortColumnName:
+ path = component.getAdapter(self.request, name='zc.table')()
+ if sortReversed:
+ dirIndicator = ('<img src="%s/sort_arrows_up.gif" '
+ 'style="vertical-align: top; '
+ 'margin-top: 0px" '
+ 'alt="(ascending)"/>' % resource_path)
+ else:
+ dirIndicator = ('<img src="%s/sort_arrows_down.gif" '
+ 'style="vertical-align: top; '
+ 'margin-top: 0px" '
+ 'alt="(descending)"/>' % resource_path)
+ else:
+ dirIndicator = ('<img src="%s/sort_arrows.gif" '
+ 'style="vertical-align: top; '
+ 'margin-top: 0px" '
+ 'alt="(sortable)"/>' % resource_path)
+ sort_on_name = getSortOnName(self.prefix)
+ script_name = self.script_name
+ return self._header_template(locals())
+
+ def _header_template(self, options):
+ # The <img> below is intentionally not in the <span> because IE
+ # doesn't underline it correctly when the CSS class is changed.
+ # XXX can we avoid changing the className and get a similar effect?
+ template = """
+ <span class="zc-table-sortable"
+ onclick="javascript: %(script_name)s(
+ '%(columnName)s', '%(sort_on_name)s')"
+ onMouseOver="javascript: this.className='sortable zc-table-sortable'"
+ onMouseOut="javascript: this.className='zc-table-sortable'">
+ %(header)s</span> %(dirIndicator)s
+ """
+ return template % options
+
+class StandaloneSortFormatterMixin(AbstractSortFormatterMixin):
+ "A version of the sort formatter mixin for standalone tables, not forms"
+
+ script_name = 'onSortClickStandalone'
+
+
+class FormSortFormatterMixin(AbstractSortFormatterMixin):
+ """A version of the sort formatter mixin that plays well within forms.
+ Does *not* draw a form tag itself, and requires something else to do so.
+ """
+
+ def __init__(self, context, request, items, visible_column_names=None,
+ batch_start=None, batch_size=None, prefix=None, columns=None,
+ sort_on=None, ignore_request=False):
+ if not ignore_request:
+ sort_on = (
+ getRequestSortOn(request, getSortOnName(prefix)) or sort_on)
+ else:
+ sort_on = sort_on
+ if sort_on or getattr(items, '__getitem__', None) is None:
+ items = ColumnSortedItems(items, sort_on)
+
+ super(FormSortFormatterMixin, self).__init__(
+ context, request, items, visible_column_names,
+ batch_start, batch_size, prefix, columns)
+
+ if sort_on:
+ items.setFormatter(self)
+
+ script_name = 'onSortClickForm'
+
+ def renderExtra(self):
+ """Render the hidden input field used to keep up with sorting"""
+ if (interfaces.IColumnSortedItems.providedBy(self.items) and
+ self.items.sort_on):
+ value = []
+ for name, reverse in reversed(self.items.sort_on):
+ value.append(name)
+ if reverse:
+ value.append(name)
+ value = ' '.join(value)
+ else:
+ value = ''
+
+ sort_on_name = getSortOnName(self.prefix)
+ return '<input type="hidden" name=%s id=%s value=%s />\n' % (
+ quoteattr(sort_on_name+":tokens"),
+ quoteattr(sort_on_name),
+ quoteattr(value)
+ ) + super(FormSortFormatterMixin, self).renderExtra()
+
+ def setItems(self, items):
+ if (interfaces.IColumnSortedItems.providedBy(self.items) and
+ not interfaces.IColumnSortedItems.providedBy(items)):
+ items = ColumnSortedItems(items, self.items.sort_on)
+ if interfaces.IColumnSortedItems.providedBy(items):
+ items.setFormatter(self)
+ self.items = items
+
+class AlternatingRowFormatterMixin(object):
+ row_classes = ('even', 'odd')
+
+ def renderRows(self):
+ self.row = 0
+ return super(AlternatingRowFormatterMixin, self).renderRows()
+
+ def renderRow(self, item):
+ self.row += 1
+ klass = self.cssClasses.get('tr', '')
+ if klass:
+ klass += ' '
+ return ' <tr class=%s>\n%s </tr>\n' % (
+ quoteattr(klass + self.row_classes[self.row % 2]),
+ self.renderCells(item))
+
+
+# TODO Remove all these concrete classes
+
+class SortingFormatter(SortingFormatterMixin, Formatter):
+ pass
+
+class AlternatingRowFormatter(AlternatingRowFormatterMixin, Formatter):
+ pass
+
+class StandaloneSortFormatter(
+ SortingFormatterMixin, StandaloneSortFormatterMixin, Formatter):
+ pass
+
+class FormSortFormatter(FormSortFormatterMixin, Formatter):
+ pass
+
+class StandaloneFullFormatter(
+ SortingFormatterMixin, StandaloneSortFormatterMixin,
+ AlternatingRowFormatterMixin, Formatter):
+ pass
+
+class FormFullFormatter(
+ FormSortFormatterMixin, AlternatingRowFormatterMixin, Formatter):
+ pass
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/table.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/testing.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/testing.py (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/testing.py 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,35 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Testing helpers
+
+$Id: testing.py 2520 2005-06-27 21:26:18Z benji $
+"""
+from zope import interface, component
+from zope.app.testing import ztapi
+import zc.table.interfaces
+import zc.table.table
+
+
+class SimpleFormatter(zc.table.table.Formatter):
+ interface.classProvides(zc.table.interfaces.IFormatterFactory)
+
+
+def setUp(test):
+ ztapi.provideUtility(zc.table.interfaces.IFormatterFactory,
+ SimpleFormatter)
+ assert component.getUtility(zc.table.interfaces.IFormatterFactory) != None
+
+
+def tearDown(test):
+ ztapi.unprovideUtility(zc.table.interfaces.IFormatterFactory)
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/testing.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/tests.py
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/tests.py (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/tests.py 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,60 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""
+
+$Id: tests.py 4428 2005-12-13 23:35:48Z gary $
+"""
+import unittest
+from zope import component
+from zope.app.testing import placelesssetup
+import zope.publisher.interfaces.browser
+import zope.schema.interfaces
+import zope.app.form.browser
+
+def columnSetUp(test):
+ placelesssetup.setUp(test)
+ component.provideAdapter(
+ zope.app.form.browser.TextWidget,
+ (zope.schema.interfaces.ITextLine,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ),
+ zope.app.form.interfaces.IInputWidget)
+ component.provideAdapter(
+ zope.app.form.browser.CheckBoxWidget,
+ (zope.schema.interfaces.IBool,
+ zope.publisher.interfaces.browser.IBrowserRequest,
+ ),
+ zope.app.form.interfaces.IInputWidget)
+
+def test_suite():
+ from zope.testing import doctest
+ return unittest.TestSuite((
+ doctest.DocFileSuite('README.txt',
+ optionflags=doctest.NORMALIZE_WHITESPACE+doctest.ELLIPSIS,
+ ),
+ doctest.DocFileSuite(
+ 'column.txt',
+ setUp = columnSetUp, tearDown=placelesssetup.tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE+doctest.ELLIPSIS,
+ ),
+ doctest.DocFileSuite(
+ 'fieldcolumn.txt',
+ setUp = columnSetUp, tearDown=placelesssetup.tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE+doctest.ELLIPSIS,
+ ),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
+
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/tests.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc/table/zc.table-configure.zcml
===================================================================
--- python-zc.table/branches/upstream/current/src/zc/table/zc.table-configure.zcml (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc/table/zc.table-configure.zcml 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1 @@
+<include package="zc.table"/>
\ No newline at end of file
Property changes on: python-zc.table/branches/upstream/current/src/zc/table/zc.table-configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/PKG-INFO
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/PKG-INFO (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/PKG-INFO 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,13 @@
+Metadata-Version: 1.0
+Name: zc.table
+Version: 0.5.1
+Summary: This is a Zope 3 extension that helps with the construction of (HTML) tables.
+Features include dynamic HTML table generation, batching and sorting.
+
+Home-page: UNKNOWN
+Author: Zope Project
+Author-email: zope3-dev at zope.org
+License: ZPL
+Description: UNKNOWN
+Keywords: zope zope3
+Platform: UNKNOWN
Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/SOURCES.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/SOURCES.txt (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/SOURCES.txt 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1,29 @@
+Makefile
+ZopePublicLicense.txt
+setup.py
+src/zc/__init__.py
+src/zc.table.egg-info/PKG-INFO
+src/zc.table.egg-info/SOURCES.txt
+src/zc.table.egg-info/dependency_links.txt
+src/zc.table.egg-info/namespace_packages.txt
+src/zc.table.egg-info/not-zip-safe
+src/zc.table.egg-info/requires.txt
+src/zc.table.egg-info/top_level.txt
+src/zc/table/README.txt
+src/zc/table/SETUP.cfg
+src/zc/table/TODO.txt
+src/zc/table/__init__.py
+src/zc/table/column.py
+src/zc/table/column.txt
+src/zc/table/configure.zcml
+src/zc/table/fieldcolumn.py
+src/zc/table/fieldcolumn.txt
+src/zc/table/interfaces.py
+src/zc/table/table.py
+src/zc/table/testing.py
+src/zc/table/tests.py
+src/zc/table/zc.table-configure.zcml
+src/zc/table/resources/sort_arrows.gif
+src/zc/table/resources/sort_arrows_down.gif
+src/zc/table/resources/sort_arrows_up.gif
+src/zc/table/resources/sorting.js
Property changes on: python-zc.table/branches/upstream/current/src/zc.table.egg-info/SOURCES.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/dependency_links.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/dependency_links.txt (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/dependency_links.txt 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1 @@
+http://download.zope.org/distribution/
Property changes on: python-zc.table/branches/upstream/current/src/zc.table.egg-info/dependency_links.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/namespace_packages.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/namespace_packages.txt (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/namespace_packages.txt 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1 @@
+zc
Property changes on: python-zc.table/branches/upstream/current/src/zc.table.egg-info/namespace_packages.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/not-zip-safe
===================================================================
Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/requires.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/requires.txt (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/requires.txt 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1 @@
+zc.resourcelibrary >= 0.5
\ No newline at end of file
Property changes on: python-zc.table/branches/upstream/current/src/zc.table.egg-info/requires.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: python-zc.table/branches/upstream/current/src/zc.table.egg-info/top_level.txt
===================================================================
--- python-zc.table/branches/upstream/current/src/zc.table.egg-info/top_level.txt (rev 0)
+++ python-zc.table/branches/upstream/current/src/zc.table.egg-info/top_level.txt 2006-10-25 11:07:23 UTC (rev 343)
@@ -0,0 +1 @@
+zc
Property changes on: python-zc.table/branches/upstream/current/src/zc.table.egg-info/top_level.txt
___________________________________________________________________
Name: svn:eol-style
+ native
More information about the pkg-zope-commits
mailing list