[Checkins] SVN: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/ start with service view
Godefroid Chapelle
gotcha at bubblenet.be
Mon Oct 11 08:07:42 EDT 2010
Log message for revision 117448:
start with service view
Changed:
D Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt
D Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/cronjob.pt
D Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/icons/
D Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/job.py
D Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/table_header.pt
-=-
Deleted: 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-10-11 12:05:58 UTC (rev 117447)
+++ Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/README.txt 2010-10-11 12:07:42 UTC (rev 117448)
@@ -1,259 +0,0 @@
-==================================
-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
-
-Now we add a task service:
-
- >>> browser.open('http://localhost/manage')
- >>> browser.getLink('Remote Task Service').click()
- >>> browser.getControl(name='new_value').value = 'tasks'
- >>> browser.getControl('Apply').click()
-
-Now let's have a look at the job's table:
-
- >>> browser.getLink('tasks').click()
-
-You can see the available tasks:
-
- >>> 'Available Tasks' in browser.contents
- True
-
-By default there is an "echo" task:
-
- >>> '<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
-one via XML-RPC:
-
- >>> 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
- ...
-
-If we now refresh the screen, we will see the new job:
-
- >>> 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>
- ...
-
-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.
-
- >>> 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
- ...
-
-You can cancel scheduled jobs:
-
- >>> browser.getControl('Cancel', index=0).click()
- >>> 'No jobs were selected.' in browser.contents
- True
-
- >>> browser.getControl(name='jobs:list').getControl(
- ... value='1506179619').click()
- >>> browser.getControl('Cancel', index=0).click()
- >>> 'Jobs were successfully cancelled.' in browser.contents
- True
-
-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)
Deleted: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/cronjob.pt
===================================================================
--- Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/cronjob.pt 2010-10-11 12:05:58 UTC (rev 117447)
+++ Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/cronjob.pt 2010-10-11 12:07:42 UTC (rev 117448)
@@ -1,9 +0,0 @@
-<div metal:use-macro="view/base_template/macros/main" >
- <div metal:fill-slot="above_buttons"
- tal:define="inputForm view/inputForm | nothing"
- tal:condition="inputForm">
- <p>Job input parameter</p>
- <div tal:replace="structure inputForm" /><hr/>
- </div>
-</div>
-
Deleted: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/job.py
===================================================================
--- Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/job.py 2010-10-11 12:05:58 UTC (rev 117447)
+++ Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/job.py 2010-10-11 12:07:42 UTC (rev 117448)
@@ -1,218 +0,0 @@
-##############################################################################
-#
-# 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 datetime import datetime
-
-from zope import interface
-from zope import component
-from zope import schema
-from zope.browserpage import namedtemplate
-
-from zope import formlib
-
-from zope.traversing.browser.absoluteurl import absoluteURL
-from zope.publisher.browser import BrowserPage
-
-from zope.app.pagetemplate import ViewPageTemplateFile
-from zope.app.form.browser.textwidgets import TextWidget
-from zope.app.form.browser.itemswidgets import DropdownWidget
-from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
-
-from lovely.remotetask.interfaces import CRONJOB, ICronJob
-
-
-def noValidation(self, *args, **kwargs):
- return ()
-
-
-class JobDetail(BrowserPage):
- """A simple task input detail view."""
-
- def __call__(self):
- return u'No input detail available'
-
-
-class CronJobDetail(BrowserPage):
- """A simple task input detail view."""
-
- def __call__(self):
- job = self.context
- if job.scheduledFor is None:
- return u'not yet scheduled'
- if job.status == CRONJOB:
- dformat = self.request.locale.dates.getFormatter('dateTime',
- 'short')
- else:
- dformat = self.request.locale.dates.getFormatter('dateTime',
- 'medium')
- return u'Scheduled for %s'% dformat.format(job.scheduledFor)
-
-
-class StringTupleWidget(TextWidget):
-
- values = None
-
- def _toFormValue(self, input):
- if not input:
- return u''
- return u' '.join([str(v) for v in input])
-
- def _toFieldValue(self, input):
- input = input.strip()
- if self.convert_missing_value and input == self._missing:
- return self.context.missing_value
- if self.values is not None and input == '*':
- return self.values
- return tuple([int(v) for v in input.split()])
-
-
-class HourWidget(StringTupleWidget):
-
- values = tuple(range(0,24))
-
-
-class MinuteWidget(StringTupleWidget):
-
- values = tuple(range(0,60))
-
-
-class DayOfMonthWidget(StringTupleWidget):
-
- values = tuple(range(0,31))
-
-
-class MonthWidget(StringTupleWidget):
-
- values = tuple(range(0,12))
-
-
-class DayOfWeekWidget(StringTupleWidget):
-
- values = tuple(range(0,7))
-
-class TaskWidget(DropdownWidget):
-
- def __init__(self, field, request):
- terms = [SimpleTerm(name) for name in field.context.getAvailableTasks()]
- vocabulary = SimpleVocabulary(terms)
- super(TaskWidget, self).__init__(field, vocabulary, request)
-
-class CronJobFormBase(object):
- """base settings for all cron job forms"""
-
- form_fields = formlib.form.Fields(ICronJob).select(
- 'task',
- 'hour',
- 'minute',
- 'dayOfMonth',
- 'month',
- 'dayOfWeek',
- 'delay',
- )
- form_fields['task'].custom_widget = TaskWidget
- form_fields['hour'].custom_widget = HourWidget
- form_fields['minute'].custom_widget = MinuteWidget
- form_fields['dayOfMonth'].custom_widget = DayOfMonthWidget
- form_fields['month'].custom_widget = MonthWidget
- form_fields['dayOfWeek'].custom_widget = DayOfWeekWidget
-
- base_template = formlib.form.EditForm.template
- template = ViewPageTemplateFile('cronjob.pt')
-
-
-class CronJobEdit(formlib.form.EditForm, CronJobFormBase):
- """An edit view for cron jobs."""
-
- inputForm = None
-
- def setUpWidgets(self, ignore_request=False):
- jobtask = component.queryUtility(self.context.__parent__.taskInterface,
- name=self.context.task)
- if ( jobtask is not None
- and hasattr(jobtask, 'inputSchema')
- and jobtask.inputSchema is not interface.Interface
- ):
- subform = InputSchemaForm(context=self.context,
- request=self.request,
- )
- subform.prefix = 'taskinput'
- subform.form_fields = formlib.form.Fields(jobtask.inputSchema)
- self.inputForm = subform
- super(CronJobEdit, self).setUpWidgets(ignore_request=ignore_request)
-
- @formlib.form.action(u'Apply')
- def handle_apply_action(self, action, data):
- inputData = None
- if self.inputForm is not None:
- self.inputForm.update()
- inputData = {}
- errors = formlib.form.getWidgetsData(self.inputForm.widgets,
- self.inputForm.prefix,
- inputData)
- if len(inputData) == 0:
- inputData = None
- self.context.task = data['task']
- self.context.update(
- hour = data['hour'],
- minute = data['minute'],
- dayOfMonth = data['dayOfMonth'],
- month = data['month'],
- dayOfWeek = data['dayOfWeek'],
- delay = data['delay'],
- )
- self.context.__parent__.reschedule(self.context.id)
-
- @formlib.form.action(u'Cancel', validator=noValidation)
- def handle_cancel_action(self, action, data):
- self.request.response.redirect(self.nextURL())
-
- def nextURL(self):
- return '%s/@@jobs.html'% absoluteURL(self.context.__parent__,
- self.request)
-
-
-class AddCronJob(formlib.form.Form, CronJobFormBase):
- """An edit view for cron jobs."""
-
- @formlib.form.action(u'Add')
- def handle_add_action(self, action, data):
- self.context.addCronJob(
- task = data['task'],
- hour = data['hour'],
- minute = data['minute'],
- dayOfMonth = data['dayOfMonth'],
- month = data['month'],
- dayOfWeek = data['dayOfWeek'],
- delay = data['delay'],
- )
-
- @formlib.form.action(u'Cancel', validator=noValidation)
- def handle_cancel_action(self, action, data):
- self.request.response.redirect(self.nextURL())
-
- def nextURL(self):
- return '%s/@@jobs.html'% absoluteURL(self.context,
- self.request)
-
-
-class InputSchemaForm(formlib.form.AddForm):
- """An editor for input data of a job"""
- interface.implements(formlib.interfaces.ISubPageForm)
- template = namedtemplate.NamedTemplate('default')
- actions = []
Deleted: Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/table_header.pt
===================================================================
--- Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/table_header.pt 2010-10-11 12:05:58 UTC (rev 117447)
+++ Sandbox/gotcha/z3c.taskqueue_ui/trunk/src/z3c/taskqueue_ui/browser/table_header.pt 2010-10-11 12:07:42 UTC (rev 117448)
@@ -1,11 +0,0 @@
-<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>
More information about the checkins
mailing list