add cco.work to loops-ext, + minor Python3 fixes
This commit is contained in:
parent
55af2b5f75
commit
812bd28bc2
13 changed files with 685 additions and 0 deletions
97
cco/work/README.rst
Normal file
97
cco/work/README.rst
Normal file
|
@ -0,0 +1,97 @@
|
|||
|
||||
cco.work - cyberconcepts.org: project and task management stuff
|
||||
===============================================================
|
||||
|
||||
>>> from zope.publisher.browser import TestRequest
|
||||
>>> from logging import getLogger
|
||||
>>> log = getLogger('cco.work')
|
||||
|
||||
>>> from loops.setup import addAndConfigureObject, addObject
|
||||
>>> from loops.concept import Concept
|
||||
>>> from loops.common import adapted, baseObject
|
||||
|
||||
>>> concepts = loopsRoot['concepts']
|
||||
>>> len(list(concepts.keys()))
|
||||
14
|
||||
|
||||
>>> project = concepts['project']
|
||||
>>> task = concepts['task']
|
||||
|
||||
>>> from loops.browser.node import NodeView
|
||||
>>> home = loopsRoot['views']['home']
|
||||
>>> homeView = NodeView(home, TestRequest())
|
||||
|
||||
|
||||
Projects
|
||||
--------
|
||||
|
||||
### Basic operations ###
|
||||
|
||||
We start with creating a project and assigning an estimated effort to it.
|
||||
|
||||
>>> from loops.concept import Concept
|
||||
>>> from loops.setup import addAndConfigureObject
|
||||
|
||||
>>> proj01 = adapted(addAndConfigureObject(concepts, Concept, 'project01',
|
||||
... title=u'Project #1', conceptType=project))
|
||||
|
||||
When counting all assoctioated tasks we get just 1, the project itself.
|
||||
|
||||
>>> len(proj01.getAllTasks())
|
||||
1
|
||||
|
||||
We now add a task to the project so that we now count two tasks
|
||||
(including the project itself).
|
||||
|
||||
>>> task01 = adapted(addAndConfigureObject(concepts, Concept, 'task01',
|
||||
... title=u'Task #1', conceptType=task))
|
||||
>>> baseObject(task01).assignParent(baseObject(proj01))
|
||||
>>> len(proj01.getAllTasks())
|
||||
2
|
||||
|
||||
The actual effort for the project is 0.0 because there aren't any work items
|
||||
assigned to any of the tasks.
|
||||
|
||||
>>> proj01.actualEffort
|
||||
0.0
|
||||
|
||||
u'0:00'
|
||||
|
||||
So if we add work items the corresponding efforts are summed up.
|
||||
|
||||
>>> task01.addWorkItem('4711', 'work', effort=15 * 3600)
|
||||
>>> proj01.actualEffort
|
||||
54000.0
|
||||
|
||||
u'15:00'
|
||||
|
||||
>>> task02 = adapted(addAndConfigureObject(concepts, Concept, 'task02',
|
||||
... title=u'Task #2', conceptType=task))
|
||||
>>> baseObject(task02).assignParent(baseObject(proj01))
|
||||
>>> task02.addWorkItem('4711', 'work', effort=8 * 3600)
|
||||
>>> proj01.actualEffort
|
||||
82800.0
|
||||
|
||||
u'23:00'
|
||||
|
||||
The estimated and charged effords for tasks may be stored in
|
||||
corresponding fields. Their sums are shown in the enclosing project.
|
||||
|
||||
>>> task01.estimatedEffort = 100
|
||||
>>> task02.estimatedEffort = 15
|
||||
|
||||
>>> proj01.estimatedEffort
|
||||
115.0
|
||||
|
||||
Reporting
|
||||
---------
|
||||
|
||||
### Tasks overview ###
|
||||
|
||||
>>> from cco.work.browser import TasksOverview
|
||||
>>> view = TasksOverview(proj01, TestRequest())
|
||||
|
||||
>>> ri = view.reportInstance
|
||||
>>> ri
|
||||
<cco.work.report.TasksOverview object ...>
|
||||
|
2
cco/work/__init__.py
Normal file
2
cco/work/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
""" package cco.work
|
||||
"""
|
28
cco/work/browser.py
Normal file
28
cco/work/browser.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
#
|
||||
# Copyright (c) 2017 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
View class(es) for the cco.work package.
|
||||
"""
|
||||
|
||||
from loops.expert.browser.report import ReportConceptView
|
||||
|
||||
|
||||
class TasksOverview(ReportConceptView):
|
||||
|
||||
reportName = 'tasks_overview'
|
57
cco/work/configure.zcml
Normal file
57
cco/work/configure.zcml
Normal file
|
@ -0,0 +1,57 @@
|
|||
<configure
|
||||
xmlns:zope="http://namespaces.zope.org/zope"
|
||||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
xmlns:i18n="http://namespaces.zope.org/i18n"
|
||||
i18n_domain="cco.work">
|
||||
|
||||
<i18n:registerTranslations directory="locales" />
|
||||
|
||||
<!-- concept adapter classes -->
|
||||
|
||||
<zope:adapter factory="cco.work.task.Project" trusted="True" />
|
||||
<zope:class class="cco.work.task.Project">
|
||||
<require permission="zope.View"
|
||||
interface="cco.work.interfaces.IProject" />
|
||||
<require permission="zope.ManageContent"
|
||||
set_schema="cco.work.interfaces.IProject" />
|
||||
</zope:class>
|
||||
|
||||
<zope:adapter factory="cco.work.task.Task" trusted="True" />
|
||||
<zope:class class="cco.work.task.Task">
|
||||
<require permission="zope.View"
|
||||
interface="cco.work.interfaces.ITask" />
|
||||
<require permission="zope.ManageContent"
|
||||
set_schema="cco.work.interfaces.ITask" />
|
||||
</zope:class>
|
||||
|
||||
<!-- application views -->
|
||||
|
||||
<zope:adapter
|
||||
name="cco.work.tasks_overview.html"
|
||||
for="loops.interfaces.IConcept
|
||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
provides="zope.interface.Interface"
|
||||
factory="cco.work.browser.TasksOverview"
|
||||
permission="loops.ViewRestricted" />
|
||||
|
||||
<!-- field instances -->
|
||||
|
||||
<zope:adapter
|
||||
name="duration"
|
||||
factory="cco.work.interfaces.DurationFieldInstance" />
|
||||
|
||||
<!-- report instances -->
|
||||
|
||||
<zope:adapter
|
||||
name="cco.work.tasks_overview"
|
||||
factory="cco.work.report.TasksOverview"
|
||||
provides="loops.expert.report.IReportInstance"
|
||||
trusted="True" />
|
||||
<zope:class class="cco.work.report.TasksOverview">
|
||||
<require permission="zope.View"
|
||||
interface="loops.expert.report.IReportInstance" />
|
||||
<require permission="zope.ManageContent"
|
||||
set_schema="loops.expert.report.IReportInstance" />
|
||||
</zope:class>
|
||||
|
||||
</configure>
|
95
cco/work/interfaces.py
Normal file
95
cco/work/interfaces.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
#
|
||||
# Copyright (c) 2017 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Interfaces for organizational stuff like persons and addresses.
|
||||
"""
|
||||
|
||||
from zope.i18nmessageid import MessageFactory
|
||||
from zope.interface import Interface, Attribute
|
||||
from zope import interface, component, schema
|
||||
|
||||
from cybertools.composer.schema.field import FieldInstance
|
||||
from cybertools.composer.schema.interfaces import FieldType
|
||||
from cybertools.organize.interfaces import ITask
|
||||
from loops.interfaces import ILoopsAdapter, IConceptSchema
|
||||
|
||||
|
||||
_ = MessageFactory('cco.work')
|
||||
|
||||
|
||||
|
||||
class Duration(schema.Int):
|
||||
|
||||
__typeInfo__ = ('duration',
|
||||
FieldType('textline',
|
||||
u'A field representing a duration in time.',
|
||||
instanceName='duration'))
|
||||
|
||||
|
||||
class DurationFieldInstance(FieldInstance):
|
||||
|
||||
def display(self, value):
|
||||
if value is None:
|
||||
return ''
|
||||
if isinstance(value, basestring):
|
||||
value = value.replace(',', '.')
|
||||
try:
|
||||
value = float(value)
|
||||
except ValueError:
|
||||
value = 0.0
|
||||
return u'%02i:%02i' % divmod(value * self.factor / 60.0, 60)
|
||||
|
||||
@property
|
||||
def factor(self):
|
||||
return getattr(self.context.baseField, 'factor', 1)
|
||||
|
||||
|
||||
# project and task management
|
||||
|
||||
class IProject(ILoopsAdapter):
|
||||
|
||||
estimatedEffort = Duration(
|
||||
title=_(u'label_estimatedEffort'),
|
||||
description=_(u'desc_estimatedEffort'),
|
||||
readonly=True,)
|
||||
chargedEffort = Duration(
|
||||
title=_(u'label_chargedEffort'),
|
||||
description=_(u'desc_chargedEffort'),
|
||||
readonly=True,)
|
||||
actualEffort = Duration(
|
||||
title=_(u'label_actualEffort'),
|
||||
description=_(u'desc_actualEffort'),
|
||||
readonly=True)
|
||||
|
||||
estimatedEffort.factor = chargedEffort.factor = 3600
|
||||
|
||||
|
||||
class ITask(IConceptSchema, ITask, IProject):
|
||||
|
||||
estimatedEffort = Duration(
|
||||
title=_(u'label_estimatedEffort'),
|
||||
description=_(u'desc_estimatedEffort'),
|
||||
required=False,)
|
||||
chargedEffort = Duration(
|
||||
title=_(u'label_chargedEffort'),
|
||||
description=_(u'desc_chargedEffort'),
|
||||
required=False,)
|
||||
|
||||
estimatedEffort.factor = chargedEffort.factor = 3600
|
||||
|
42
cco/work/locales/cco.work.pot
Normal file
42
cco/work/locales/cco.work.pot
Normal file
|
@ -0,0 +1,42 @@
|
|||
|
||||
"Project-Id-Version: 1.0\n"
|
||||
"POT-Creation-Date: 2017-10-23 12:00 CET\n"
|
||||
"PO-Revision-Date: 2017-12-07 12:00 CET\n"
|
||||
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
||||
"Language-Team: cyberconcepts.org team <team@cyberconcepts.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: sublime_text\n"
|
||||
|
||||
# forms
|
||||
|
||||
msgid "label_estimatedEffort"
|
||||
msgstr "Estimated Effort (hours)"
|
||||
|
||||
msgid "desc_estimatedEffort"
|
||||
msgstr "The estimated effort in hours for completing this project."
|
||||
|
||||
msgid "label_chargedEffort"
|
||||
msgstr "Charged Effort (hours)"
|
||||
|
||||
msgid "desc_chargedEffort"
|
||||
msgstr "The effort in hours for completing this project as charged to the customer."
|
||||
|
||||
msgid "label_actualEffort"
|
||||
msgstr "Actual Effort (hours)"
|
||||
|
||||
msgid "desc_actualEffort"
|
||||
msgstr "The actual effort in hours."
|
||||
|
||||
# reports
|
||||
|
||||
msgid "colheader_estimatedEffort"
|
||||
msgstr "Estimated Effort"
|
||||
|
||||
msgid "colheader_chargedEffort"
|
||||
msgstr "Charged Effort"
|
||||
|
||||
msgid "colheader_actualEffort"
|
||||
msgstr "Actual Effort"
|
||||
|
BIN
cco/work/locales/de/LC_MESSAGES/cco.work.mo
Normal file
BIN
cco/work/locales/de/LC_MESSAGES/cco.work.mo
Normal file
Binary file not shown.
47
cco/work/locales/de/LC_MESSAGES/cco.work.po
Normal file
47
cco/work/locales/de/LC_MESSAGES/cco.work.po
Normal file
|
@ -0,0 +1,47 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
"Project-Id-Version: 1.0\n"
|
||||
"POT-Creation-Date: 2017-10-23 12:00 CET\n"
|
||||
"PO-Revision-Date: 2017-11-14 12:00 CET\n"
|
||||
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
||||
"Language-Team: cyberconcepts.org team <team@cyberconcepts.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: sublime_text\n"
|
||||
|
||||
# forms
|
||||
|
||||
msgid "label_estimatedEffort"
|
||||
msgstr "Geschätzter Aufwand (Stunden)"
|
||||
|
||||
msgid "desc_estimatedEffort"
|
||||
msgstr "Der geschätzte Aufwand für das Projekt in Stunden."
|
||||
|
||||
msgid "label_chargedEffort"
|
||||
msgstr "Aufwand lt. Rechnung (Stunden)"
|
||||
|
||||
msgid "desc_chargedEffort"
|
||||
msgstr "Der dem Kunden in Rechnung gestellte Aufwand in Stunden."
|
||||
|
||||
msgid "label_actualEffort"
|
||||
msgstr "Tatsächlicher Aufwand (Stunden)"
|
||||
|
||||
msgid "desc_actualEffort"
|
||||
msgstr "Der tatsächliche Aufwand in Stunden für das Projekt."
|
||||
|
||||
# reports
|
||||
|
||||
msgid "colheader_task"
|
||||
msgstr "Projekt/Aufgabe"
|
||||
|
||||
msgid "colheader_estimatedEffort"
|
||||
msgstr "Geschätzter Aufwand"
|
||||
|
||||
msgid "colheader_chargedEffort"
|
||||
msgstr "Aufwand lt. Rechnung"
|
||||
|
||||
msgid "colheader_actualEffort"
|
||||
msgstr "Tatsächlicher Aufwand"
|
||||
|
BIN
cco/work/locales/en/LC_MESSAGES/cco.work.mo
Normal file
BIN
cco/work/locales/en/LC_MESSAGES/cco.work.mo
Normal file
Binary file not shown.
47
cco/work/locales/en/LC_MESSAGES/cco.work.po
Normal file
47
cco/work/locales/en/LC_MESSAGES/cco.work.po
Normal file
|
@ -0,0 +1,47 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
"Project-Id-Version: 1.0\n"
|
||||
"POT-Creation-Date: 2017-10-23 12:00 CET\n"
|
||||
"PO-Revision-Date: 2017-12-07 12:00 CET\n"
|
||||
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
||||
"Language-Team: cyberconcepts.org team <team@cyberconcepts.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: sublime_text\n"
|
||||
|
||||
# forms
|
||||
|
||||
msgid "label_estimatedEffort"
|
||||
msgstr "Estimated Effort (hours)"
|
||||
|
||||
msgid "desc_estimatedEffort"
|
||||
msgstr "The estimated effort in hours for completing this project."
|
||||
|
||||
msgid "label_chargedEffort"
|
||||
msgstr "Charged Effort (hours)"
|
||||
|
||||
msgid "desc_quotedEffort"
|
||||
msgstr "The effort in hours for completing this project as given in a quote or offering."
|
||||
|
||||
msgid "label_actualEffort"
|
||||
msgstr "Actual Effort (hours)"
|
||||
|
||||
msgid "desc_actualEffort"
|
||||
msgstr "The actual effort in hours."
|
||||
|
||||
# reports
|
||||
|
||||
msgid "colheader_task"
|
||||
msgstr "Project/Task"
|
||||
|
||||
msgid "colheader_estimatedEffort"
|
||||
msgstr "Estimated Effort"
|
||||
|
||||
msgid "colheader_chargedEffort"
|
||||
msgstr "Charged Effort"
|
||||
|
||||
msgid "colheader_actualEffort"
|
||||
msgstr "Actual Effort"
|
||||
|
60
cco/work/report.py
Normal file
60
cco/work/report.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
#
|
||||
# Copyright (c) 2017 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Report class(es) for the cco.work package.
|
||||
"""
|
||||
|
||||
from cybertools.util.jeep import Jeep
|
||||
from loops.common import adapted, baseObject
|
||||
from loops.expert.field import Field, DecimalField, TargetField, UrlField
|
||||
from loops.expert.report import ReportInstance
|
||||
from loops.organize.work.report import DurationField
|
||||
from cco.work.interfaces import IProject, ITask, _
|
||||
|
||||
|
||||
task = UrlField('title', _(u'colheader_task'),
|
||||
executionSteps=['sort', 'output'])
|
||||
estimatedEffort = DurationField(
|
||||
'estimatedEffort', _(u'colheader_estimatedEffort'),
|
||||
factor = 3600,
|
||||
executionSteps=['output', 'totals'])
|
||||
chargedEffort = DurationField('chargedEffort', _(u'colheader_chargedEffort'),
|
||||
factor = 3600,
|
||||
executionSteps=['output', 'totals'])
|
||||
actualEffort = DurationField('actualEffort', _(u'colheader_actualEffort'),
|
||||
executionSteps=['output', 'totals'])
|
||||
|
||||
|
||||
class TasksOverview(ReportInstance):
|
||||
|
||||
type = 'cco.work.tasks_overview'
|
||||
label = u'Tasks Overview'
|
||||
|
||||
fields = Jeep((task, estimatedEffort, chargedEffort, actualEffort))
|
||||
#userSettings = (dayFrom, dayTo, activity)
|
||||
defaultOutputFields = fields
|
||||
defaultSortCriteria = (task,)
|
||||
|
||||
def selectObjects(self, parts):
|
||||
result = []
|
||||
for c in baseObject(self.view.adapted).getChildren():
|
||||
obj = adapted(c)
|
||||
if IProject.providedBy(obj) or ITask.providedBy(obj):
|
||||
result.append(obj)
|
||||
return result
|
115
cco/work/task.py
Normal file
115
cco/work/task.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
# cco.work.task
|
||||
|
||||
""" Implementation of cco.work concepts.
|
||||
"""
|
||||
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.interface import implementer
|
||||
|
||||
from cybertools.organize.interfaces import IWorkItems
|
||||
from loops.common import AdapterBase, adapted, baseObject
|
||||
#from loops.organize.task import Task
|
||||
from loops.type import TypeInterfaceSourceList
|
||||
from loops import util
|
||||
from cco.work.interfaces import IProject, ITask
|
||||
|
||||
#from cco.storage.loops import common
|
||||
|
||||
|
||||
TypeInterfaceSourceList.typeInterfaces += (IProject, ITask)
|
||||
|
||||
|
||||
#class TaskBase(common.AdapterBase):
|
||||
class TaskBase(AdapterBase):
|
||||
|
||||
_contextAttributes = list(ITask)
|
||||
|
||||
#start = end = None
|
||||
|
||||
defaultStates = ['done', 'done_x', 'finished', 'finished_x']
|
||||
|
||||
@property
|
||||
def actualEffort(self):
|
||||
result = 0.0
|
||||
for t in self.getAllTasks():
|
||||
for wi in t.getWorkItems():
|
||||
result += wi.effort
|
||||
return result
|
||||
|
||||
def getSubTasks(self):
|
||||
if self.__is_dummy__:
|
||||
return []
|
||||
result = []
|
||||
for c in baseObject(self).getChildren():
|
||||
obj = adapted(c)
|
||||
if IProject.providedBy(obj) or ITask.providedBy(obj):
|
||||
result.append(obj)
|
||||
return result
|
||||
|
||||
def getAllTasks(self):
|
||||
if self.__is_dummy__:
|
||||
return []
|
||||
result = set([self])
|
||||
for t1 in self.getSubTasks():
|
||||
if t1 not in result:
|
||||
for t2 in t1.getAllTasks():
|
||||
if t2 not in result:
|
||||
result.add(t2)
|
||||
return result
|
||||
|
||||
@Lazy
|
||||
def workItems(self):
|
||||
rm = self.getLoopsRoot().getRecordManager()
|
||||
return IWorkItems(rm['work'])
|
||||
|
||||
def addWorkItem(self, party, action='plan', **kw):
|
||||
wi = self.workItems.add(util.getUidForObject(baseObject(self)), party)
|
||||
wi.doAction(action, party, **kw)
|
||||
|
||||
def getWorkItems(self, crit={}):
|
||||
kw = dict(task=util.getUidForObject(baseObject(self)),
|
||||
state=crit.get('states') or self.defaultStates)
|
||||
return self.workItems.query(**kw)
|
||||
|
||||
|
||||
@implementer(IProject)
|
||||
class Project(TaskBase):
|
||||
""" Adapter for concepts of a project type
|
||||
with additional fields for planning/controlling.
|
||||
"""
|
||||
|
||||
_adapterAttributes = AdapterBase._adapterAttributes + (
|
||||
'estimatedEffort', 'chargedEffort', 'actualEffort',)
|
||||
_contextAttributes = list(IProject)
|
||||
|
||||
@property
|
||||
def estimatedEffort(self):
|
||||
return sum(self.tofloat(t.estimatedEffort or 0.0)
|
||||
for t in self.getSubTasks())
|
||||
|
||||
@property
|
||||
def chargedEffort(self):
|
||||
return sum(self.tofloat(t.chargedEffort or 0.0)
|
||||
for t in self.getSubTasks())
|
||||
|
||||
@staticmethod
|
||||
def tofloat(v):
|
||||
if isinstance(v, str):
|
||||
v = v.replace(',', '.')
|
||||
try:
|
||||
return float(v)
|
||||
except ValueError:
|
||||
return 0.0
|
||||
|
||||
|
||||
@implementer(ITask)
|
||||
class Task(TaskBase):
|
||||
""" Adapter for concepts of a task type
|
||||
with additional fields for planning/controlling.
|
||||
"""
|
||||
|
||||
#estimatedEffort = chargedEffort = 0.0
|
||||
|
||||
_adapterAttributes = AdapterBase._adapterAttributes + ('actualEffort',)
|
||||
_contextAttributes = list(ITask)
|
||||
|
95
cco/work/tests.py
Normal file
95
cco/work/tests.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
#! /usr/bin/python
|
||||
|
||||
"""
|
||||
Tests for the 'cco.work' package.
|
||||
"""
|
||||
|
||||
import os
|
||||
import unittest, doctest
|
||||
from zope import component
|
||||
from zope.app.testing.setup import placefulSetUp, placefulTearDown
|
||||
|
||||
from cybertools.organize.work import workItemStates
|
||||
from cybertools.tracking.btree import TrackingStorage
|
||||
from loops.concept import Concept
|
||||
from loops.expert.report import IReport, IReportInstance, Report
|
||||
from loops.organize.work.base import WorkItem, WorkItems
|
||||
from loops.setup import addAndConfigureObject
|
||||
from loops.tests.setup import TestSite
|
||||
from cco.work.interfaces import IProject, ITask
|
||||
from cco.work.report import TasksOverview
|
||||
from cco.work.task import Project, Task
|
||||
|
||||
|
||||
def setupComponents(loopsRoot):
|
||||
component.provideAdapter(WorkItems)
|
||||
component.provideUtility(workItemStates(), name='organize.workItemStates')
|
||||
component.provideAdapter(Project)
|
||||
component.provideAdapter(Report, provides=IReport)
|
||||
component.provideAdapter(Task)
|
||||
component.provideAdapter(TasksOverview, provides=IReportInstance,
|
||||
name='cco.work.tasks_overview')
|
||||
|
||||
|
||||
def setUp(self):
|
||||
site = placefulSetUp(True)
|
||||
t = TestSite(site)
|
||||
concepts, resources, views = t.setup()
|
||||
loopsRoot = site['loops']
|
||||
self.globs['loopsRoot'] = loopsRoot
|
||||
setupComponents(loopsRoot)
|
||||
records = loopsRoot.getRecordManager()
|
||||
if 'work' not in records:
|
||||
records['work'] = TrackingStorage(trackFactory=WorkItem)
|
||||
setupObjects(concepts)
|
||||
|
||||
def setupObjects(concepts):
|
||||
addAndConfigureObject(concepts, Concept, 'project',
|
||||
title='Project', conceptType=concepts['type'],
|
||||
typeInterface=IProject)
|
||||
addAndConfigureObject(concepts, Concept, 'task',
|
||||
title='Task', conceptType=concepts['type'],
|
||||
typeInterface=ITask)
|
||||
addAndConfigureObject(concepts, Concept, 'report',
|
||||
title='Task', conceptType=concepts['type'],
|
||||
typeInterface=IReport)
|
||||
addAndConfigureObject(concepts, Concept, 'tasks_overview',
|
||||
title='Tasks Overview', conceptType=concepts['report'],
|
||||
reportType='cco.work.tasks_overview')
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
placefulTearDown()
|
||||
|
||||
|
||||
class StorageTest(unittest.TestCase):
|
||||
"Basic tests."
|
||||
|
||||
def setUp(self):
|
||||
site = placefulSetUp(True)
|
||||
t = TestSite(site)
|
||||
self.concepts, resources, views = t.setup()
|
||||
loopsRoot = site['loops']
|
||||
setupComponents(loopsRoot)
|
||||
setupObjects(self.concepts)
|
||||
|
||||
def tearDown(self):
|
||||
placefulTearDown()
|
||||
|
||||
def testStorage(self):
|
||||
self.setUp()
|
||||
#print('***', self.concepts)
|
||||
self.tearDown()
|
||||
|
||||
|
||||
def test_suite():
|
||||
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||
loader = unittest.TestLoader()
|
||||
return unittest.TestSuite((
|
||||
doctest.DocFileSuite('README.rst', optionflags=flags,
|
||||
setUp=setUp, tearDown=tearDown),
|
||||
loader.loadTestsFromTestCase(StorageTest),
|
||||
))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
Loading…
Add table
Reference in a new issue