merge branch master

This commit is contained in:
Helmut Merz 2011-12-02 10:16:56 +01:00
commit 5f95ef439e
22 changed files with 496 additions and 148 deletions

View file

@ -649,6 +649,23 @@ to the bottom, and to the top.
['m111', 'm114', 'm112', 'm113'] ['m111', 'm114', 'm112', 'm113']
Breadcrumbs
-----------
>>> view = NodeView(m112, TestRequest())
>>> view.breadcrumbs()
[]
>>> loopsRoot.options = ['showBreadcrumbs']
>>> m114.nodeType = 'page'
>>> m114.target = cc1
>>> view = NodeView(m114, TestRequest())
>>> view.breadcrumbs()
[{'url': 'http://127.0.0.1/loops/views/m1', 'label': u'Menu'},
{'url': 'http://127.0.0.1/loops/views/m1/m11', 'label': u'Zope'},
{'url': 'http://127.0.0.1/loops/views/m1/m11/m114', 'label': u''}]
End-user Forms and Special Views End-user Forms and Special Views
================================ ================================

View file

@ -18,8 +18,6 @@
""" """
Common base class for loops browser view classes. Common base class for loops browser view classes.
$Id$
""" """
from cgi import parse_qs, parse_qsl from cgi import parse_qs, parse_qsl
@ -146,6 +144,9 @@ class BaseView(GenericView, I18NView):
return self.controller.getTemplateMacros('resource', resource_macros) return self.controller.getTemplateMacros('resource', resource_macros)
#return resource_macros.macros #return resource_macros.macros
def breadcrumbs(self):
return []
@Lazy @Lazy
def name(self): def name(self):
return getName(self.context) return getName(self.context)

View file

@ -139,7 +139,7 @@ class Layout(Base, ConceptView):
parts = (self.options('parts') or parts = (self.options('parts') or
self.typeOptions('parts') or self.typeOptions('parts') or
['h1', 'g3']) ['h1', 'g3'])
ti = adapted(self.context.conceptType).typeInterface #ti = adapted(self.context.conceptType).typeInterface
for p in parts: for p in parts:
viewName = 'lobo_' + p viewName = 'lobo_' + p
view = component.queryMultiAdapter((self.context, self.request), view = component.queryMultiAdapter((self.context, self.request),

View file

@ -455,7 +455,6 @@ div.comment {
} }
.blogpost .description { .blogpost .description {
font-weight: bold;
font-size: 90%; font-size: 90%;
color: #666666; color: #666666;
padding-top: 0.4em; padding-top: 0.4em;

View file

@ -91,6 +91,10 @@ class NodeView(BaseView):
self.recordAccess() self.recordAccess()
return result return result
@Lazy
def title(self):
return self.context.title or getName(self.context)
def breadcrumbs(self): def breadcrumbs(self):
if not self.globalOptions('showBreadcrumbs'): if not self.globalOptions('showBreadcrumbs'):
return [] return []
@ -100,7 +104,13 @@ class NodeView(BaseView):
if menuItem != menu.context: if menuItem != menu.context:
data.append(dict(label=menuItem.title, data.append(dict(label=menuItem.title,
url=absoluteURL(menuItem, self.request))) url=absoluteURL(menuItem, self.request)))
return data for p in getParents(menuItem):
if p == menu.context:
break
data.insert(1, dict(label=p.title,
url=absoluteURL(p, self.request)))
if self.virtualTarget:
data.extend(self.virtualTarget.breadcrumbs())
def recordAccess(self, viewName=''): def recordAccess(self, viewName=''):
target = self.virtualTargetObject target = self.virtualTargetObject

View file

@ -29,10 +29,11 @@
<metal:breadcrumbs define-slot="breadcrumbs"> <metal:breadcrumbs define-slot="breadcrumbs">
<div tal:define="crumbs view/breadcrumbs" <div tal:define="crumbs view/breadcrumbs"
tal:condition="crumbs"> tal:condition="crumbs">
<span i18n:translate="">You are here:</span>
<span tal:repeat="crumb crumbs"> <span tal:repeat="crumb crumbs">
<a tal:attributes="href crumb/url" <a tal:attributes="href crumb/url"
tal:content="crumb/label" /> tal:content="crumb/label" />
<span tal:condition="not:repeat/crumb/end"> / </span></span> <span tal:condition="not:repeat/crumb/end"> > </span></span>
</div></metal:breadcrumbs> </div></metal:breadcrumbs>
<div metal:define-slot="actions"></div> <div metal:define-slot="actions"></div>
<div metal:define-slot="message"></div> <div metal:define-slot="message"></div>
@ -63,7 +64,7 @@
tal:attributes="href string:${view/topMenu/url}/impressum">Impressum</a>) tal:attributes="href string:${view/topMenu/url}/impressum">Impressum</a>)
<br /> <br />
Powered by Powered by
<b><a href="http://loops.cy55.de">loops</a></b> &middot; <b><a href="http://www.wissen-statt-suchen.de">loops</a></b> &middot;
<b><a href="http://wiki.zope.org/zope3">Zope 3</a></b> &middot; <b><a href="http://wiki.zope.org/zope3">Zope 3</a></b> &middot;
<b><a href="http://www.python.org">Python</a></b> &middot; <b><a href="http://www.python.org">Python</a></b> &middot;
<b><a href="http://www.dojotoolkit.org">Dojo</a></b>. <b><a href="http://www.dojotoolkit.org">Dojo</a></b>.

View file

@ -13,7 +13,7 @@
/* general */ /* general */
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
margin-bottom: 0.5em; margin-bottom: 0.4em;
} }
a[href]:hover { a[href]:hover {
@ -472,23 +472,37 @@ div.comment {
/* blog */ /* blog */
.blog h1.headline {
clear: both;
padding-top: 1em;
margin-bottom: 0.2em;
font-size: 150%;
}
.blog .description { .blog .description {
font-size: 90%; font-size: 90%;
color: #666666; color: #666666;
} }
.blog .text {
font-size: 90%;
margin-top: 0.5em;
}
.blog img {
padding-top: 0.2em;
}
.blogpost .description { .blogpost .description {
font-weight: bold;
font-size: 90%; font-size: 90%;
color: #666666; color: #666666;
padding-top: 0.4em; margin-top: 1em;
} }
.blog .info, .blogpost .info { .blog .info, .blogpost .info {
font-style: italic; font-style: italic;
font-size: 90%; font-size: 90%;
color: #666666; color: #666666;
padding-top: 0.4em;
} }
/* calendar, work items */ /* calendar, work items */

View file

@ -321,10 +321,32 @@ Micro Articles
>>> from loops.compound.microart.base import MicroArt >>> from loops.compound.microart.base import MicroArt
>>> from loops.compound.microart.interfaces import IMicroArt >>> from loops.compound.microart.interfaces import IMicroArt
>>> component.provideAdapter(BlogPost, provides=IMicroArt) >>> component.provideAdapter(MicroArt, provides=IMicroArt)
>>> tMicroArt = addAndConfigureObject(concepts, Concept, 'microart', >>> tMicroArt = addAndConfigureObject(concepts, Concept, 'microart',
... title=u'MicroArt', conceptType=tType) ... title=u'MicroArt', conceptType=tType,
... typeInterface=IMicroArt)
>>> ma01 = addAndConfigureObject(concepts, Concept, 'ma01',
... conceptType=tMicroArt,
... title=u'Organizational Knowledge',
... story=u'Systemic KM talks about organizational knowledge.',
... insight=u'Organizational knowledge is not visible.',
... consequences=u'Use examples. Look for strucure and rules. '
... u'Knowledge shows itself in actions.',
... followUps=u'What about collective intelligence? '
... u'How does an organization express itself?')
>>> ma01._insight
u'Organizational knowledge is not visible.'
>>> list(resources)
[..., u'ma01_story']
>>> adMa01 = adapted(ma01)
>>> adMa01.insight
u'Organizational knowledge is not visible.'
>>> adMa01.story
u'Systemic KM talks about organizational knowledge.'
Fin de partie Fin de partie

View file

@ -43,25 +43,26 @@ class MicroArt(Compound):
implements(IMicroArt) implements(IMicroArt)
_adapterAttributes = Compound._adapterAttributes + ('text',) _contextAttributes = list(IMicroArt)
_noexportAttributes = ('text',) _adapterAttributes = Compound._adapterAttributes + ('story',)
_textIndexAttributes = ('text',) _noexportAttributes = ('story',)
_textIndexAttributes = ('story', 'insight', 'consequences', 'folloUps')
defaultTextContentType = 'text/restructured' defaultTextContentType = 'text/html'
textContentType = defaultTextContentType textContentType = defaultTextContentType
def getText(self): def getStory(self):
res = self.getParts() res = self.getParts()
if len(res) > 0: if len(res) > 0:
return adapted(res[0]).data return adapted(res[0]).data
return u'' return u''
def setText(self, value): def setStory(self, value):
res = self.getParts() res = self.getParts()
if len(res) > 0: if len(res) > 0:
res = adapted(res[0]) res = adapted(res[0])
else: else:
tTextDocument = self.conceptManager['textdocument'] tTextDocument = self.conceptManager['textdocument']
name = getName(self.context) + '_text' name = getName(self.context) + '_story'
res = addAndConfigureObject(self.resourceManager, Resource, name, res = addAndConfigureObject(self.resourceManager, Resource, name,
title=self.title, contentType=self.defaultTextContentType, title=self.title, contentType=self.defaultTextContentType,
resourceType=tTextDocument) resourceType=tTextDocument)
@ -70,4 +71,4 @@ class MicroArt(Compound):
res = adapted(res) res = adapted(res)
res.data = value res.data = value
notify(ObjectModifiedEvent(res.context)) notify(ObjectModifiedEvent(res.context))
text = property(getText, setText) story = property(getStory, setStory)

View file

@ -26,7 +26,7 @@ from zope import component
from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
from loops.browser.concept import ConceptView, ConceptRelationView from loops.browser.concept import ConceptView
from loops.common import adapted from loops.common import adapted
from loops import util from loops import util
from loops.util import _ from loops.util import _
@ -37,16 +37,31 @@ view_macros = ViewPageTemplateFile('view_macros.pt')
class MicroArtView(ConceptView): class MicroArtView(ConceptView):
@Lazy
def contentType(self):
return 'text/restructured'
@Lazy
def macros(self):
return self.controller.getTemplateMacros('microart.view', view_macros)
@Lazy @Lazy
def macro(self): def macro(self):
return view_macros.macros['microart'] return self.macros['main']
def render(self): @Lazy
return self.renderText(self.data['text'], self.adapted.textContentType) def story(self):
return self.renderText(self.adapted.story, self.contentType)
def resources(self): @Lazy
stdPred = self.loopsRoot.getConceptManager().getDefaultPredicate() def insight(self):
rels = self.context.getResourceRelations([stdPred]) return self.renderText(self.adapted.insight, self.contentType)
for r in rels:
yield self.childViewFactory(r, self.request, contextIsSecond=True) @Lazy
def consequences(self):
return self.renderText(self.adapted.consequences, self.contentType)
@Lazy
def followUps(self):
return self.renderText(self.adapted.followUps, self.contentType)

View file

@ -27,6 +27,11 @@ from loops.compound.interfaces import ICompound
from loops.util import _ from loops.util import _
class HtmlField(schema.Text):
__typeInfo__ = ('html',)
class IMicroArt(ICompound): class IMicroArt(ICompound):
""" A short article with a few elements, for collecting """ A short article with a few elements, for collecting
relevant information in a knowledge management environment. relevant information in a knowledge management environment.
@ -34,6 +39,7 @@ class IMicroArt(ICompound):
# title = Ueberschrift, Thema # title = Ueberschrift, Thema
#story = HtmlField( # Geschichte
story = schema.Text( # Geschichte story = schema.Text( # Geschichte
title=_(u'Story'), title=_(u'Story'),
description=_(u'The story, i.e. the main text of your ' description=_(u'The story, i.e. the main text of your '
@ -54,7 +60,7 @@ class IMicroArt(ICompound):
followUps = schema.Text( #Anschlussfragen followUps = schema.Text( #Anschlussfragen
title=_(u'Follow-up Questions'), title=_(u'Follow-up Questions'),
description=_(u'Question for helping to solve or avoid ' description=_(u'Questions for helping to solve or avoid '
u'similar problems in the future.'), u'similar problems in the future.'),
required=False) required=False)

View file

@ -1,6 +1,8 @@
<!-- ZPT macros for loops.compound.microart views --> <!-- ZPT macros for loops.compound.microart views -->
<html i18n:domain="loops">
<div metal:define-macro="microart"
<div metal:define-macro="main"
tal:define="data item/data" tal:define="data item/data"
class="microart"> class="microart">
<metal:block use-macro="view/concept_macros/concepttitle_only" /> <metal:block use-macro="view/concept_macros/concepttitle_only" />
@ -9,9 +11,17 @@
tal:condition="description"> tal:condition="description">
<span tal:content="structure description">Description</span> <span tal:content="structure description">Description</span>
</div> </div>
<div class="text" <div class="text">
tal:content="structure item/render">Here comes the text...</div> <div class="span-6 last"
<metal:resources use-macro="view/concept_macros/conceptchildren" /> tal:content="structure item/story" />
<metal:resources use-macro="view/concept_macros/conceptresources" /> <div class="span-2"
<metal:block use-macro="view/comment_macros/comments" /> tal:content="structure item/insight" />
<div class="span-2"
tal:content="structure item/consequences" />
<div class="span-2 last"
tal:content="structure item/followUps" /></div><br clear="both" />
<metal:block use-macro="view/comment_macros/comments" />
</div> </div>
</html>

View file

@ -73,8 +73,7 @@ class ResultsView(NodeView):
@Lazy @Lazy
def params(self): def params(self):
params = dict(self.request.form) params = dict(self.request.form)
if 'report_execute' in params: params.pop('report_execute', None)
del params['report_execute']
return params return params
@Lazy @Lazy
@ -85,7 +84,7 @@ class ResultsView(NodeView):
def reportInstance(self): def reportInstance(self):
instance = component.getAdapter(self.report, IReportInstance, instance = component.getAdapter(self.report, IReportInstance,
name=self.report.reportType) name=self.report.reportType)
instance.request = self.request instance.view = self
return instance return instance
#@Lazy #@Lazy
@ -105,3 +104,5 @@ class ResultsView(NodeView):
def displayedColumns(self): def displayedColumns(self):
return self.reportInstance.getActiveOutputFields() return self.reportInstance.getActiveOutputFields()
def getColumnRenderer(self, name):
return self.result_macros[name]

View file

@ -15,8 +15,44 @@
<div metal:define-macro="results"> <div metal:define-macro="results">
Default Results Listing <table tal:define="results view/results">
<tr>
<th tal:repeat="col view/displayedColumns"
tal:content="col/title"
i18n:translate="" />
</tr>
<tr tal:repeat="row results">
<tal:column repeat="col view/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 view/displayedColumns">
<metal:column use-macro="python:view.getColumnRenderer(col.renderer)" />
</tal:column>
</tr>
</table>
</div> </div>
<metal:standard define-macro="standard">
<td tal:content="structure python:col.getDisplayValue(row)" />
</metal:standard>
<metal:right define-macro="right">
<td style="text-align: right"
tal:content="python:col.getDisplayValue(row)" />
</metal:right>
<metal:target define-macro="target">
<td tal:define="value python:col.getDisplayValue(row)">
<a tal:omit-tag="not:value/url"
tal:attributes="href value/url"
tal:content="value/title" /></td>
</metal:target>
</html> </html>

View file

@ -22,9 +22,9 @@
provides="loops.expert.report.IReport" trusted="True" /> provides="loops.expert.report.IReport" trusted="True" />
<class class="loops.expert.report.Report"> <class class="loops.expert.report.Report">
<require permission="zope.View" <require permission="zope.View"
interface="loops.expert.report.IReportSchema" /> interface="loops.expert.report.IReport" />
<require permission="zope.ManageContent" <require permission="zope.ManageContent"
set_schema="loops.expert.report.IReportSchema" /> set_schema="loops.expert.report.IReport" />
</class> </class>
<adapter factory="loops.expert.report.DefaultConceptReportInstance" <adapter factory="loops.expert.report.DefaultConceptReportInstance"

52
expert/field.py Normal file
View file

@ -0,0 +1,52 @@
#
# Copyright (c) 2011 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
#
"""
Field definitions for reports.
"""
from cybertools.composer.report.field import Field
from loops import util
class UrlField(Field):
renderer = 'target'
def getDisplayValue(self, row):
nv = row.parent.context.view.nodeView
return dict(title=self.getValue(row), url=nv.getUrlForTarget(row.context))
class TargetField(Field):
renderer = 'target'
def getValue(self, row):
value = self.getRawValue(row)
if value is None:
return None
return util.getObjectForUid(value)
def getDisplayValue(self, row):
value = self.getValue(row)
if value is None:
return dict(title=u'', url=u'')
view = row.parent.context.view
return dict(title=value.title, url=view.getUrlForTarget(value))

View file

@ -29,6 +29,7 @@ from zope.security.proxy import removeSecurityProxy
from cybertools.composer.report.base import Report as BaseReport from cybertools.composer.report.base import Report as BaseReport
from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria
from cybertools.composer.report.interfaces import IReport as IBaseReport from cybertools.composer.report.interfaces import IReport as IBaseReport
from cybertools.composer.report.interfaces import IReportParams
from cybertools.composer.report.result import ResultSet, Row from cybertools.composer.report.result import ResultSet, Row
from cybertools.util.jeep import Jeep from cybertools.util.jeep import Jeep
from loops.common import AdapterBase from loops.common import AdapterBase
@ -40,7 +41,7 @@ from loops.util import _
# interfaces # interfaces
class IReport(ILoopsAdapter): class IReport(ILoopsAdapter, IReportParams):
""" The report adapter for the persistent object (concept) that stores """ The report adapter for the persistent object (concept) that stores
the report in the concept map. the report in the concept map.
""" """
@ -53,19 +54,11 @@ class IReport(ILoopsAdapter):
required=True) required=True)
class IReportSchema(IBaseReport, IReport): class IReportInstance(IBaseReport):
""" All report attributes - use for security declarations.
"""
class IReportInstance(Interface):
""" The report-type-specific object (an adapter on the report) that """ The report-type-specific object (an adapter on the report) that
does the real report execution stuff. does the real report execution stuff.
""" """
label = Attribute('The user-friendly label of the report type specified '
'by this instance class.')
# report concept adapter and instances # report concept adapter and instances
@ -85,19 +78,26 @@ class ReportInstance(BaseReport):
rowFactory = Row rowFactory = Row
view = None # set upon creation
def __init__(self, context): def __init__(self, context):
self.context = context self.context = context
def getResultsRenderer(self, name, macros): def getResultsRenderer(self, name, macros):
return macros[name] return macros[name]
@property
def queryCriteria(self):
return self.context.queryCriteria
def getResults(self, dynaParams=None): def getResults(self, dynaParams=None):
crit = self.queryCriteria crit = self.queryCriteria
if crit is None: if crit is None:
return [] return []
for k, v in dynaParams.items(): if dynaParams is not None:
if k in crit.parts.keys(): for k, v in dynaParams.items():
crit.parts[k].value = v if k in crit.parts.keys():
crit.parts[k].value = v
parts = Jeep(crit.parts) parts = Jeep(crit.parts)
result = list(self.selectObjects(parts)) # may modify parts result = list(self.selectObjects(parts)) # may modify parts
qc = CompoundQueryCriteria(parts) qc = CompoundQueryCriteria(parts)
@ -105,8 +105,21 @@ class ReportInstance(BaseReport):
sortCriteria=self.getSortCriteria(), queryCriteria=qc) sortCriteria=self.getSortCriteria(), queryCriteria=qc)
def selectObjects(self, parts): def selectObjects(self, parts):
# to be implemented by subclass
return [] return []
@Lazy
def conceptManager(self):
return self.view.conceptManager
@Lazy
def recordManager(self):
return self.view.loopsRoot.getRecordManager()
@Lazy
def hasReportPredicate(self):
return self.conceptManager['hasreport']
class ReportTypeSourceList(object): class ReportTypeSourceList(object):

Binary file not shown.

View file

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: $Id$\n" "Project-Id-Version: $Id$\n"
"POT-Creation-Date: 2007-05-22 12:00 CET\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n"
"PO-Revision-Date: 2011-10-31 12:00 CET\n" "PO-Revision-Date: 2011-12-02 12:00 CET\n"
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n" "Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
"Language-Team: loops developers <helmutm@cy55.de>\n" "Language-Team: loops developers <helmutm@cy55.de>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -11,6 +11,9 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: kwrite\n" "Generated-By: kwrite\n"
msgid "You are here:"
msgstr "Sie sind hier:"
msgid "Concept" msgid "Concept"
msgstr "Begriff" msgstr "Begriff"
@ -80,6 +83,8 @@ msgstr "Thema bearbeiten..."
msgid "Modify topic." msgid "Modify topic."
msgstr "Thema ändern" msgstr "Thema ändern"
# blog
msgid "Edit Blog Post..." msgid "Edit Blog Post..."
msgstr "Eintrag bearbeiten..." msgstr "Eintrag bearbeiten..."
@ -101,6 +106,34 @@ msgstr "Tagebucheintrag anlegen"
msgid "Export Blog" msgid "Export Blog"
msgstr "Tagebuch exportieren" msgstr "Tagebuch exportieren"
# micro article
msgid "Story"
msgstr "Story"
msgid "The story, i.e. the main text of your micro article. Who did what? What happend?"
msgstr "Die Geschichte, der Haupttext Ihres MikroArtikels. Wer hat was getan? Was geschah?"
msgid "Insight"
msgstr "Einsicht"
msgid "What can we learn from the story? What has gone wrong? What was good?"
msgstr "Was können wir aus der Geschichte lernen? Was ist schiefgegangen? Was war gut?"
msgid "Consequences"
msgstr "Folgerungen"
msgid "What we will do next time in a similar situation?"
msgstr "Was werden wir das nächste Mal in einer ähnlichen Situation tun?"
msgid "Follow-up Questions"
msgstr "Anschlussfragen"
msgid "Questions for helping to solve or avoid similar problems in the future."
msgstr "Fragen, die einem dabei helfen können, das Problem in der Zukunft zu lösen oder zu vermeiden."
# glossary
msgid "Glossary Item" msgid "Glossary Item"
msgstr "Glossareintrag" msgstr "Glossareintrag"

View file

@ -183,12 +183,57 @@ So we use the PersonWorkItems view, assigning john to the query.
Work Reports Work Reports
============ ============
First we have to make sure that there is a report concept type available.
In addition we need a predicate that connects one or more tasks with a report.
>>> from loops.expert.report import IReport, Report
>>> component.provideAdapter(Report)
>>> tReport = addAndConfigureObject(concepts, Concept, 'report',
... title=u'Report', conceptType=concepts.getTypeConcept(),
... typeInterface=IReport)
>>> hasReport = addAndConfigureObject(concepts, Concept, 'hasreport',
... title=u'has Report', conceptType=concepts.getPredicateType())
Now we can create a report and register it as the report for the task
used above.
>>> workStatement = addAndConfigureObject(concepts, Concept, 'work_statement',
... title=u'Work Statement', conceptType=tReport,
... reportType='work_report')
>>> workStatement.assignChild(task01, hasReport)
The executable report is a report instance that is an adapter to the
(adapted) report instance.
>>> from loops.organize.work.report import WorkReportInstance >>> from loops.organize.work.report import WorkReportInstance
>>> from loops.expert.report import IReportInstance >>> from loops.expert.report import IReportInstance
>>> component.provideAdapter(WorkReportInstance, >>> component.provideAdapter(WorkReportInstance,
... provides=IReportInstance, ... provides=IReportInstance,
... name='work_report') ... name='work_report')
The user interface to the report is a report view, the results are presented
in a results view.
>>> from loops.view import Node
>>> reportNode = addAndConfigureObject(home, Node, 'report',
... title=u'Report', target=workStatement)
>>> from loops.expert.browser.report import ReportView, ResultsView
>>> resultsView = ResultsView(reportNode, TestRequest())
>>> results = resultsView.results()#.getResult()
>>> len(list(results))
1
>>> for row in results:
... for col in resultsView.displayedColumns:
... print col.getDisplayValue(row),
... print
08/12/28 19:00 20:15
{'url': '.../home/report/.36', 'title': u'loops Development'}
{'url': '.../home/report/.33', 'title': u'john'} 01:15 00:15 finished
>>> results.totals.data
{'effort': 900}
Fin de partie Fin de partie
============= =============

View file

@ -28,59 +28,192 @@ from cybertools.composer.report.base import Report
from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria
from cybertools.composer.report.field import Field from cybertools.composer.report.field import Field
from cybertools.composer.report.result import ResultSet, Row as BaseRow from cybertools.composer.report.result import ResultSet, Row as BaseRow
from cybertools.organize.interfaces import IWorkItems
from cybertools.util.date import timeStamp2Date
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.expert.field import TargetField
from loops.expert.report import ReportInstance from loops.expert.report import ReportInstance
from loops import util
results_template = ViewPageTemplateFile('results.pt')
task = Field('task', u'Task', class DateField(Field):
description=u'The task to which work items belong.',
executionSteps=['query', 'output', 'sort']) part = 'date'
work = Field('work', u'Work', format = 'short'
description=u'The short description of the work.', renderer = 'right'
executionSteps=['output'])
workDescription = Field('workDescription', u'Work Description', def getValue(self, row):
description=u'The long description of the work.', value = self.getRawValue(row)
executionSteps=['output']) if value is None:
day = Field('day', u'Day', return None
description=u'The day the work was done.', return timeStamp2Date(value)
executionSteps=['output', 'sort'])
def getDisplayValue(self, row):
value = self.getValue(row)
if value:
view = row.parent.context.view
return formatDate(value, self.part, self.format,
view.languageInfo.language)
return u''
class TimeField(DateField):
part = 'time'
class DurationField(Field):
renderer = 'right'
def getValue(self, row):
value = self.getRawValue(row)
if value and 'totals' in self.executionSteps:
data = row.parent.totals.data
data[self.name] = data.get(self.name, 0) + value
if value:
value /= 3600.0
return value
def getDisplayValue(self, row):
value = self.getValue(row)
if not value:
return u''
return u'%02i:%02i' % divmod(value * 60, 60)
tasks = Field('tasks', u'Tasks',
description=u'The tasks from which work items should be selected.',
executionSteps=['query'])
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'])
dayTo = Field('dayTo', u'End Day', dayTo = Field('dayTo', u'End Day',
description=u'The last day until which to select work.', description=u'The last day until which to select work.',
executionSteps=['query']) executionSteps=['query'])
day = DateField('day', u'Day',
description=u'The day the work was done.',
executionSteps=['sort', 'output'])
timeStart = TimeField('start', u'Start',
description=u'The time the unit of work was started.',
executionSteps=['sort', 'output'])
timeEnd = TimeField('end', u'End',
description=u'The time the unit of work was finished.',
executionSteps=['output'])
task = TargetField('taskId', u'Task',
description=u'The task to which work items belong.',
executionSteps=['output'])
party = TargetField('userName', u'Party',
description=u'The party (usually a person) who did the work.',
executionSteps=['query', 'sort', 'output'])
title = Field('title', u'Title',
description=u'The short description of the work.',
executionSteps=['output'])
description = Field('description', u'Description',
description=u'The long description of the work.',
executionSteps=['x_output'])
duration = DurationField('duration', u'Duration',
description=u'The duration of the work.',
executionSteps=['output'])
effort = DurationField('effort', u'Effort',
description=u'The effort of the work.',
executionSteps=['output', 'totals'])
state = Field('state', u'State',
description=u'The state of the work.',
executionSteps=['query', 'output'])
class WorkRow(BaseRow): class WorkRow(BaseRow):
pass def getRawValue(self, attr):
if attr in self.attributeHandlers:
return self.attributeHandlers[attr](self, attr)
track = self.context
if attr in track.metadata_attributes:
return getattr(track, attr)
return track.data.get(attr, u'')
def getDay(self, attr):
return self.context.timeStamp
def getDuration(self, attr):
value = self.context.data.get('duration')
if value is None:
value = self.getRawValue('end') - self.getRawValue('start')
return value
def getEffort(self, attr):
value = self.context.data.get('effort')
if value is None:
value = self.getDuration(attr)
return value
attributeHandlers = dict(day=getDay, duration=getDuration, effort=getEffort)
class WorkReportInstance(ReportInstance): class WorkReportInstance(ReportInstance):
type = "deliverables" type = "work_statement"
label = u'Work Report' label = u'Work Statement'
rowFactory = WorkRow rowFactory = WorkRow
fields = Jeep((day, dayFrom, dayTo, task, work, workDescription))
defaultOutputFields = fields
@Lazy fields = Jeep((dayFrom, dayTo, tasks,
day, timeStart, timeEnd, task, party, title, description,
duration, effort, state))
defaultOutputFields = fields
defaultSortCriteria = (day, timeStart,)
@property
def queryCriteria(self): def queryCriteria(self):
# TODO: take from persistent report where appropriate form = self.view.request.form
crit = [LeafQueryCriteria(f.name, f.operator, None, f) crit = self.context.queryCriteria or []
for f in self.getAllQueryFields()] if not crit and 'tasks' not in form:
f = self.fields['tasks']
tasks = baseObject(self.context).getChildren([self.hasReportPredicate])
tasks = [util.getUidForObject(task) for task in tasks]
crit = [LeafQueryCriteria(f.name, f.operator, tasks, f)]
for f in self.getAllQueryFields():
if f.name in form:
crit.append(LeafQueryCriteria(f.name, f.operator, form[f.name], f))
return CompoundQueryCriteria(crit) return CompoundQueryCriteria(crit)
def getResultsRenderer(self, name, defaultMacros):
return results_template.macros[name]
def selectObjects(self, parts): def selectObjects(self, parts):
task = parts.get('task') result = []
if not task: tasks = [util.getObjectForUid(t) for t in parts.pop('tasks').comparisonValue]
return [] for t in list(tasks):
return [] tasks.extend(self.getAllSubtasks(t))
for t in tasks:
result.extend(self.selectWorkItems(t, parts))
# remove parts already used for selection from parts list:
parts.pop('userName', None)
return result
def selectWorkItems(self, task, parts):
states = ['done', 'done_x', 'finished']
kw = dict(task=util.getUidForObject(task), state=states)
if 'userName' in parts:
kw['userName'] = parts['userName'].comparisonValue
wi = self.workItems
return wi.query(**kw)
def getAllSubtasks(self, concept):
result = []
for c in concept.getChildren():
if c.conceptType in self.taskTypes:
result.append(c)
result.extend(self.getAllSubtasks(c))
return result
@Lazy
def taskTypes(self):
return (self.conceptManager['task'],
self.conceptManager['event'],
self.conceptManager['project'])
@Lazy
def workItems(self):
return IWorkItems(self.recordManager['work'])

View file

@ -1,61 +0,0 @@
<html i18n:domain="loops">
<div metal:define-macro="results">
<h2 i18n:translate="">Work Items</h2>
<table>
<tr>
<th tal:repeat="col view/displayedColumns"
tal:content="col/title"
i18n:translate="" />
</tr>
<tr tal:repeat="row view/results">
<tal:column repeat="col view/displayedColumns">
<metal:column use-macro="python: view.getColumnRenderer(col.renderer)" />
</tal:column>
</tr>
</table>
</div>
<div metal:define-macro="xx_results">
Work Items Listing
<table class="listing">
<tr>
<tal:colheader repeat="column work/allColumns">
<th tal:condition="python: column in work.columns"
tal:content="column"
i18n:translate="">Task</th>
</tal:colheader>
</tr>
<tal:workitem tal:repeat="row work/listWorkItems">
<tr tal:condition="row/monthChanged">
<td class="headline"
tal:attributes="colspan python: len(work.columns)"
tal:content="row/month">2009-01</td></tr>
<tr tal:attributes="class python:
(repeat['row'].odd() and 'even' or 'odd')">
<td class="nowrap center"
tal:define="today python: row.isToday and ' today' or ''"
tal:attributes="title row/weekDay;
class string:nowrap center$today"
i18n:attributes="title"
tal:content="row/day">2007-03-30</td>
<td class="nowrap center" tal:content="row/start">17:30</td>
<td class="nowrap center" tal:content="row/end">20:00</td>
<td class="nowrap center" tal:content="row/duration">2:30</td>
<td tal:condition="python: 'Task' in work.columns">
<a tal:attributes="href row/objectData/url"
tal:content="row/objectData/title">Task</a></td>
<td tal:condition="python: 'User' in work.columns">
<a tal:attributes="href row/user/url"
tal:content="row/user/title">John</a></td>
<td tal:content="row/track/title"
tal:attributes="title row/descriptionPlain">Title</td>
</tr>
</tal:workitem>
</table>
</div>
</html>