[Checkins] SVN: lovely.remotetask/trunk/src/lovely/remotetask/ Improve remote task jobs overview, implemented batching, and sorting.

Roger Ineichen roger at projekt01.ch
Sat Nov 18 11:26:22 EST 2006


Log message for revision 71181:
  Improve remote task jobs overview, implemented batching, and sorting.
  Update tests
  
  TODO:
  Make batching ans sorting working together.

Changed:
  U   lovely.remotetask/trunk/src/lovely/remotetask/README.txt
  U   lovely.remotetask/trunk/src/lovely/remotetask/browser/README.txt
  U   lovely.remotetask/trunk/src/lovely/remotetask/browser/configure.zcml
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/icons/
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/icons/ascending.gif
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/icons/decending.gif
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/job.py
  U   lovely.remotetask/trunk/src/lovely/remotetask/browser/jobs.pt
  U   lovely.remotetask/trunk/src/lovely/remotetask/browser/service.py
  A   lovely.remotetask/trunk/src/lovely/remotetask/browser/table_header.pt
  U   lovely.remotetask/trunk/src/lovely/remotetask/interfaces.py
  U   lovely.remotetask/trunk/src/lovely/remotetask/service.py

-=-
Modified: lovely.remotetask/trunk/src/lovely/remotetask/README.txt
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/README.txt	2006-11-18 14:21:40 UTC (rev 71180)
+++ lovely.remotetask/trunk/src/lovely/remotetask/README.txt	2006-11-18 16:26:21 UTC (rev 71181)
@@ -129,6 +129,3 @@
 
   >>> sorted([job.status for job in service.jobs.values()])
   ['queued']
-
-
-

Modified: lovely.remotetask/trunk/src/lovely/remotetask/browser/README.txt
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/README.txt	2006-11-18 14:21:40 UTC (rev 71180)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/README.txt	2006-11-18 16:26:21 UTC (rev 71181)
@@ -27,7 +27,7 @@
 
 By default there is an "echo" task:
 
-  >>> '<li>echo</li>' in browser.contents
+  >>> '<div>echo</div>' in browser.contents
   True
 
 Below you see a table of all the jobs. Initially we have no jobs, so let's add
@@ -57,31 +57,36 @@
 
   >>> browser.reload()
   >>> print browser.contents
-  <!DOCTYPE ...
+  <!DOCTYPE
   ...
+  <tbody>
   <tr class="odd">
-    <td>
+    <td class="">
       <input type="checkbox" name="jobs:list" value="1">
     </td>
-    <td>
+    <td class="tableId">
       1
     </td>
-    <td>
+    <td class="tableTask">
       echo
     </td>
-    <td>
-      queued
+    <td class="tableStatus">
+      <span class="status-queued">queued</span>
     </td>
-    <td>
+    <td class="tableDetail">
+      No input detail available
+    </td>
+    <td class="tableCreated">
       ...
     </td>
-    <td>
+    <td class="tableStart">
       [not set]
     </td>
-    <td>
+    <td class="tableEnd">
       [not set]
     </td>
   </tr>
+  </tbody>
   ...
 
 You can cancel scheduled jobs:
@@ -97,6 +102,6 @@
 
 You can also clean attic jobs:
 
-  >>> browser.getControl('Clean').click()
+  >>> browser.getControl('Remove all').click()
   >>> 'Cleaned 1 Jobs' in  browser.contents
   True

Modified: lovely.remotetask/trunk/src/lovely/remotetask/browser/configure.zcml
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/configure.zcml	2006-11-18 14:21:40 UTC (rev 71180)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/configure.zcml	2006-11-18 16:26:21 UTC (rev 71181)
@@ -1,6 +1,11 @@
 <configure
     xmlns="http://namespaces.zope.org/browser">
 
+  <resourceDirectory
+      name="lovely-remotetask-icons"
+      directory="icons"
+      />
+
   <addMenuItem
       class="..service.TaskService"
       title="Remote Task Service"
@@ -16,4 +21,11 @@
       menu="zmi_views" title="Jobs"
       />
 
+  <page
+      for="..interfaces.IJob"
+      name="detail"
+      permission="zope.ManageContent"
+      class=".job.JobDetail"
+      />
+
 </configure>

Added: lovely.remotetask/trunk/src/lovely/remotetask/browser/icons/ascending.gif
===================================================================
(Binary files differ)


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/browser/icons/ascending.gif
___________________________________________________________________
Name: svn:mime-type
   + image/gif

Added: lovely.remotetask/trunk/src/lovely/remotetask/browser/icons/decending.gif
===================================================================
(Binary files differ)


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/browser/icons/decending.gif
___________________________________________________________________
Name: svn:mime-type
   + image/gif

Added: lovely.remotetask/trunk/src/lovely/remotetask/browser/job.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/job.py	2006-11-18 14:21:40 UTC (rev 71180)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/job.py	2006-11-18 16:26:21 UTC (rev 71181)
@@ -0,0 +1,27 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems 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.
+#
+##############################################################################
+"""Task detail view
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.publisher.browser import BrowserPage
+
+
+class JobDetail(BrowserPage):
+    """A simple task input detail view."""
+
+    def __call__(self):
+        return u'No input detail available'


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/browser/job.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: lovely.remotetask/trunk/src/lovely/remotetask/browser/jobs.pt
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/jobs.pt	2006-11-18 14:21:40 UTC (rev 71180)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/jobs.pt	2006-11-18 16:26:21 UTC (rev 71181)
@@ -1,11 +1,87 @@
 <html metal:use-macro="context/@@standard_macros/view"
       i18n:domain="lovely.remotetask">
+<head metal:fill-slot="style_slot">
+<style type="text/css" media="all">
+
+.batch div {
+    font-size: 11px;
+    padding-top: 5px;
+    padding-right: 5px;
+    vertical-align: middle;
+    float: left;
+}
+.clear {
+	height: 1px;
+	clear: both;
+}
+table.list th {
+  font-size: 11px;
+  text-align: left;
+	font-weight: bold;
+	background-color: silver;
+}
+.tableId {
+	width: 24px;
+  font-size: 11px;
+}
+.tableTask {
+  font-size: 11px;
+}
+.tableStatus {
+  font-size: 11px;
+}
+.tableDetail {
+  font-size: 11px;
+}
+.tableCreated {
+	width: 100px;
+  font-size: 11px;
+}
+.tableStart {
+	width: 100px;
+  font-size: 11px;
+}
+.tableEnd {
+	width: 100px;
+  font-size: 11px;
+}
+.status-queued {
+  color: gray;
+}
+.status-processing {
+  color: orange;
+}
+.status-cancelled {
+  color: silver;
+}
+.status-error {
+  color: red;
+}
+.status-completed {
+  color: green;
+}
+input {
+  font-size: 10px;
+	padding: 0px;
+}
+#taskInfo {
+  font-size: 10px;
+}
+</style>
+</head>
 <body>
 <div metal:fill-slot="body">
 
 <form action="" method="post"
-      tal:attributes="action request/URL">
+      tal:attributes="action request/URL" tal:define="jobs view/jobs">
 
+  <div class="message"
+       tal:condition="view/status"
+       tal:content="view/status"
+       i18n:translate="">
+    Something happened.
+  </div>
+
   <div class="row">
     <div class="controls">
       <input type="submit" class="button" name="STARTPROCESSING"
@@ -19,37 +95,71 @@
     </div>
   </div>
 
-  <p i18n:translate="">
-    Available Tasks:
-  </p>
-  <ul>
-    <li tal:repeat="task view/getAvailableTasks"
-        tal:content="task" />
-  </ul>
-
-  <div class="message"
-       tal:condition="view/status"
-       tal:content="view/status"
-       i18n:translate="">
-    Something happened.
-  </div>
-
-  <div class="row">
-    <div class="controls">
-      <input type="submit" class="button" name="CLEAN" value="Clean"
-             i18n:attributes="value" />
+  <div class="batch" tal:condition="jobs/startNumber">
+    <div class="prev_batch" tal:define="prev jobs/prevBatch">
+      <a href=""
+          tal:condition="prev"
+          tal:attributes="href 
+              string:./@@jobs.html?start=${prev/start}&size=${prev/size}"
+          i18n:translate="">
+        Previous
+        (<d tal:replace="prev/startNumber" i18n:name="start_number" /> to
+         <d tal:replace="prev/endNumber" i18n:name="end_number" />) 
+      </a>&nbsp;
     </div>
+    <div class="curr_batch" i18n:translate="">
+        <d tal:replace="jobs/startNumber" i18n:name="start_number"/> to
+        <d tal:replace="jobs/endNumber" i18n:name="end_number"/>
+        of <d tal:replace="jobs/total" i18n:name="batch_total_number"/> found
+        (<d tal:replace="view/numberOfItems" i18n:name="image_number"/> total)
+    </div>
+    <div class="next_batch" tal:define="next jobs/nextBatch">
+      <a href=""
+          tal:condition="next"
+          tal:attributes="href 
+              string:./@@jobs.html?start=${next/start}&size=${next/size}"
+          i18n:translate="">
+        Next
+        (<d tal:replace="next/startNumber" i18n:name="start_number" /> to
+         <d tal:replace="next/endNumber" i18n:name="end_number" />)
+      </a>&nbsp;
+    </div>
+    <div class="clear"></div>
   </div>
-  
+	<input type="text" name="size" value="" 
+          tal:attributes="value request/size|nothing" />
+  <input type="submit" class="button" name="SUBMIT_BATCH_SIZE" value="set batch size"
+         i18n:attributes="value" />
+  <div class="clear" />
   <tal:block tal:replace="structure view/table" />
 
+
   <div class="row">
     <div class="controls">
-      <input type="submit" class="button" name="CANCEL" value="Cancel"
+      <input type="submit" class="button" name="CLEAN_COMPLETED" value="Remove completed"
              i18n:attributes="value" />
+      <input type="submit" class="button" name="CLEAN_CANCELLED" value="Remove cancelled"
+             i18n:attributes="value" />
+      <input type="submit" class="button" name="CLEAN_ERROR" value="Remove error"
+             i18n:attributes="value" />
+      <input type="submit" class="button" name="CLEAN_ALL" value="Remove all"
+             i18n:attributes="value" />
+
+      <input type="submit" class="button" name="CANCEL" value="Cancel selected"
+             i18n:attributes="value" />
     </div>
   </div>
 
+  <div id="taskInfo">
+  <p i18n:translate="">
+    Available Tasks:
+  </p>
+  <div id="availableTasks">
+    <div tal:repeat="task view/getAvailableTasks"
+        tal:content="task" />
+  </div>
+	</div>
+
 </form>
 
 </div>

Modified: lovely.remotetask/trunk/src/lovely/remotetask/browser/service.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/service.py	2006-11-18 14:21:40 UTC (rev 71180)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/service.py	2006-11-18 16:26:21 UTC (rev 71181)
@@ -17,12 +17,125 @@
 """
 __docformat__ = 'restructuredtext'
 
+from xml.sax.saxutils import quoteattr
+
 import zope.interface
+import zope.component
+from zope.publisher.browser import BrowserPage
 from zope.app.pagetemplate import ViewPageTemplateFile
-from zope.publisher.browser import BrowserPage
-from zc.table import column, table
+from zope.app.session.interfaces import ISession
+from zc.table import column, table, batching
 from zc.table.interfaces import ISortableColumn
+from lovely.remotetask import interfaces
 
+SORTED_ON_KEY = 'lovely.remotetask.service.table.sorted-on'
+
+
+from zope.interface.common.mapping import IItemMapping
+
+class IBatch(IItemMapping):
+    """A Batch represents a sub-list of the full enumeration.
+
+    The Batch constructor takes a list (or any list-like object) of elements,
+    a starting index and the size of the batch. From this information all
+    other values are calculated.
+    """
+
+    def __len__():
+        """Return the length of the batch. This might be different than the
+        passed in batch size, since we could be at the end of the list and
+        there are not enough elements left to fill the batch completely."""
+
+    def __iter__(): 
+        """Creates an iterator for the contents of the batch (not the entire
+        list)."""
+
+    def __contains__(key):
+        """Checks whether the key (in our case an index) exists."""
+
+    def nextBatch(self):
+        """Return the next batch. If there is no next batch, return None."""
+    
+    def prevBatch(self):
+        """Return the previous batch. If there is no previous batch, return
+        None."""
+
+    def first(self):
+        """Return the first element of the batch."""
+
+    def last(self):
+        """Return the last element of the batch."""
+
+    def total(self):
+        """Return the length of the list (not the batch)."""
+
+    def startNumber(self):
+        """Give the start **number** of the batch, which is 1 more than the
+        start index passed in."""
+
+    def endNumber(self):
+        """Give the end **number** of the batch, which is 1 more than the
+        final index."""
+
+
+class Batch(object):
+    zope.interface.implements(IBatch)
+
+    def __init__(self, list, start=0, size=20):
+        self.list = list
+        self.start = start
+        if len(list) == 0:
+            self.start = -1
+        elif start >= len(list):
+            raise IndexError('start index key out of range')
+        self.size = size
+        self.trueSize = size
+        if start+size >= len(list):
+            self.trueSize = len(list)-start
+        self.end = start+self.trueSize-1
+
+    def __len__(self):
+        return self.trueSize
+
+    def __getitem__(self, key):
+        if key >= self.trueSize:
+            raise IndexError('batch index out of range')
+        return self.list[self.start+key]
+
+    def __iter__(self): 
+        return iter(self.list[self.start:self.end+1])
+
+    def __contains__(self, item):
+        return item in self.__iter__()
+
+    def nextBatch(self):
+        start = self.start + self.size
+        if start >= len(self.list):
+            return None
+        return Batch(self.list, start, self.size)
+    
+    def prevBatch(self):
+        start = self.start - self.size
+        if start < 0:
+            return None
+        return Batch(self.list, start, self.size)
+
+    def first(self):
+        return self.list[self.start]
+
+    def last(self):
+        return self.list[self.end]
+
+    def total(self):
+        return len(self.list)
+
+    def startNumber(self):
+        return self.start+1
+
+    def endNumber(self):
+        return self.end+1
+
+
 class CheckboxColumn(column.Column):
     """Provide a column to select applications."""
 
@@ -30,6 +143,30 @@
         widget = (u'<input type="checkbox" name="jobs:list" value="%i">')
         return widget %item.id
 
+
+class JobDetailColumn(column.Column):
+    """Provide a column of taks input detail view."""
+
+    def renderCell(self, item, formatter):
+        if not item.input:
+            return u'No input data given.'
+        view = zope.component.getMultiAdapter((item, formatter.request), 
+            name='detail')
+        return view()
+
+
+class StatusColumn(column.GetterColumn):
+    zope.interface.implements(ISortableColumn)
+
+    def renderCell(self, item, formatter):
+        status = self.getter(item, formatter)
+        cssClass = 'status-' + status
+        return '<span class="%s">%s</span>' % (cssClass, status)
+
+    def getSortKey(self, item, formatter):
+        return self.getter(item, formatter)
+
+
 class DatetimeColumn(column.GetterColumn):
     zope.interface.implements(ISortableColumn)
 
@@ -39,6 +176,87 @@
             'dateTime', 'short')
         return date and dformat.format(date) or '[not set]'
 
+    def getSortKey(self, item, formatter):
+        return self.getter(item, formatter)
+
+class ListFormatter(table.SortingFormatterMixin, 
+    table.AlternatingRowFormatter):
+    """Provides a width for each column."""
+
+    sortedHeaderTemplate = ViewPageTemplateFile('table_header.pt')
+    widths = None
+    columnCSS = None
+
+    def __init__(self, *args, **kw):
+        # Figure out sorting situation
+        kw['ignore_request'] = True
+
+        request = args[1]
+        prefix = kw.get('prefix')
+        session = ISession(request)[SORTED_ON_KEY]
+        if 'sort-on' in request:
+            name = request['sort-on']
+            if prefix and name.startswith(prefix):
+                name = name[len(prefix):]
+                oldName, oldReverse = session.get(prefix, (None, None))
+                if oldName == name:
+                    session[prefix] = (name, not oldReverse)
+                else:
+                    session[prefix] = (name, False)
+        # Now get the sort-on data from the session
+        if prefix in session:
+            kw['sort_on'] = [session[prefix]]
+
+        super(ListFormatter, self).__init__(*args, **kw)
+        self.columnCSS = {}
+
+        self.sortOn = (None, None)
+        if 'sort_on' in kw:
+            for name, reverse in kw['sort_on']:
+                self.columnCSS[name] = 'sorted-on'
+            self.sortOn = kw['sort_on'][0]
+
+    # sortable table support via session
+    def getHeader(self, column):
+        contents = column.renderHeader(self)
+        if (ISortableColumn.providedBy(column)):
+            contents = self._wrapInSortUI(contents, column)
+        return contents
+
+    def _wrapInSortUI(self, header, column):
+        name = column.name
+        if self.prefix:
+            name = self.prefix + name
+        isAscending = self.sortOn[0] == column.name and not self.sortOn[1]
+        isDecending = self.sortOn[0] == column.name and self.sortOn[1]
+        return self.sortedHeaderTemplate(
+            header=header, name=name,
+            isAscending=isAscending, isDecending=isDecending)
+
+    def renderHeader(self, column):
+        width = ''
+        if self.widths:
+            idx = list(self.visible_columns).index(column)
+            width = ' width="%i"' %self.widths[idx]
+        klass = self.cssClasses.get('tr', '')
+        if column.name in self.columnCSS:
+            klass += klass and ' ' or '' + self.columnCSS[column.name]
+        return '      <th%s class=%s>\n        %s\n      </th>\n' % (
+            width, quoteattr(klass), self.getHeader(column))
+
+
+    def renderCell(self, item, column):
+        klass = self.cssClasses.get('tr', '')
+        if column.name in self.columnCSS:
+            klass += klass and ' ' or '' + self.columnCSS[column.name]
+        return '    <td class=%s>\n      %s\n    </td>\n' % (
+            quoteattr(klass), self.getCell(item, column))
+
+    def renderExtra(self):
+        """Avoid use of resourcelibrary in original class."""
+        return ''
+
+
 class JobsOverview(BrowserPage):
 
     template = ViewPageTemplateFile('jobs.pt')
@@ -48,21 +266,50 @@
         CheckboxColumn(u'Sel'),
         column.GetterColumn(u'Id', lambda x, f: str(x.id), name='id'),
         column.GetterColumn(u'Task', lambda x, f: x.task, name='task'),
-        column.GetterColumn(u'Status', lambda x, f: x.status, name='status'),
-        DatetimeColumn(u'Creation Date',
+        StatusColumn(u'Status', lambda x, f: x.status, name='status'),
+        JobDetailColumn(u'Detail', name='detail'),
+        DatetimeColumn(u'Creation',
                        lambda x, f: x.created, name='created'),
-        DatetimeColumn(u'Start Date',
+        DatetimeColumn(u'Start',
                        lambda x, f: x.started, name='start'),
-        DatetimeColumn(u'Completion Date',
-                       lambda x, f: x.completed, name='completed'),
+        DatetimeColumn(u'End',
+                       lambda x, f: x.completed, name='end'),
         )
 
     def table(self):
-        formatter = table.StandaloneFullFormatter(
-            self.context, self.request, self.context.jobs.values(),
-            prefix='jobs.', columns=self.columns)
+        formatter = ListFormatter(
+            self.context, self.request, self.jobs(),
+            prefix='zc.table', columns=self.columns)
+        formatter.widths=[25, 50, 150, 75, 250, 100, 100, 100]
+        formatter.cssClasses['table'] = 'list'
+        formatter.columnCSS['id'] = 'tableId'
+        formatter.columnCSS['task'] = 'tableTask'
+        formatter.columnCSS['status'] = 'tableStatus'
+        formatter.columnCSS['detail'] = 'tableDetail'
+        formatter.columnCSS['created'] = 'tableCreated'
+        formatter.columnCSS['start'] = 'tableStart'
+        formatter.columnCSS['end'] = 'tableEnd'
         return formatter()
 
+    def jobs(self):
+        if hasattr(self, '_jobs'):
+            return self._jobs
+
+        start = int(self.request.get('start', 0))
+        sval = self.request.get('size', 10)
+        if sval:
+            size = int(sval)
+        else:
+            size = 10
+
+        jobs = list(self.context.jobs.values())
+        self._jobs = Batch(jobs, start, size)
+        return self._jobs
+
+    def numberOfItems(self):
+        jobs = list(self.context.jobs.values())
+        return len(jobs)
+
     def getAvailableTasks(self):
         return sorted(self.context.getAvailableTasks().keys())
 
@@ -78,11 +325,26 @@
                 self.status = 'Jobs were successfully cancelled.'
             else:
                 self.status = u'No jobs were selected.'
-        elif 'CLEAN' in self.request:
+        elif 'CLEAN_ALL' in self.request:
             jobs = len(list(self.context.jobs.keys()))
             self.context.clean()
             cleaned = jobs - len(list(self.context.jobs.keys()))
             self.status = u'Cleaned %r Jobs' % cleaned
+        elif 'CLEAN_ERROR' in self.request:
+            jobs = len(list(self.context.jobs.keys()))
+            self.context.clean(stati=[interfaces.ERROR])
+            cleaned = jobs - len(list(self.context.jobs.keys()))
+            self.status = u'Cleaned %r Jobs' % cleaned
+        elif 'CLEAN_CANCELLED' in self.request:
+            jobs = len(list(self.context.jobs.keys()))
+            self.context.clean(stati=[interfaces.CANCELLED])
+            cleaned = jobs - len(list(self.context.jobs.keys()))
+            self.status = u'Cleaned %r Jobs' % cleaned
+        elif 'CLEAN_COMPLETED' in self.request:
+            jobs = len(list(self.context.jobs.keys()))
+            self.context.clean(stati=[interfaces.COMPLETED])
+            cleaned = jobs - len(list(self.context.jobs.keys()))
+            self.status = u'Cleaned %r Jobs' % cleaned
 
     def __call__(self):
         self.update()

Added: lovely.remotetask/trunk/src/lovely/remotetask/browser/table_header.pt
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/browser/table_header.pt	2006-11-18 14:21:40 UTC (rev 71180)
+++ lovely.remotetask/trunk/src/lovely/remotetask/browser/table_header.pt	2006-11-18 16:26:21 UTC (rev 71181)
@@ -0,0 +1,11 @@
+<html tal:omit-tag="">
+  <a href=""
+     tal:attributes="href string:${request/URL}?sort-on=${options/name}"
+     tal:content="options/header" />
+  <img src="" width="7" height="4" style="vertical-align: middle"
+       tal:condition="options/isAscending"
+       tal:attributes="src context/++resource++lovely-remotetask-icons/ascending.gif" />
+  <img src="" width="7" height="4" style="vertical-align: middle"
+       tal:condition="options/isDecending"
+       tal:attributes="src context/++resource++lovely-remotetask-icons/decending.gif" />
+</html>


Property changes on: lovely.remotetask/trunk/src/lovely/remotetask/browser/table_header.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: lovely.remotetask/trunk/src/lovely/remotetask/interfaces.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/interfaces.py	2006-11-18 14:21:40 UTC (rev 71180)
+++ lovely.remotetask/trunk/src/lovely/remotetask/interfaces.py	2006-11-18 16:26:21 UTC (rev 71181)
@@ -45,9 +45,8 @@
         arguments for the task.
         """
 
-    def clean():
-        """removes all jobs which are completed or canceled or have
-        errors"""
+    def clean(stati=[CANCELLED, ERROR, COMPLETED]):
+        """removes all jobs which are completed or canceled or have errors."""
         
     def cancel(jobid):
         """Cancel a particular job."""

Modified: lovely.remotetask/trunk/src/lovely/remotetask/service.py
===================================================================
--- lovely.remotetask/trunk/src/lovely/remotetask/service.py	2006-11-18 14:21:40 UTC (rev 71180)
+++ lovely.remotetask/trunk/src/lovely/remotetask/service.py	2006-11-18 16:26:21 UTC (rev 71181)
@@ -67,13 +67,17 @@
         newjob.status = interfaces.QUEUED
         return jobid
 
-    def clean(self):
+    def clean(self, stati=[interfaces.CANCELLED, interfaces.ERROR, 
+        interfaces.COMPLETED]):
         """See interfaces.ITaskService"""
+        allowed = [interfaces.CANCELLED, interfaces.ERROR, 
+            interfaces.COMPLETED]
         for key in list(self.jobs.keys()):
             job = self.jobs[key]
-            if job.status in [interfaces.CANCELLED,
-                              interfaces.ERROR,
-                              interfaces.COMPLETED]:
+            if job.status in stati:
+                if job.status not in allowed:
+                    raise ValueError('Not allowed status for removing. %s' % \
+                        job.status)
                 del self.jobs[key]
 
     def cancel(self, jobid):



More information about the Checkins mailing list