Merge branch 'master' into bbmaster

This commit is contained in:
Helmut Merz 2012-01-05 12:37:20 +01:00
commit 2c141a9285
9 changed files with 201 additions and 33 deletions

View file

@ -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):

View file

@ -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>

View file

@ -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)

View file

@ -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">
&copy; Copyright 2011, cyberconcepts IT-Consulting Dr. Helmut Merz &copy; 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 />

View file

@ -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>

View file

@ -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())

View file

@ -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
============= =============

View file

@ -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"

View file

@ -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:]