[Checkins] SVN: zc.table/trunk/src/zc/table/ Fix table sorting code. Add lame test.

Gary Poster gary at zope.com
Wed Mar 22 11:41:42 EST 2006


Log message for revision 66126:
  Fix table sorting code.  Add lame test.
  

Changed:
  U   zc.table/trunk/src/zc/table/README.txt
  U   zc.table/trunk/src/zc/table/table.py

-=-
Modified: zc.table/trunk/src/zc/table/README.txt
===================================================================
--- zc.table/trunk/src/zc/table/README.txt	2006-03-22 15:43:22 UTC (rev 66125)
+++ zc.table/trunk/src/zc/table/README.txt	2006-03-22 16:41:42 UTC (rev 66126)
@@ -83,7 +83,7 @@
     name - (optional) the name of the column.  The title is used if a name is
            not specified.
 
-It includes a reasonable simple implementation of ISortableColumn but does
+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.
 
@@ -1106,4 +1106,94 @@
       </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>

Modified: zc.table/trunk/src/zc/table/table.py
===================================================================
--- zc.table/trunk/src/zc/table/table.py	2006-03-22 15:43:22 UTC (rev 66125)
+++ zc.table/trunk/src/zc/table/table.py	2006-03-22 16:41:42 UTC (rev 66126)
@@ -146,11 +146,34 @@
     formatter = None
 
     def __init__(self, items, sort_on):
-        self.items = items
+        self._items = items
         self.sort_on = sort_on # tuple of (column name, reversed) pairs
-        self.cache = []
-        self.iter = None
+        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
 
@@ -169,23 +192,20 @@
         if isinstance(key, slice):
             start = slice.start
             stop = slice.stop
-            stride = slice.stride
+            stride = slice.step
         else:
             start = stop = key
             stride = 1
 
+        items = self.items
         if not self.sort_on:
             try:
-                return self.items.__getitem__(key)
+                return items.__getitem__(key)
             except (AttributeError, TypeError):
-                res = []
                 if stride != 1:
                     raise NotImplemented
-
-                ix_offset = len(self.cache)
-                for ix, val in enumerate(self.items):
-                    ix += ix_offset
-                    self.cache.append(val)
+                res = []
+                for ix, val in enumerate(items):
                     if ix >= start:
                         res.append(val)
                     if ix >= stop:
@@ -199,7 +219,7 @@
                     raise IndexError, 'list index out of range'
 
         items = self.sorters[0](
-            self.items, self.formatter, start, stop, self.sorters[1:])
+            items, self.formatter, start, stop, self.sorters[1:])
 
         if isinstance(key, slice):
             return items[start:stop:stride]
@@ -208,31 +228,18 @@
 
     def __nonzero__(self):
         try:
-            iter(self).next()
+            iter(self.items).next()
         except StopIteration:
             return False
         return True
 
     def __iter__(self):
-        if self.iter is None:
-            if not self.sort_on:
-                self.iter = iter(self.items)
-            else:
-                self.iter = iter(self.sorters[0](
-                    self.items, self.formatter, 0, None, self.sorters[1:]))
-        ix = 0
-        cache = self.cache
-        while True:
-            try:
-                yield cache[ix]
-            except IndexError:
-                try:
-                    next = self.iter.next()
-                except StopIteration:
-                    break
-                cache.append(next)
-                yield next
-            ix += 1
+        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)
@@ -359,6 +366,9 @@
                             '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?
@@ -370,9 +380,8 @@
                     onMouseOut="javascript: this.className='zc-table-sortable'">
                 %(header)s</span> %(dirIndicator)s
         """
-        return template % locals()
+        return template % options
 
-
 class StandaloneSortFormatterMixin(AbstractSortFormatterMixin):
     "A version of the sort formatter mixin for standalone tables, not forms"
 



More information about the Checkins mailing list