zc.table-0.9.0/0000775000177100020040000000000012053234232014344 5ustar menesismenesis00000000000000zc.table-0.9.0/README.txt0000664000177100020040000000022412053233200016032 0ustar menesismenesis00000000000000This is a Zope 3 extension that helps with the construction of (HTML) tables. Features include dynamic HTML table generation, batching and sorting. zc.table-0.9.0/src/0000775000177100020040000000000012053234232015133 5ustar menesismenesis00000000000000zc.table-0.9.0/src/zc/0000775000177100020040000000000012053234232015547 5ustar menesismenesis00000000000000zc.table-0.9.0/src/zc/__init__.py0000664000177100020040000000031012053233200017644 0ustar menesismenesis00000000000000# this is a namespace package try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) zc.table-0.9.0/src/zc/table/0000775000177100020040000000000012053234232016636 5ustar menesismenesis00000000000000zc.table-0.9.0/src/zc/table/testing.py0000664000177100020040000000237312053233200020664 0ustar menesismenesis00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Testing helpers $Id: testing.py 2520 2005-06-27 21:26:18Z benji $ """ from zope import interface, component import zc.table.interfaces import zc.table.table class SimpleFormatter(zc.table.table.Formatter): interface.classProvides(zc.table.interfaces.IFormatterFactory) def setUp(test): gsm = component.getGlobalSiteManager() gsm.registerUtility(SimpleFormatter, zc.table.interfaces.IFormatterFactory) assert component.getUtility(zc.table.interfaces.IFormatterFactory) != None def tearDown(test): gsm = component.getGlobalSiteManager() gsm.unregisterUtility(provided=zc.table.interfaces.IFormatterFactory) zc.table-0.9.0/src/zc/table/README.txt0000664000177100020040000010237212053233200020333 0ustar menesismenesis00000000000000====== 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 and 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()
First Third
a0 c0
a2 c2
a1 c1
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 = '\n' >>> html += '\n'+ formatter.renderHeaders() + '\n' >>> for index, row in enumerate(formatter.getRows()): ... if index % 2: ... html += '' ... else: ... html += '' ... for index, cell in enumerate(row): ... if index == 0: ... html += '\n' >>> html += '
' ... else: ... html += '' ... html += cell + '' ... html += '
' >>> print html
First Third
a0c0
a2c2
a1c1
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() ...
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() ...
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()
First Third
a0 c0
>>> formatter.batch_start=1 >>> print formatter()
First Third
a2 c2
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 '
%s
' % ( ... 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()
First
Third
a0 c0
a2 c2
a1 c1
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()
First Third
a0 c0
a2 c2
a1 c1
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()
First Third
a0 c0
a2 c2
a1 c1
Note that this formatter also plays nicely with the other CSS classes defined by the formatter: >>> formatter.cssClasses['tr'] = 'list' >>> print formatter()
First Third
a0 c0
a2 c2
a1 c1
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()
First Third
a2 c2
a1 c1
a0 c0
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()
First Third
a2 c2
a1 c1
a1 c7
a1 c8
a1 c9
a0 c0
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()
First Third
a0 c0
a1 c1
a1 c7
a1 c8
a1 c9
a2 c2
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()
First Third
a1 c1
a0 c0
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()
First... Third...
a0 c0
a2 c2
a1 c1
... 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()
First... Third...
a0 c0
a2 c2
a1 c1
... 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()
First... Third...
a2 c2
a1 c1
a0 c0
... 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()
First... Third...
a2 c2
a1 c1
a1 c7
a1 c8
a1 c9
a0 c0
... 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()
First Third...
a2 c2
a1 c1
a1 c7
a1 c8
a1 c9
a0 c0
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()
First Third
a0 c0
a2 c2
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()
First Third
a2 c2
a1 c1
zc.table-0.9.0/src/zc/table/fieldcolumn.py0000664000177100020040000001567512053233200021521 0ustar menesismenesis00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## import re from xml.sax.saxutils import quoteattr from zope import component import zope.schema.interfaces import zope.formlib.interfaces import zope.formlib.form from zope.formlib.interfaces import IInputWidget, IDisplayWidget from zope.formlib.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 """ __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 '' zc.table-0.9.0/src/zc/table/column.py0000664000177100020040000002610012053233200020476 0ustar menesismenesis00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """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.formlib.interfaces import IInputWidget from zope.formlib.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( cmp=lambda a, b: multiplier*cmp(a, b), key=lambda item: getSortKey(item, 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).replace('&', '&') \ .replace('<', '<') \ .replace('>', '>') 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 '%s' % (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 = "\n%s\n%s' % ( self._getCSSClass('table'), self.renderContents(), self.renderExtra()) def renderExtra(self): zc.resourcelibrary.need('zc.table') return '' def renderContents(self): return ' \n%s \n \n%s \n' % ( self._getCSSClass('thead'), self.renderHeaderRow(), self.renderRows()) def renderHeaderRow(self): return ' \n%s \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 ' \n %s\n \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 ' \n%s \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 ' \n %s\n \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: if sortReversed: dirIndicator = ('' % resource_path) else: dirIndicator = ('' % resource_path) else: dirIndicator = ('' % resource_path) sort_on_name = getSortOnName(self.prefix) script_name = self.script_name return self._header_template(locals()) def _header_template(self, options): # The below is intentionally not in the 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 = """ %(header)s %(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 '\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 ' \n%s \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 zc.table-0.9.0/src/zc/table/batching.pt0000664000177100020040000000361112053233200020755 0ustar menesismenesis00000000000000
< Prev < Prev Next > Next >
zc.table-0.9.0/src/zc/table/resources/0000775000177100020040000000000012053234232020650 5ustar menesismenesis00000000000000zc.table-0.9.0/src/zc/table/resources/sort_arrows.gif0000664000177100020040000000011412053233200023711 0ustar menesismenesis00000000000000GIF89a ‘ÿÿÿÌÌÌ™™™ÿÿÿ!ù, œ'p+ÊœƒHJ%²Ö ûl"fMGs-Õ£ÉR;zc.table-0.9.0/src/zc/table/resources/sort_arrows_down.gif0000664000177100020040000000013312053233200024741 0ustar menesismenesis00000000000000GIF89a ¢òe"ÌÌÌÿÿÿ™™™ÿÿÿ!ù,  HJ#û#¥¤ÌZ7º÷B(ŽÂgr@ªbƒ `Jû>Kó$;zc.table-0.9.0/src/zc/table/resources/sorting.js0000664000177100020040000000101412053233200022661 0ustar menesismenesis00000000000000function 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(); } zc.table-0.9.0/src/zc/table/resources/sort_arrows_up.gif0000664000177100020040000000013312053233200024416 0ustar menesismenesis00000000000000GIF89a ¢òe"ÌÌÌÿÿÿ™™™ÿÿÿ!ù,  HJ#û¥¤ÌZ7º÷B(ŽÂgrAªbƒ`Jû>Kó$;zc.table-0.9.0/src/zc/table/batching.py0000664000177100020040000001057412053233200020770 0ustar menesismenesis00000000000000############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Table formatting and configuration """ from zope import interface from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile import zc.table.table import zc.table.interfaces unspecified = object() class Formatter(zc.table.table.FormSortFormatterMixin, zc.table.table.AlternatingRowFormatterMixin, zc.table.table.Formatter): interface.classProvides(zc.table.interfaces.IFormatterFactory) def __init__(self, context, request, items, visible_column_names=None, batch_start=None, batch_size=unspecified, prefix=None, columns=None, sort_on=None): if batch_size is unspecified: batch_size = 20 if prefix is None: prefix = 'zc.table' super(Formatter, self).__init__( context, request, items, visible_column_names, batch_start, batch_size, prefix, columns, sort_on=sort_on, ) @property def batch_change_name(self): return self.prefix + '.batch_change' @property def batch_start_name(self): return self.prefix + '.batch_start' _batch_start = None _batch_start_computed = False def setPrefix(self, prefix): super(Formatter, self).setPrefix(prefix) self._batch_start_computed = False @apply def batch_start(): def fget(self): if not self._batch_start_computed: self.updateBatching() return self._batch_start def fset(self, value): self._batch_start = value self._batch_start_computed = False def fdel(self): self._batch_start = None return property(fget, fset, fdel) def updateBatching(self): request = self.request if self._batch_start is None: try: self.batch_start = int(request.get(self.batch_start_name, '0')) except ValueError: self._batch_start = 0 # Handle requests to change batches: change = request.get(self.batch_change_name) if change == "next": self._batch_start += self.batch_size try: length = len(self.items) except TypeError: for length, ob in enumerate(self.items): if length > self._batch_start: break else: self._batch_start = length else: if self._batch_start > length: self._batch_start = length elif change == "back": self._batch_start -= self.batch_size if self._batch_start < 0: self._batch_start = 0 self.next_batch_start = self._batch_start + self.batch_size try: self.items[self.next_batch_start] except IndexError: self.next_batch_start = None self.previous_batch_start = self._batch_start - self.batch_size if self.previous_batch_start < 0: self.previous_batch_start = None self._batch_start_computed = True batching_template = ViewPageTemplateFile('batching.pt') def renderExtra(self): if not self._batch_start_computed: self.updateBatching() return self.batching_template() + super(Formatter, self).renderExtra() def __call__(self): return ('\n' '
' '\n' '\n' % self.prefix) + self.renderContents() + '
\n' + self.renderExtra() + '
\n' ) zc.table-0.9.0/src/zc/table/__init__.py0000664000177100020040000000135612053233200020746 0ustar menesismenesis00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """HTML Table support $Id: __init__.py 1335 2005-04-19 05:21:10Z gary $ """ from zc.table.table import Formatter zc.table-0.9.0/src/zc/table/fieldcolumn.txt0000664000177100020040000004060612053233200021700 0ustar menesismenesis00000000000000FieldColumn =========== 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) ... salutation = schema.Choice( ... title=u'Salutation', ... values = ['Mr','Ms'], ... ) >>> class Contact: ... interface.implements(IContact) ... def __init__(self, id, name, email, salutation): ... self.id = id ... self.name = name ... self.email = email ... self.salutation = salutation >>> contacts = ( ... Contact('1', 'Bob Smith', 'bob@zope.com', 'Mr'), ... Contact('2', 'Sally Baker', 'sally@zope.com', 'Ms'), ... Contact('3', 'Jethro Tul', 'jethro@zope.com', 'Mr'), ... Contact('4', 'Joe Walsh', 'joe@zope.com', 'Mr'), ... ) 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) ... >>> class BindingContactColumn(ContactColumn): ... def getFieldContext(self, item, formatter): ... return item ... >>> columns = (ContactColumn(IContact["name"]), ... ContactColumn(IContact["email"]), ... BindingContactColumn(IContact["salutation"]) ... ) 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()
Name Email Address Salutation
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@zope.com' >>> print formatter()
Name Email Address Salutation
and the contact data is unchanged: >>> contacts[3].email 'joe@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@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@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', ConstraintNotSatisfied(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] >>> 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()
Bit 0 Bit 1
>>> 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 pprint import pprint >>> pprint(input) {'0': True, '3': False} >>> columns[1].update(data, input, formatter) True >>> data ([0, 2], [1, 1], [2, 2], [3, 1]) zc.table-0.9.0/src/zc/table/configure.zcml0000664000177100020040000000027612053233200021505 0ustar menesismenesis00000000000000 zc.table-0.9.0/src/zc/table/interfaces.py0000664000177100020040000002100112053233200021317 0ustar menesismenesis00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """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 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 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""" zc.table-0.9.0/src/zc/table/TODO.txt0000664000177100020040000000033012053233200020132 0ustar menesismenesis00000000000000add 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 zc.table-0.9.0/src/zc/table/column.txt0000664000177100020040000003633412053233200020677 0ustar menesismenesis00000000000000Useful 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@zope.com'), ... Contact('2', 'Sally Baker', 'sally@zope.com'), ... Contact('3', 'Jethro Tul', 'jethro@zope.com'), ... Contact('4', 'Joe Walsh', 'joe@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()
Name Email address
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@zope.com' >>> print formatter()
Name Email address
and the contact data is unchanged: >>> contacts[3].email 'joe@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@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@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'', ConstraintNotSatisfied(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()
Bit 0 Bit 1
>>> 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 pprint 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()
<>& encoding bug ================ There was a bug in column.py, it did not encode the characters <>& to < > & >>> bugcontacts = ( ... Contact('1', 'Bob ', 'bob@zope.com'), ... Contact('2', 'Sally & Baker', 'sally@zope.com'), ... ) We'll define columns that displays name and email addresses. >>> from zc.table import column >>> bugcolumns = ( ... column.GetterColumn( ... title="Name", name="name", ... getter=lambda contact, formatter: contact.name, ... ), ... column.GetterColumn( ... title="E-mail", name="email", ... getter=lambda contact, formatter: contact.email, ... ), ... ) >>> request = zope.publisher.browser.TestRequest() >>> context = None >>> formatter = table.Formatter( ... context, request, bugcontacts, columns=bugcolumns) >>> print formatter()
Name E-mail
Bob <Smith> bob@zope.com
Sally & Baker sally@zope.com
>>> from zc.table import column >>> bug2columns = ( ... column.FieldEditColumn( ... "Name", "test", IContact["name"], ... lambda contact: contact.id, ... ), ... column.FieldEditColumn( ... "Email address", "test", IContact["email"], ... lambda contact: contact.id, ... ), ... ) >>> formatter = table.Formatter( ... context, request, bugcontacts, columns=bug2columns) >>> print formatter()
Name Email address
zc.table-0.9.0/src/zc.table.egg-info/0000775000177100020040000000000012053234232020327 5ustar menesismenesis00000000000000zc.table-0.9.0/src/zc.table.egg-info/namespace_packages.txt0000664000177100020040000000000312053234232024653 0ustar menesismenesis00000000000000zc zc.table-0.9.0/src/zc.table.egg-info/not-zip-safe0000664000177100020040000000000112053233224022555 0ustar menesismenesis00000000000000 zc.table-0.9.0/src/zc.table.egg-info/requires.txt0000664000177100020040000000031212053234232022723 0ustar menesismenesis00000000000000setuptools zc.resourcelibrary >= 0.6 zope.browserpage >= 3.10 zope.formlib >= 4 zope.cachedescriptors zope.component zope.formlib zope.i18n zope.interface zope.schema [test] zope.testing zope.publisherzc.table-0.9.0/src/zc.table.egg-info/dependency_links.txt0000664000177100020040000000000112053234232024375 0ustar menesismenesis00000000000000 zc.table-0.9.0/src/zc.table.egg-info/SOURCES.txt0000664000177100020040000000155512053234232022221 0ustar menesismenesis00000000000000CHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt TODO.txt bootstrap.py buildout.cfg 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/TODO.txt src/zc/table/__init__.py src/zc/table/batching.pt src/zc/table/batching.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/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.jszc.table-0.9.0/src/zc.table.egg-info/PKG-INFO0000664000177100020040000000356412053234232021434 0ustar menesismenesis00000000000000Metadata-Version: 1.1 Name: zc.table Version: 0.9.0 Summary: Zope table Home-page: http://pypi.python.org/pypi/zc.table/ Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This is a Zope 3 extension that helps with the construction of (HTML) tables. Features include dynamic HTML table generation, batching and sorting. CHANGES ======= 0.9.0 (2012-11-21) ------------------ - Using Python's ``doctest`` module instead of deprecated ``zope.testing.doctest``. - Removed dependency on ``zope.app.testing`` and ``zope.app.form``. - Add test extra, move ``zope.testing`` to it. 0.8.1 (2010-05-25) ------------------ - Replaced html entities with unicode entities since they are xthml valid. 0.8.0 (2009-07-23) ------------------ - Updated tests to latest packages. 0.7.0 (2008-05-20) ------------------ - Fixed HTML-encoding of cell contents for ``GetterColumn``. - Add ``href`` attributes (for MSIE) and fix up JavaScript on Next/Prev links for batching. - Update packaging. 0.6 (2006-09-22) ---------------- Initial release on Cheeseshop. Keywords: zope zope3 Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zc.table-0.9.0/src/zc.table.egg-info/top_level.txt0000664000177100020040000000000312053234232023052 0ustar menesismenesis00000000000000zc zc.table-0.9.0/buildout.cfg0000664000177100020040000000014112053233200016642 0ustar menesismenesis00000000000000[buildout] develop = . parts = test [test] recipe = zc.recipe.testrunner eggs = zc.table [test] zc.table-0.9.0/COPYRIGHT.txt0000664000177100020040000000004012053233200016441 0ustar menesismenesis00000000000000Zope Foundation and Contributorszc.table-0.9.0/setup.py0000664000177100020040000000427712053233200016062 0ustar menesismenesis00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Setup for zc.table package """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup( name="zc.table", version='0.9.0', url="http://pypi.python.org/pypi/zc.table/", install_requires=[ 'setuptools', 'zc.resourcelibrary >= 0.6', 'zope.browserpage >= 3.10', 'zope.formlib >= 4', 'zope.cachedescriptors', 'zope.component', 'zope.formlib', 'zope.i18n', 'zope.interface', 'zope.schema', ], extras_require=dict( test=['zope.testing', 'zope.publisher']), packages=find_packages('src'), package_dir= {'':'src'}, namespace_packages=['zc'], package_data = { '': ['*.txt', '*.zcml', '*.gif', '*.js'], 'zc.table':['resources/*', '*.pt'], }, author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description="Zope table", long_description=( read('README.txt') + '\n\n' + read('CHANGES.txt') ), license='ZPL 2.1', keywords="zope zope3", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], zip_safe=False, ) zc.table-0.9.0/PKG-INFO0000664000177100020040000000356412053234232015451 0ustar menesismenesis00000000000000Metadata-Version: 1.1 Name: zc.table Version: 0.9.0 Summary: Zope table Home-page: http://pypi.python.org/pypi/zc.table/ Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This is a Zope 3 extension that helps with the construction of (HTML) tables. Features include dynamic HTML table generation, batching and sorting. CHANGES ======= 0.9.0 (2012-11-21) ------------------ - Using Python's ``doctest`` module instead of deprecated ``zope.testing.doctest``. - Removed dependency on ``zope.app.testing`` and ``zope.app.form``. - Add test extra, move ``zope.testing`` to it. 0.8.1 (2010-05-25) ------------------ - Replaced html entities with unicode entities since they are xthml valid. 0.8.0 (2009-07-23) ------------------ - Updated tests to latest packages. 0.7.0 (2008-05-20) ------------------ - Fixed HTML-encoding of cell contents for ``GetterColumn``. - Add ``href`` attributes (for MSIE) and fix up JavaScript on Next/Prev links for batching. - Update packaging. 0.6 (2006-09-22) ---------------- Initial release on Cheeseshop. Keywords: zope zope3 Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zc.table-0.9.0/bootstrap.py0000664000177100020040000000316512053233200016732 0ustar menesismenesis00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id: bootstrap.py 68905 2006-06-29 10:46:56Z jim $ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', 'from setuptools.command.easy_install import main; main()', '-mqNxd', tmpeggs, 'zc.buildout', {'PYTHONPATH': ws.find(pkg_resources.Requirement.parse('setuptools')).location }, ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zc.table-0.9.0/setup.cfg0000664000177100020040000000007312053234232016165 0ustar menesismenesis00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zc.table-0.9.0/TODO.txt0000664000177100020040000000030612053233200015643 0ustar menesismenesis00000000000000To-do ----- - Get an updated version of the buildout/testrunner recipes to generate a usable test runner (bin/test). Currently needs to be manually hacked to add the path for parts/zope3/src. zc.table-0.9.0/CHANGES.txt0000664000177100020040000000134112053233200016146 0ustar menesismenesis00000000000000CHANGES ======= 0.9.0 (2012-11-21) ------------------ - Using Python's ``doctest`` module instead of deprecated ``zope.testing.doctest``. - Removed dependency on ``zope.app.testing`` and ``zope.app.form``. - Add test extra, move ``zope.testing`` to it. 0.8.1 (2010-05-25) ------------------ - Replaced html entities with unicode entities since they are xthml valid. 0.8.0 (2009-07-23) ------------------ - Updated tests to latest packages. 0.7.0 (2008-05-20) ------------------ - Fixed HTML-encoding of cell contents for ``GetterColumn``. - Add ``href`` attributes (for MSIE) and fix up JavaScript on Next/Prev links for batching. - Update packaging. 0.6 (2006-09-22) ---------------- Initial release on Cheeseshop. zc.table-0.9.0/LICENSE.txt0000664000177100020040000000402612053233200016163 0ustar menesismenesis00000000000000Zope 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.