Merge branch 'master' into bbmaster
This commit is contained in:
commit
2c141a9285
9 changed files with 201 additions and 33 deletions
|
@ -349,7 +349,7 @@ class BaseView(GenericView, I18NView):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def dcDescription(self):
|
def dcDescription(self):
|
||||||
return self.dublincore.description or self.description
|
return self.dublincore.description or u'' #self.description
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def headTitle(self):
|
def headTitle(self):
|
||||||
|
|
|
@ -97,7 +97,7 @@
|
||||||
tal:attributes="onChange
|
tal:attributes="onChange
|
||||||
string:return replaceFieldsNode(
|
string:return replaceFieldsNode(
|
||||||
'form.fields', 'form.type',
|
'form.fields', 'form.type',
|
||||||
'${view/url}/$innerForm')">
|
'${view/virtualTargetUrl}/$innerForm')">
|
||||||
<option value=".loops/concepts/note"
|
<option value=".loops/concepts/note"
|
||||||
i18n:translate=""
|
i18n:translate=""
|
||||||
tal:repeat="type python:
|
tal:repeat="type python:
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
<script type="text/javascript"
|
<script type="text/javascript"
|
||||||
tal:content="string:replaceFieldsNode(
|
tal:content="string:replaceFieldsNode(
|
||||||
'form.fields', 'form.type',
|
'form.fields', 'form.type',
|
||||||
'${view/url}/$innerForm')">
|
'${view/virtualTargetUrl}/$innerForm')">
|
||||||
</script>
|
</script>
|
||||||
</th></tr></tbody>
|
</th></tr></tbody>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#-*- coding: UTF-8 -*-
|
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
|
@ -41,7 +40,7 @@ class Controller(BaseController):
|
||||||
if desc:
|
if desc:
|
||||||
for line in desc.splitlines():
|
for line in desc.splitlines():
|
||||||
if ':' in line:
|
if ':' in line:
|
||||||
name, value = line.split(':')
|
name, value = line.split(':', 1)
|
||||||
if name in metaTagNames:
|
if name in metaTagNames:
|
||||||
macros.register('meta', name, metaName=name,
|
macros.register('meta', name, metaName=name,
|
||||||
metaContent=value)
|
metaContent=value)
|
||||||
|
|
|
@ -66,7 +66,8 @@
|
||||||
<div id="footer" class="footer clear"
|
<div id="footer" class="footer clear"
|
||||||
metal:define-macro="footer">
|
metal:define-macro="footer">
|
||||||
<metal:footer define-slot="footer">
|
<metal:footer define-slot="footer">
|
||||||
© Copyright 2011, cyberconcepts IT-Consulting Dr. Helmut Merz
|
© Copyright <span tal:replace="view/currentYear" />,
|
||||||
|
cyberconcepts IT-Consulting Dr. Helmut Merz
|
||||||
(<a href="#"
|
(<a href="#"
|
||||||
tal:attributes="href string:${view/topMenu/url}/impressum">Impressum</a>)
|
tal:attributes="href string:${view/topMenu/url}/impressum">Impressum</a>)
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -18,18 +18,18 @@
|
||||||
<table class="report"
|
<table class="report"
|
||||||
tal:define="results view/results">
|
tal:define="results view/results">
|
||||||
<tr>
|
<tr>
|
||||||
<th tal:repeat="col view/displayedColumns"
|
<th tal:repeat="col results/displayedColumns"
|
||||||
tal:content="col/title"
|
tal:content="col/title"
|
||||||
i18n:translate="" />
|
i18n:translate="" />
|
||||||
</tr>
|
</tr>
|
||||||
<tr tal:repeat="row results">
|
<tr tal:repeat="row results">
|
||||||
<tal:column repeat="col view/displayedColumns">
|
<tal:column repeat="col results/displayedColumns">
|
||||||
<metal:column use-macro="python:view.getColumnRenderer(col.renderer)" />
|
<metal:column use-macro="python:view.getColumnRenderer(col.renderer)" />
|
||||||
</tal:column>
|
</tal:column>
|
||||||
</tr>
|
</tr>
|
||||||
<tr tal:define="row nocall:results/totals"
|
<tr tal:define="row nocall:results/totals"
|
||||||
tal:condition="nocall:row">
|
tal:condition="nocall:row">
|
||||||
<tal:column repeat="col view/displayedColumns">
|
<tal:column repeat="col results/displayedColumns">
|
||||||
<metal:column use-macro="python:view.getColumnRenderer(col.renderer)" />
|
<metal:column use-macro="python:view.getColumnRenderer(col.renderer)" />
|
||||||
</tal:column>
|
</tal:column>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -56,4 +56,29 @@
|
||||||
</metal:target>
|
</metal:target>
|
||||||
|
|
||||||
|
|
||||||
|
<div metal:define-macro="subreport">
|
||||||
|
<td>
|
||||||
|
<table class="subreport"
|
||||||
|
tal:define="results python:col.getValue(row)">
|
||||||
|
<tr>
|
||||||
|
<th tal:repeat="col results/displayedColumns"
|
||||||
|
tal:content="col/title"
|
||||||
|
i18n:translate="" />
|
||||||
|
</tr>
|
||||||
|
<tr tal:repeat="row results">
|
||||||
|
<tal:column repeat="col results/displayedColumns">
|
||||||
|
<metal:column use-macro="python:view.getColumnRenderer(col.renderer)" />
|
||||||
|
</tal:column>
|
||||||
|
</tr>
|
||||||
|
<tr tal:define="row nocall:results/totals"
|
||||||
|
tal:condition="nocall:row">
|
||||||
|
<tal:column repeat="col results/displayedColumns">
|
||||||
|
<metal:column use-macro="python:view.getColumnRenderer(col.renderer)" />
|
||||||
|
</tal:column>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -21,7 +21,9 @@ Field definitions for reports.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from cybertools.composer.report.field import Field
|
from cybertools.composer.report.field import Field
|
||||||
|
from cybertools.composer.report.result import ResultSet
|
||||||
from loops.common import baseObject
|
from loops.common import baseObject
|
||||||
|
from loops.expert.report import ReportInstance
|
||||||
from loops import util
|
from loops import util
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,6 +42,8 @@ class UrlField(Field):
|
||||||
|
|
||||||
def getDisplayValue(self, row):
|
def getDisplayValue(self, row):
|
||||||
nv = row.parent.context.view.nodeView
|
nv = row.parent.context.view.nodeView
|
||||||
|
if row.context is None: # probably a totals row
|
||||||
|
return dict(title=u'', url=u'')
|
||||||
return dict(title=self.getValue(row),
|
return dict(title=self.getValue(row),
|
||||||
url=nv.getUrlForTarget(baseObject(row.context)))
|
url=nv.getUrlForTarget(baseObject(row.context)))
|
||||||
|
|
||||||
|
@ -61,3 +65,25 @@ class TargetField(Field):
|
||||||
view = row.parent.context.view
|
view = row.parent.context.view
|
||||||
return dict(title=value.title, url=view.getUrlForTarget(value))
|
return dict(title=value.title, url=view.getUrlForTarget(value))
|
||||||
|
|
||||||
|
|
||||||
|
# sub-report stuff
|
||||||
|
|
||||||
|
class SubReport(ReportInstance):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SubReportField(Field):
|
||||||
|
|
||||||
|
renderer = 'subreport'
|
||||||
|
reportFactory = SubReport
|
||||||
|
|
||||||
|
def getReportInstance(self, row):
|
||||||
|
baseReport = row.parent.context
|
||||||
|
instance = self.reportFactory(baseReport.context)
|
||||||
|
instance.view = baseReport.view
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def getValue(self, row):
|
||||||
|
ri = self.getReportInstance(row)
|
||||||
|
return ResultSet(ri, ri.getResults())
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
loops - Linked Objects for Organization and Processing Services
|
loops - Linked Objects for Organization and Processing Services
|
||||||
===============================================================
|
===============================================================
|
||||||
|
|
||||||
($Id$)
|
|
||||||
|
|
||||||
Let's do some basic setup
|
Let's do some basic setup
|
||||||
|
|
||||||
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
|
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
|
||||||
|
@ -194,6 +192,9 @@ In addition we need a predicate that connects one or more tasks with a report.
|
||||||
>>> hasReport = addAndConfigureObject(concepts, Concept, 'hasreport',
|
>>> hasReport = addAndConfigureObject(concepts, Concept, 'hasreport',
|
||||||
... title=u'has Report', conceptType=concepts.getPredicateType())
|
... title=u'has Report', conceptType=concepts.getPredicateType())
|
||||||
|
|
||||||
|
Work statement report
|
||||||
|
---------------------
|
||||||
|
|
||||||
Now we can create a report and register it as the report for the task
|
Now we can create a report and register it as the report for the task
|
||||||
used above.
|
used above.
|
||||||
|
|
||||||
|
@ -235,6 +236,54 @@ in a results view.
|
||||||
{'effort': 900}
|
{'effort': 900}
|
||||||
|
|
||||||
|
|
||||||
|
Meeting Minutes
|
||||||
|
===============
|
||||||
|
|
||||||
|
We can use an event with assigned tasks as the basis for planning a meeting
|
||||||
|
and recording information about the tasks.
|
||||||
|
|
||||||
|
Let's start with creating an a event and assigning it a task.
|
||||||
|
|
||||||
|
>>> from loops.organize.interfaces import ITask
|
||||||
|
>>> tEvent = addAndConfigureObject(concepts, Concept, 'event',
|
||||||
|
... title=u'Event', conceptType=concepts.getTypeConcept(),
|
||||||
|
... typeInterface=ITask)
|
||||||
|
|
||||||
|
>>> ev01 = addAndConfigureObject(concepts, Concept, 'ev01',
|
||||||
|
... title=u'loops Meeting', conceptType=tEvent)
|
||||||
|
>>> ev01.assignChild(task01)
|
||||||
|
|
||||||
|
Now we create the meeting minutes report. We assign the event type as a
|
||||||
|
child in order to provide the information for which types of objects the
|
||||||
|
report is available.
|
||||||
|
|
||||||
|
>>> from loops.organize.work.report import MeetingMinutes
|
||||||
|
>>> component.provideAdapter(MeetingMinutes, provides=IReportInstance,
|
||||||
|
... name='meeting_minutes')
|
||||||
|
|
||||||
|
>>> meetingMinutes = addAndConfigureObject(concepts, Concept, 'meeting_minutes',
|
||||||
|
... title=u'Meeting Minutes', conceptType=tReport,
|
||||||
|
... reportType='meeting_minutes')
|
||||||
|
>>> meetingMinutes.assignChild(tEvent, hasReport)
|
||||||
|
|
||||||
|
We can now access the report using a results view.
|
||||||
|
|
||||||
|
>>> from loops.util import getUidForObject
|
||||||
|
>>> input = dict(tasks=getUidForObject(ev01))
|
||||||
|
>>> resultsView = ResultsView(home, TestRequest(form=input))
|
||||||
|
>>> resultsView.virtualTargetObject = meetingMinutes
|
||||||
|
|
||||||
|
>>> results = resultsView.results()
|
||||||
|
>>> len(list(results))
|
||||||
|
1
|
||||||
|
>>> for row in results:
|
||||||
|
... for col in resultsView.displayedColumns:
|
||||||
|
... print col.getDisplayValue(row),
|
||||||
|
... print
|
||||||
|
{'url': 'http://127.0.0.1/loops/views/home/.36', 'title': u'loops Development'}
|
||||||
|
<cybertools.composer.report.result.ResultSet object ...>
|
||||||
|
|
||||||
|
|
||||||
Fin de partie
|
Fin de partie
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,17 @@
|
||||||
set_schema="loops.expert.report.IReportInstance" />
|
set_schema="loops.expert.report.IReportInstance" />
|
||||||
</zope:class>
|
</zope:class>
|
||||||
|
|
||||||
|
<zope:adapter factory="loops.organize.work.report.MeetingMinutes"
|
||||||
|
name="meeting_minutes"
|
||||||
|
provides="loops.expert.report.IReportInstance"
|
||||||
|
trusted="True" />
|
||||||
|
<zope:class class="loops.organize.work.report.MeetingMinutes">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.expert.report.IReportInstance" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.expert.report.IReportInstance" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
<!-- setup -->
|
<!-- setup -->
|
||||||
|
|
||||||
<zope:adapter factory="loops.organize.work.setup.SetupManager"
|
<zope:adapter factory="loops.organize.work.setup.SetupManager"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -33,7 +33,7 @@ from cybertools.util.date import timeStamp2Date
|
||||||
from cybertools.util.format import formatDate
|
from cybertools.util.format import formatDate
|
||||||
from cybertools.util.jeep import Jeep
|
from cybertools.util.jeep import Jeep
|
||||||
from loops.common import adapted, baseObject
|
from loops.common import adapted, baseObject
|
||||||
from loops.expert.field import TargetField
|
from loops.expert.field import TargetField, TextField, UrlField, SubReportField
|
||||||
from loops.expert.report import ReportInstance
|
from loops.expert.report import ReportInstance
|
||||||
from loops import util
|
from loops import util
|
||||||
|
|
||||||
|
@ -84,9 +84,15 @@ class DurationField(Field):
|
||||||
return u'%02i:%02i' % divmod(value * 60, 60)
|
return u'%02i:%02i' % divmod(value * 60, 60)
|
||||||
|
|
||||||
|
|
||||||
|
# common fields
|
||||||
|
|
||||||
tasks = Field('tasks', u'Tasks',
|
tasks = Field('tasks', u'Tasks',
|
||||||
description=u'The tasks from which work items should be selected.',
|
description=u'The tasks from which sub-tasks and '
|
||||||
|
u'work items should be selected.',
|
||||||
executionSteps=['query'])
|
executionSteps=['query'])
|
||||||
|
|
||||||
|
# work report fields
|
||||||
|
|
||||||
dayFrom = Field('dayFrom', u'Start Day',
|
dayFrom = Field('dayFrom', u'Start Day',
|
||||||
description=u'The first day from which to select work.',
|
description=u'The first day from which to select work.',
|
||||||
executionSteps=['query'])
|
executionSteps=['query'])
|
||||||
|
@ -108,12 +114,12 @@ task = TargetField('taskId', u'Task',
|
||||||
party = TargetField('userName', u'Party',
|
party = TargetField('userName', u'Party',
|
||||||
description=u'The party (usually a person) who did the work.',
|
description=u'The party (usually a person) who did the work.',
|
||||||
executionSteps=['query', 'sort', 'output'])
|
executionSteps=['query', 'sort', 'output'])
|
||||||
title = Field('title', u'Title',
|
workTitle = Field('title', u'Title',
|
||||||
description=u'The short description of the work.',
|
description=u'The short description of the work.',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
description = Field('description', u'Description',
|
workDescription = Field('description', u'Description',
|
||||||
description=u'The long description of the work.',
|
description=u'The long description of the work.',
|
||||||
executionSteps=['x_output'])
|
executionSteps=['output'])
|
||||||
duration = DurationField('duration', u'Duration',
|
duration = DurationField('duration', u'Duration',
|
||||||
description=u'The duration of the work.',
|
description=u'The duration of the work.',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
|
@ -124,6 +130,20 @@ state = Field('state', u'State',
|
||||||
description=u'The state of the work.',
|
description=u'The state of the work.',
|
||||||
executionSteps=['query', 'output'])
|
executionSteps=['query', 'output'])
|
||||||
|
|
||||||
|
# task/event report fields
|
||||||
|
|
||||||
|
taskTitle = UrlField('title', u'Title',
|
||||||
|
description=u'The short description of the task.',
|
||||||
|
executionSteps=['output'])
|
||||||
|
taskDescription = TextField('description', u'Description',
|
||||||
|
description=u'The long description of the task.',
|
||||||
|
executionSteps=['output'])
|
||||||
|
workItems = SubReportField('workItems', u'Work Items',
|
||||||
|
description=u'A list of work items belonging to the task.',
|
||||||
|
executionSteps=['output'])
|
||||||
|
|
||||||
|
|
||||||
|
# basic definitions and work report instance
|
||||||
|
|
||||||
class WorkRow(BaseRow):
|
class WorkRow(BaseRow):
|
||||||
|
|
||||||
|
@ -161,11 +181,13 @@ class WorkReportInstance(ReportInstance):
|
||||||
rowFactory = WorkRow
|
rowFactory = WorkRow
|
||||||
|
|
||||||
fields = Jeep((dayFrom, dayTo, tasks,
|
fields = Jeep((dayFrom, dayTo, tasks,
|
||||||
day, timeStart, timeEnd, task, party, title, description,
|
day, timeStart, timeEnd, task, party, workTitle, #description,
|
||||||
duration, effort, state))
|
duration, effort, state))
|
||||||
|
|
||||||
defaultOutputFields = fields
|
defaultOutputFields = fields
|
||||||
defaultSortCriteria = (day, timeStart,)
|
defaultSortCriteria = (day, timeStart,)
|
||||||
|
states = ('done', 'done_x', 'finished')
|
||||||
|
taskTypeNames = ('task', 'event', 'project')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def queryCriteria(self):
|
def queryCriteria(self):
|
||||||
|
@ -183,22 +205,20 @@ class WorkReportInstance(ReportInstance):
|
||||||
|
|
||||||
def selectObjects(self, parts):
|
def selectObjects(self, parts):
|
||||||
result = []
|
result = []
|
||||||
tasks = [util.getObjectForUid(t) for t in parts.pop('tasks').comparisonValue]
|
for t in self.getTasks(parts):
|
||||||
for t in list(tasks):
|
|
||||||
tasks.extend(self.getAllSubtasks(t))
|
|
||||||
for t in tasks:
|
|
||||||
result.extend(self.selectWorkItems(t, parts))
|
result.extend(self.selectWorkItems(t, parts))
|
||||||
# remove parts already used for selection from parts list:
|
# remove parts already used for selection from parts list:
|
||||||
parts.pop('userName', None)
|
parts.pop('userName', None)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def selectWorkItems(self, task, parts):
|
def getTasks(self, parts):
|
||||||
states = ['done', 'done_x', 'finished']
|
taskIds = parts.pop('tasks').comparisonValue
|
||||||
kw = dict(task=util.getUidForObject(task), state=states)
|
if not isinstance(taskIds, (list, tuple)):
|
||||||
if 'userName' in parts:
|
taskIds = [taskIds]
|
||||||
kw['userName'] = parts['userName'].comparisonValue
|
tasks = [util.getObjectForUid(t) for t in taskIds]
|
||||||
wi = self.workItems
|
for t in list(tasks):
|
||||||
return wi.query(**kw)
|
tasks.extend(self.getAllSubtasks(t))
|
||||||
|
return tasks
|
||||||
|
|
||||||
def getAllSubtasks(self, concept):
|
def getAllSubtasks(self, concept):
|
||||||
result = []
|
result = []
|
||||||
|
@ -208,12 +228,49 @@ class WorkReportInstance(ReportInstance):
|
||||||
result.extend(self.getAllSubtasks(c))
|
result.extend(self.getAllSubtasks(c))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def selectWorkItems(self, task, parts):
|
||||||
|
# TODO: take states from parts
|
||||||
|
kw = dict(task=util.getUidForObject(task), state=self.states)
|
||||||
|
if 'userName' in parts:
|
||||||
|
kw['userName'] = parts['userName'].comparisonValue
|
||||||
|
wi = self.workItems
|
||||||
|
return wi.query(**kw)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def taskTypes(self):
|
def taskTypes(self):
|
||||||
return (self.conceptManager['task'],
|
return [c for c in [self.conceptManager.get(name)
|
||||||
self.conceptManager['event'],
|
for name in self.taskTypeNames]
|
||||||
self.conceptManager['project'])
|
if c is not None]
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def workItems(self):
|
def workItems(self):
|
||||||
return IWorkItems(self.recordManager['work'])
|
return IWorkItems(self.recordManager['work'])
|
||||||
|
|
||||||
|
|
||||||
|
# meeting minutes
|
||||||
|
|
||||||
|
class TaskRow(BaseRow):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MeetingMinutes(WorkReportInstance):
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# header (event) fields: title, description, from/to,
|
||||||
|
# location, participants (or put in description?)
|
||||||
|
# result set field for work items
|
||||||
|
# work item fields: title, description, party, deadline, state
|
||||||
|
|
||||||
|
type = "meeting_minutes"
|
||||||
|
label = u'Meeting Minutes'
|
||||||
|
|
||||||
|
rowFactory = TaskRow
|
||||||
|
|
||||||
|
fields = Jeep((tasks, taskTitle, taskDescription, workItems))
|
||||||
|
defaultOutputFields = fields
|
||||||
|
states = ('planned', 'accepted', 'done', 'done_x', 'finished')
|
||||||
|
|
||||||
|
def selectObjects(self, parts):
|
||||||
|
return self.getTasks(parts)[1:]
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue