[Checkins] SVN: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/ basic view
Godefroid Chapelle
gotcha at bubblenet.be
Mon Dec 13 05:26:45 EST 2010
Log message for revision 118849:
basic view
depending on z3c.table
Changed:
U Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt
U Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/configure.zcml
U Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/jobs.pt
U Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/service.py
U Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/configure.zcml
-=-
Modified: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt
===================================================================
--- Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt 2010-12-12 19:07:45 UTC (rev 118848)
+++ Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt 2010-12-13 10:26:45 UTC (rev 118849)
@@ -2,258 +2,59 @@
Task Service Browser Management UI
==================================
-Let's start a browser:
- >>> from zope.testbrowser.testing import Browser
- >>> browser = Browser()
- >>> browser.addHeader('Authorization','Basic mgr:mgrpw')
- >>> browser.handleErrors = False
+ >>> from z3c.taskqueue.service import TaskService
+ >>> service_instance = TaskService()
-Now we add a task service:
+ >>> def echo(input):
+ ... return input
- >>> browser.open('http://localhost/manage')
- >>> browser.getLink('Remote Task Service').click()
- >>> browser.getControl(name='new_value').value = 'tasks'
- >>> browser.getControl('Apply').click()
+ >>> from z3c.taskqueue import task
+ >>> echoTask = task.SimpleTask(echo)
-Now let's have a look at the job's table:
+ >>> echoTask(service_instance, 1, input={'foo': 'blah'})
+ {'foo': 'blah'}
- >>> browser.getLink('tasks').click()
+Let's now register the task as a utility:
-You can see the available tasks:
+ >>> import zope.component
+ >>> zope.component.provideUtility(echoTask, name='echo')
- >>> 'Available Tasks' in browser.contents
- True
+The echo task is now available in the service:
-By default there is an "echo" task:
+ >>> service_instance.getAvailableTasks()
+ {u'exception': <ExceptionTask>, u'echo': <SimpleTask <function echo ...>>}
- >>> '<div>echo</div>' in browser.contents
- True
+Since the service cannot instantaneously complete a task, incoming jobs are
+managed by a queue. First we request the echo task to be executed:
-Below you see a table of all the jobs. Initially we have no jobs, so let's add
-one via XML-RPC:
+ >>> jobid = service_instance.add(u'echo', {'foo': 'bar'})
+ >>> jobid
+ 1392637175
- >>> print http(r"""
- ... POST /tasks/ HTTP/1.0
- ... Authorization: Basic mgr:mgrpw
- ... Content-Type: text/xml
- ...
- ... <?xml version='1.0'?>
- ... <methodCall>
- ... <methodName>add</methodName>
- ... <params>
- ... <value><string>echo</string></value>
- ... <value><struct>
- ... <key><string>foo</string></key>
- ... <value><string>bar</string></value>
- ... </struct></value>
- ... </params>
- ... </methodCall>
- ... """)
- HTTP/1.0 200 Ok
- ...
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
-If we now refresh the screen, we will see the new job:
+Let's instantiate a table to display the jobs...
- >>> browser.reload()
- >>> print browser.contents
- <!DOCTYPE ...
- <tbody>
- <tr class="odd">
- <td class="">
- <input type="checkbox" name="jobs:list" value="1506179619">
- </td>
- <td class="tableId">
- 1506179619
- </td>
- <td class="tableTask">
- echo
- </td>
- <td class="tableStatus">
- <span class="status-queued">queued</span>
- </td>
- <td class="tableDetail">
- No input detail available
- </td>
- <td class="tableCreated">
- ...
- </td>
- <td class="tableStart">
- [not set]
- </td>
- <td class="tableEnd">
- [not set]
- </td>
- </tr>
- </tbody>
- ...
+ >>> from z3c.taskqueue_ui.browser import service
+ >>> jobsTable = service.JobsTable(service_instance.jobs.values(), request)
+ >>> jobsTable.update()
-It is possible to provide custom views for the details. Note the name of the
-view "echo_detail", it consists of the task name and "_detail". This allows us
-to use different detail views on the same job classes. if no such view is
-found a view with name 'detail' is searched.
+and render it.
- >>> from zope import interface
- >>> from zope.publisher.interfaces.browser import IBrowserView
- >>> class EchoDetailView(object):
- ... interface.implements(IBrowserView)
- ... def __init__(self, context, request):
- ... self.context = context
- ... self.request = request
- ... def __call__(self):
- ... return u'echo: foo=%s'% self.context.input['foo']
- >>> from lovely.remotetask.interfaces import IJob
- >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
- >>> from zope import component
- >>> component.provideAdapter(EchoDetailView,
- ... (IJob, IDefaultBrowserLayer),
- ... name='echo_detail')
- >>> browser.reload()
- >>> print browser.contents
- <!DOCTYPE
- ...
- <td class="tableDetail">
- echo: foo=bar
- ...
+ >>> table_html = jobsTable.render()
+ >>> assert '<th>Task name</th>' in table_html
+ >>> assert '<th>Status</th>' in table_html
+ >>> assert '<th>Created</th>' in table_html
+ >>> assert '<td>echo</td>' in table_html
+ >>> assert '<td>queued</td>' in table_html
-You can cancel scheduled jobs:
+We can access an overview of the jobs in the service.
- >>> browser.getControl('Cancel', index=0).click()
- >>> 'No jobs were selected.' in browser.contents
- True
+ >>> jobs_overview = service.JobsOverview(service_instance, request)
+ >>> view_html = jobs_overview()
- >>> browser.getControl(name='jobs:list').getControl(
- ... value='1506179619').click()
- >>> browser.getControl('Cancel', index=0).click()
- >>> 'Jobs were successfully cancelled.' in browser.contents
- True
+Let's check that it includes the table.
-It is also possible cancel all jobs::
-
- >>> browser.getControl('Cancel all', index=0).click()
- >>> 'All jobs cancelled' in browser.contents
- True
-
-You can also clean attic jobs:
-
- >>> browser.getControl('Remove all').click()
- >>> 'Cleaned 1 Jobs' in browser.contents
- True
-
-
-Thread Exception Reporting
---------------------------
-
-If a job raises an exception the task service repeats the job 3 times. On
-every exception a traceback is written to the log.
-
-We modify the python logger to get the log output.
-
- >>> import logging
- >>> logger = logging.getLogger("lovely.remotetask")
- >>> logger.setLevel(logging.ERROR)
- >>> import StringIO
- >>> io = StringIO.StringIO()
- >>> ch = logging.StreamHandler(io)
- >>> ch.setLevel(logging.DEBUG)
- >>> logger.addHandler(ch)
-
- >>> from time import sleep
- >>> from zope import component
- >>> from lovely.remotetask.interfaces import ITaskService
- >>> service = getRootFolder()['tasks']
-
-We add a job for a task which raises a ZeroDivisionError every time it is
-called.
-
- >>> jobid = service.add(u'exception')
- >>> service.getStatus(jobid)
- 'queued'
- >>> import transaction
- >>> transaction.commit()
- >>> service.startProcessing()
- >>> transaction.commit()
-
- >>> import time
- >>> time.sleep(1.5)
-
-
-Note that the processing thread is daemonic, that way it won't keep the process
-alive unnecessarily.
-
- >>> import threading
- >>> for thread in threading.enumerate():
- ... if thread.getName().startswith('remotetasks.'):
- ... print thread.isDaemon()
- True
-
- >>> service.stopProcessing()
- >>> transaction.commit()
-
-
-We got log entries with the tracebacks of the division error.
-
- >>> logvalue = io.getvalue()
- >>> print logvalue
- Caught a generic exception, preventing thread from crashing
- integer division or modulo by zero
- Traceback (most recent call last):
- ...
- ZeroDivisionError: integer division or modulo by zero
- <BLANKLINE>
-
-We had 3 retries, but every error is reported twice, once by the processor and
-once from by the task service.
-
- >>> logvalue.count('ZeroDivisionError')
- 6
-
-The job status is set to 'error'.
-
- >>> service.getStatus(jobid)
- 'error'
-
-We do the same again to see if the same thing happens again. This test is
-necessary to see if the internal runCount in the task service is reset.
-
- >>> io.seek(0)
- >>> jobid = service.add(u'exception')
- >>> service.getStatus(jobid)
- 'queued'
- >>> import transaction
- >>> transaction.commit()
- >>> service.startProcessing()
- >>> transaction.commit()
- >>> sleep(1.5)
- >>> service.stopProcessing()
- >>> transaction.commit()
-
-We got log entries with the tracebacks of the division error.
-
- >>> logvalue = io.getvalue()
- >>> print logvalue
- Caught a generic exception, preventing thread from crashing
- integer division or modulo by zero
- Traceback (most recent call last):
- ...
- ZeroDivisionError: integer division or modulo by zero
- <BLANKLINE>
-
-We had 3 retries, but every error is reported twice, once by the processor and
-once from by the task service.
-
- >>> logvalue.count('ZeroDivisionError')
- 6
-
-The job status is set to 'error'.
-
- >>> service.getStatus(jobid)
- 'error'
-
-
-Clenaup
--------
-
-Allow the threads to exit:
-
- >>> sleep(0.2)
+ >>> assert table_html in view_html
Modified: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/configure.zcml
===================================================================
--- Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/configure.zcml 2010-12-12 19:07:45 UTC (rev 118848)
+++ Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/configure.zcml 2010-12-13 10:26:45 UTC (rev 118849)
@@ -9,13 +9,4 @@
permission="zope.ManageContent"
/>
- <!-- traverser for the site -->
- <view
- for="z3c.taskqueue.interfaces.ITaskService"
- type="zope.publisher.interfaces.browser.IBrowserRequest"
- provides="zope.publisher.interfaces.browser.IBrowserPublisher"
- factory=".service.ServiceJobTraverser"
- permission="zope.Public"
- />
-
</configure>
Modified: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/jobs.pt
===================================================================
--- Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/jobs.pt 2010-12-12 19:07:45 UTC (rev 118848)
+++ Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/jobs.pt 2010-12-13 10:26:45 UTC (rev 118849)
@@ -1,182 +1,5 @@
-<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>
-<script type="text/javascript">
-
- function confirmCancelAll(form) {
- if (cancelAll) {
- cancelAll=false;
- return confirm("Sure?")
- }
- else {
- return true
- };
- }
-</script>
-</head>
+<html>
<body>
-<div metal:fill-slot="body">
-
-<form action="" method="post"
- onsubmit="return confirmCancelAll(this)"
- 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"
- value="Start Processing"
- tal:condition="not:context/isProcessing"
- i18n:attributes="value" />
- <input type="submit" class="button" name="STOPPROCESSING"
- value="Stop Processing"
- tal:condition="context/isProcessing"
- i18n:attributes="value" />
- </div>
- </div>
-
- <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>
- </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>
- </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="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" />
- <input type="submit" class="button" name="CANCEL_ALL" value="Cancel all"
- i18n:attributes="value" onclick="cancelAll=true;"/>
- </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>
+ <table tal:define="table view/table" tal:replace="structure table/render" />
</body>
</html>
Modified: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/service.py
===================================================================
--- Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/service.py 2010-12-12 19:07:45 UTC (rev 118848)
+++ Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/service.py 2010-12-13 10:26:45 UTC (rev 118849)
@@ -17,189 +17,70 @@
"""
__docformat__ = 'restructuredtext'
-import transaction
-import zope.interface
-import zope.component
from zope.publisher.browser import BrowserPage
-from zope.publisher.interfaces import NotFound
-from zope.security.proxy import removeSecurityProxy
-from zope.traversing.browser.absoluteurl import absoluteURL
-
from zope.app.pagetemplate import ViewPageTemplateFile
-from zope.app.container.contained import contained
from z3c.table import column, table
-from z3c.table.interfaces import IColumn as ISortableColumn
-from z3c.taskqueue import interfaces
-SORTED_ON_KEY = 'lovely.remotetask.service.table.sorted-on'
-
-class CheckboxColumn(column.Column):
- """Provide a column to select applications."""
-
- def renderCell(self, item, formatter):
- widget = (u'<input type="checkbox" name="jobs:list" value="%i">')
- return widget % item.id
-
-
class TaskNameColumn(column.Column):
- """Provide a column for the task name and provide a link to an edit page
- is one is available."""
- def renderCell(self, item, formatter):
- view = zope.component.queryMultiAdapter((item, formatter.request),
- name='editjob')
- if view:
- url = absoluteURL(formatter.context, formatter.request)
- return '<a href="%s/%s/editjob">%s</a>' % (
- url, item.id, item.task)
- else:
- return item.task
+ header = u"Task name"
+ def renderCell(self, item):
+ return item.task
-class JobDetailColumn(column.Column):
- """Provide a column of taks input detail view."""
- def renderCell(self, item, formatter):
- view = zope.component.queryMultiAdapter((item, formatter.request),
- name='%s_detail' % item.task)
- if view is None:
- view = zope.component.getMultiAdapter((item, formatter.request),
- name='detail')
- return view()
-
-
class StatusColumn(column.Column):
- 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)
+ header = u"Status"
- def getSortKey(self, item, formatter):
- return self.getter(item, formatter)
+ def renderCell(self, item):
+ return item.status
class DatetimeColumn(column.Column):
- zope.interface.implements(ISortableColumn)
- def renderCell(self, item, formatter):
- date = self.getter(item, formatter)
- dformat = formatter.request.locale.dates.getFormatter(
+ header = u"Created"
+
+ def renderCell(self, item):
+ date = item.created
+ dformat = self.request.locale.dates.getFormatter(
'dateTime', 'medium')
return date and dformat.format(date) or '[not set]'
- def getSortKey(self, item, formatter):
- return self.getter(item, formatter)
+class JobsTable(table.SequenceTable):
-class Jobs(table.Table):
- pass
+ def setUpColumns(self):
+ firstColumn = TaskNameColumn(self.context, self.request, self)
+ firstColumn.__name__ = u'taskname'
+ firstColumn.weight = 1
+ secondColumn = StatusColumn(self.context, self.request, self)
+ secondColumn.__name__ = u'status'
+ secondColumn.weight = 2
+ thirdColumn = DatetimeColumn(self.context, self.request, self)
+ thirdColumn.__name__ = u'datetime'
+ thirdColumn.weight = 3
+ return [firstColumn, secondColumn, thirdColumn]
class JobsOverview(BrowserPage):
template = ViewPageTemplateFile('jobs.pt')
- status = None
- columns = (
-# CheckboxColumn(u'Sel'),
-# column.Column(u'Id', lambda x, f: str(x.id), name='id'),
-# TaskNameColumn(u'Task', name='task'),
-# 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',
-# lambda x, f: x.started, name='start'),
-# DatetimeColumn(u'End',
-# lambda x, f: x.completed, name='end'),
- )
-
def table(self):
- return Jobs()
+ if not hasattr(self, '_table'):
+ self._table = JobsTable(self.jobs(), self.request)
+ return self._table
def jobs(self):
- if hasattr(self, '_jobs'):
- return self._jobs
-
- jobs = list(self.context.jobs.values())
- jobs.reverse()
- self._jobs = jobs
+ if not hasattr(self, '_jobs'):
+ jobs = list(self.context.jobs.values())
+ jobs.reverse()
+ self._jobs = jobs
return self._jobs
- def numberOfItems(self):
- jobs = list(self.context.jobs.values())
- return len(jobs)
-
- def getAvailableTasks(self):
- return sorted(self.context.getAvailableTasks().keys())
-
- def update(self):
- if 'STARTPROCESSING' in self.request:
- self.context.startProcessing()
- elif 'STOPPROCESSING' in self.request:
- self.context.stopProcessing()
- elif 'CANCEL' in self.request:
- if 'jobs' in self.request:
- for id in self.request['jobs']:
- self.context.cancel(int(id))
- self.status = 'Jobs were successfully cancelled.'
- else:
- self.status = u'No jobs were selected.'
- 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(status=[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(status=[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(status=[interfaces.COMPLETED])
- cleaned = jobs - len(list(self.context.jobs.keys()))
- self.status = u'Cleaned %r Jobs' % cleaned
- elif 'CANCEL_ALL' in self.request:
- jobs = list(self.context.jobs.keys())
- for index, job in enumerate(jobs):
- if index % 100 == 99:
- transaction.commit()
- self.context.cancel(job)
- self.status = u'All jobs cancelled'
-
def __call__(self):
- self.update()
+ self.table().update()
return self.template()
-
-from zope.publisher.interfaces import IPublishTraverse
-
-
-class ServiceJobTraverser(object):
- zope.interface.implements(IPublishTraverse)
-
- def __init__(self, context, request):
- self.context = context
- self.request = request
-
- def publishTraverse(self, request, name):
- try:
- job = removeSecurityProxy(self.context.jobs[int(name)])
- # we provide a location proxy
- return contained(job, self.context, name)
- except (KeyError, ValueError):
- pass
- view = zope.component.queryMultiAdapter((self.context, request),
- name=name)
- if view is not None:
- return view
- raise NotFound(self.context, name, request)
Modified: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/configure.zcml
===================================================================
--- Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/configure.zcml 2010-12-12 19:07:45 UTC (rev 118848)
+++ Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/configure.zcml 2010-12-13 10:26:45 UTC (rev 118849)
@@ -2,6 +2,7 @@
xmlns="http://namespaces.zope.org/browser"
xmlns:zope="http://namespaces.zope.org/zope">
+ <include package="z3c.table" />
<include package="z3c.taskqueue_ui.browser" />
</configure>
More information about the checkins
mailing list