merge branch master
This commit is contained in:
		
						commit
						5f95ef439e
					
				
					 22 changed files with 496 additions and 148 deletions
				
			
		
							
								
								
									
										17
									
								
								README.txt
									
										
									
									
									
								
							
							
						
						
									
										17
									
								
								README.txt
									
										
									
									
									
								
							|  | @ -649,6 +649,23 @@ to the bottom, and to the top. | |||
|   ['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 | ||||
| ================================ | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,8 +18,6 @@ | |||
| 
 | ||||
| """ | ||||
| Common base class for loops browser view classes. | ||||
| 
 | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from cgi import parse_qs, parse_qsl | ||||
|  | @ -146,6 +144,9 @@ class BaseView(GenericView, I18NView): | |||
|         return self.controller.getTemplateMacros('resource', resource_macros) | ||||
|         #return resource_macros.macros | ||||
| 
 | ||||
|     def breadcrumbs(self): | ||||
|         return [] | ||||
| 
 | ||||
|     @Lazy | ||||
|     def name(self): | ||||
|         return getName(self.context) | ||||
|  |  | |||
|  | @ -139,7 +139,7 @@ class Layout(Base, ConceptView): | |||
|             parts = (self.options('parts') or | ||||
|                      self.typeOptions('parts') or | ||||
|                      ['h1', 'g3']) | ||||
|         ti = adapted(self.context.conceptType).typeInterface | ||||
|         #ti = adapted(self.context.conceptType).typeInterface | ||||
|         for p in parts: | ||||
|             viewName = 'lobo_' + p | ||||
|             view = component.queryMultiAdapter((self.context, self.request), | ||||
|  |  | |||
|  | @ -455,7 +455,6 @@ div.comment { | |||
| } | ||||
| 
 | ||||
| .blogpost .description { | ||||
|     font-weight: bold; | ||||
|     font-size: 90%; | ||||
|     color: #666666; | ||||
|     padding-top: 0.4em; | ||||
|  |  | |||
|  | @ -91,6 +91,10 @@ class NodeView(BaseView): | |||
|         self.recordAccess() | ||||
|         return result | ||||
| 
 | ||||
|     @Lazy | ||||
|     def title(self): | ||||
|         return self.context.title or getName(self.context) | ||||
| 
 | ||||
|     def breadcrumbs(self): | ||||
|         if not self.globalOptions('showBreadcrumbs'): | ||||
|             return [] | ||||
|  | @ -100,7 +104,13 @@ class NodeView(BaseView): | |||
|         if menuItem != menu.context: | ||||
|             data.append(dict(label=menuItem.title, | ||||
|                              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=''): | ||||
|         target = self.virtualTargetObject | ||||
|  |  | |||
|  | @ -29,10 +29,11 @@ | |||
|       <metal:breadcrumbs define-slot="breadcrumbs"> | ||||
|         <div tal:define="crumbs view/breadcrumbs" | ||||
|              tal:condition="crumbs"> | ||||
|           <span i18n:translate="">You are here:</span> | ||||
|           <span tal:repeat="crumb crumbs"> | ||||
|             <a tal:attributes="href crumb/url" | ||||
|                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:define-slot="actions"></div> | ||||
|       <div metal:define-slot="message"></div> | ||||
|  | @ -63,7 +64,7 @@ | |||
|           tal:attributes="href string:${view/topMenu/url}/impressum">Impressum</a>) | ||||
|         <br /> | ||||
|         Powered by | ||||
|         <b><a href="http://loops.cy55.de">loops</a></b> · | ||||
|         <b><a href="http://www.wissen-statt-suchen.de">loops</a></b> · | ||||
|         <b><a href="http://wiki.zope.org/zope3">Zope 3</a></b> · | ||||
|         <b><a href="http://www.python.org">Python</a></b> · | ||||
|         <b><a href="http://www.dojotoolkit.org">Dojo</a></b>. | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
| /* general */ | ||||
| 
 | ||||
| h1, h2, h3, h4, h5, h6 { | ||||
|     margin-bottom: 0.5em; | ||||
|     margin-bottom: 0.4em; | ||||
| } | ||||
| 
 | ||||
| a[href]:hover { | ||||
|  | @ -472,23 +472,37 @@ div.comment { | |||
| 
 | ||||
| /* blog */ | ||||
| 
 | ||||
| .blog h1.headline { | ||||
|     clear: both; | ||||
|     padding-top: 1em; | ||||
|     margin-bottom: 0.2em; | ||||
|     font-size: 150%; | ||||
| } | ||||
| 
 | ||||
| .blog .description { | ||||
|     font-size: 90%; | ||||
|     color: #666666; | ||||
| } | ||||
| 
 | ||||
| .blog .text { | ||||
|     font-size: 90%; | ||||
|     margin-top: 0.5em; | ||||
| } | ||||
| 
 | ||||
| .blog img { | ||||
|     padding-top: 0.2em; | ||||
| } | ||||
| 
 | ||||
| .blogpost .description { | ||||
|     font-weight: bold; | ||||
|     font-size: 90%; | ||||
|     color: #666666; | ||||
|     padding-top: 0.4em; | ||||
|     margin-top: 1em; | ||||
| } | ||||
| 
 | ||||
| .blog .info, .blogpost .info { | ||||
|     font-style: italic; | ||||
|     font-size: 90%; | ||||
|     color: #666666; | ||||
|     padding-top: 0.4em; | ||||
| } | ||||
| 
 | ||||
| /* calendar, work items */ | ||||
|  |  | |||
|  | @ -321,10 +321,32 @@ Micro Articles | |||
| 
 | ||||
|   >>> from loops.compound.microart.base import MicroArt | ||||
|   >>> from loops.compound.microart.interfaces import IMicroArt | ||||
|   >>> component.provideAdapter(BlogPost, provides=IMicroArt) | ||||
|   >>> component.provideAdapter(MicroArt, provides=IMicroArt) | ||||
| 
 | ||||
|   >>> 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 | ||||
|  |  | |||
|  | @ -43,25 +43,26 @@ class MicroArt(Compound): | |||
| 
 | ||||
|     implements(IMicroArt) | ||||
| 
 | ||||
|     _adapterAttributes = Compound._adapterAttributes + ('text',) | ||||
|     _noexportAttributes = ('text',) | ||||
|     _textIndexAttributes = ('text',) | ||||
|     _contextAttributes = list(IMicroArt) | ||||
|     _adapterAttributes = Compound._adapterAttributes + ('story',) | ||||
|     _noexportAttributes = ('story',) | ||||
|     _textIndexAttributes = ('story', 'insight', 'consequences', 'folloUps') | ||||
| 
 | ||||
|     defaultTextContentType = 'text/restructured' | ||||
|     defaultTextContentType = 'text/html' | ||||
|     textContentType = defaultTextContentType | ||||
| 
 | ||||
|     def getText(self): | ||||
|     def getStory(self): | ||||
|         res = self.getParts() | ||||
|         if len(res) > 0: | ||||
|             return adapted(res[0]).data | ||||
|         return u'' | ||||
|     def setText(self, value): | ||||
|     def setStory(self, value): | ||||
|         res = self.getParts() | ||||
|         if len(res) > 0: | ||||
|             res = adapted(res[0]) | ||||
|         else: | ||||
|             tTextDocument = self.conceptManager['textdocument'] | ||||
|             name = getName(self.context) + '_text' | ||||
|             name = getName(self.context) + '_story' | ||||
|             res = addAndConfigureObject(self.resourceManager, Resource, name, | ||||
|                     title=self.title, contentType=self.defaultTextContentType, | ||||
|                     resourceType=tTextDocument) | ||||
|  | @ -70,4 +71,4 @@ class MicroArt(Compound): | |||
|             res = adapted(res) | ||||
|         res.data = value | ||||
|         notify(ObjectModifiedEvent(res.context)) | ||||
|     text = property(getText, setText) | ||||
|     story = property(getStory, setStory) | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ from zope import component | |||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| 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 import util | ||||
| from loops.util import _ | ||||
|  | @ -37,16 +37,31 @@ view_macros = ViewPageTemplateFile('view_macros.pt') | |||
| 
 | ||||
| class MicroArtView(ConceptView): | ||||
| 
 | ||||
|     @Lazy | ||||
|     def contentType(self): | ||||
|         return 'text/restructured' | ||||
| 
 | ||||
|     @Lazy | ||||
|     def macros(self): | ||||
|         return self.controller.getTemplateMacros('microart.view', view_macros) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def macro(self): | ||||
|         return view_macros.macros['microart'] | ||||
|         return self.macros['main'] | ||||
| 
 | ||||
|     def render(self): | ||||
|         return self.renderText(self.data['text'], self.adapted.textContentType) | ||||
|     @Lazy | ||||
|     def story(self): | ||||
|         return self.renderText(self.adapted.story, self.contentType) | ||||
| 
 | ||||
|     def resources(self): | ||||
|         stdPred = self.loopsRoot.getConceptManager().getDefaultPredicate() | ||||
|         rels = self.context.getResourceRelations([stdPred]) | ||||
|         for r in rels: | ||||
|             yield self.childViewFactory(r, self.request, contextIsSecond=True) | ||||
|     @Lazy | ||||
|     def insight(self): | ||||
|         return self.renderText(self.adapted.insight, self.contentType) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def consequences(self): | ||||
|         return self.renderText(self.adapted.consequences, self.contentType) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def followUps(self): | ||||
|         return self.renderText(self.adapted.followUps, self.contentType) | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,6 +27,11 @@ from loops.compound.interfaces import ICompound | |||
| from loops.util import _ | ||||
| 
 | ||||
| 
 | ||||
| class HtmlField(schema.Text): | ||||
| 
 | ||||
|     __typeInfo__ = ('html',) | ||||
| 
 | ||||
| 
 | ||||
| class IMicroArt(ICompound): | ||||
|     """ A short article with a few elements, for collecting | ||||
|         relevant information in a knowledge management environment. | ||||
|  | @ -34,6 +39,7 @@ class IMicroArt(ICompound): | |||
| 
 | ||||
|                         # title = Ueberschrift, Thema | ||||
| 
 | ||||
|     #story = HtmlField(        # Geschichte | ||||
|     story = schema.Text(        # Geschichte | ||||
|                 title=_(u'Story'), | ||||
|                 description=_(u'The story, i.e. the main text of your ' | ||||
|  | @ -54,7 +60,7 @@ class IMicroArt(ICompound): | |||
| 
 | ||||
|     followUps = schema.Text(    #Anschlussfragen | ||||
|                 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.'), | ||||
|                 required=False) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,8 @@ | |||
| <!-- 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" | ||||
|      class="microart"> | ||||
|   <metal:block use-macro="view/concept_macros/concepttitle_only" /> | ||||
|  | @ -9,9 +11,17 @@ | |||
|        tal:condition="description"> | ||||
|     <span tal:content="structure description">Description</span> | ||||
|   </div> | ||||
|   <div class="text" | ||||
|        tal:content="structure item/render">Here comes the text...</div> | ||||
|   <metal:resources use-macro="view/concept_macros/conceptchildren" /> | ||||
|   <metal:resources use-macro="view/concept_macros/conceptresources" /> | ||||
|   <div class="text"> | ||||
|     <div class="span-6 last" | ||||
|           tal:content="structure item/story" /> | ||||
|     <div class="span-2" | ||||
|          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> | ||||
| 
 | ||||
| 
 | ||||
| </html> | ||||
|  | @ -73,8 +73,7 @@ class ResultsView(NodeView): | |||
|     @Lazy | ||||
|     def params(self): | ||||
|         params = dict(self.request.form) | ||||
|         if 'report_execute' in params: | ||||
|             del params['report_execute'] | ||||
|         params.pop('report_execute', None) | ||||
|         return params | ||||
| 
 | ||||
|     @Lazy | ||||
|  | @ -85,7 +84,7 @@ class ResultsView(NodeView): | |||
|     def reportInstance(self): | ||||
|         instance = component.getAdapter(self.report, IReportInstance, | ||||
|                                         name=self.report.reportType) | ||||
|         instance.request = self.request | ||||
|         instance.view = self | ||||
|         return instance | ||||
| 
 | ||||
|     #@Lazy | ||||
|  | @ -105,3 +104,5 @@ class ResultsView(NodeView): | |||
|     def displayedColumns(self): | ||||
|         return self.reportInstance.getActiveOutputFields() | ||||
| 
 | ||||
|     def getColumnRenderer(self, name): | ||||
|         return self.result_macros[name] | ||||
|  |  | |||
|  | @ -15,8 +15,44 @@ | |||
| 
 | ||||
| 
 | ||||
| <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> | ||||
| 
 | ||||
| 
 | ||||
| <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> | ||||
|  |  | |||
|  | @ -22,9 +22,9 @@ | |||
|            provides="loops.expert.report.IReport" trusted="True" /> | ||||
|   <class class="loops.expert.report.Report"> | ||||
|     <require permission="zope.View" | ||||
|              interface="loops.expert.report.IReportSchema" /> | ||||
|              interface="loops.expert.report.IReport" /> | ||||
|     <require permission="zope.ManageContent" | ||||
|              set_schema="loops.expert.report.IReportSchema" /> | ||||
|              set_schema="loops.expert.report.IReport" /> | ||||
|   </class> | ||||
| 
 | ||||
|   <adapter factory="loops.expert.report.DefaultConceptReportInstance" | ||||
|  |  | |||
							
								
								
									
										52
									
								
								expert/field.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								expert/field.py
									
										
									
									
									
										Normal 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)) | ||||
| 
 | ||||
|  | @ -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 LeafQueryCriteria, CompoundQueryCriteria | ||||
| 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.util.jeep import Jeep | ||||
| from loops.common import AdapterBase | ||||
|  | @ -40,7 +41,7 @@ from loops.util import _ | |||
| 
 | ||||
| # interfaces | ||||
| 
 | ||||
| class IReport(ILoopsAdapter): | ||||
| class IReport(ILoopsAdapter, IReportParams): | ||||
|     """ The report adapter for the persistent object (concept) that stores | ||||
|         the report in the concept map. | ||||
|     """ | ||||
|  | @ -53,19 +54,11 @@ class IReport(ILoopsAdapter): | |||
|         required=True) | ||||
| 
 | ||||
| 
 | ||||
| class IReportSchema(IBaseReport, IReport): | ||||
|     """ All report attributes - use for security declarations. | ||||
|     """ | ||||
| 
 | ||||
| 
 | ||||
| class IReportInstance(Interface): | ||||
| class IReportInstance(IBaseReport): | ||||
|     """ The report-type-specific object (an adapter on the report) that | ||||
|         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 | ||||
| 
 | ||||
|  | @ -85,16 +78,23 @@ class ReportInstance(BaseReport): | |||
| 
 | ||||
|     rowFactory = Row | ||||
| 
 | ||||
|     view = None     # set upon creation | ||||
| 
 | ||||
|     def __init__(self, context): | ||||
|         self.context = context | ||||
| 
 | ||||
|     def getResultsRenderer(self, name, macros): | ||||
|         return macros[name] | ||||
| 
 | ||||
|     @property | ||||
|     def queryCriteria(self): | ||||
|         return self.context.queryCriteria | ||||
| 
 | ||||
|     def getResults(self, dynaParams=None): | ||||
|         crit = self.queryCriteria | ||||
|         if crit is None: | ||||
|             return [] | ||||
|         if dynaParams is not None: | ||||
|             for k, v in dynaParams.items(): | ||||
|                 if k in crit.parts.keys(): | ||||
|                     crit.parts[k].value = v | ||||
|  | @ -105,8 +105,21 @@ class ReportInstance(BaseReport): | |||
|                          sortCriteria=self.getSortCriteria(), queryCriteria=qc) | ||||
| 
 | ||||
|     def selectObjects(self, parts): | ||||
|         # to be implemented by subclass | ||||
|         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): | ||||
| 
 | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							|  | @ -3,7 +3,7 @@ msgstr "" | |||
| 
 | ||||
| "Project-Id-Version: $Id$\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" | ||||
| "Language-Team: loops developers <helmutm@cy55.de>\n" | ||||
| "MIME-Version: 1.0\n" | ||||
|  | @ -11,6 +11,9 @@ msgstr "" | |||
| "Content-Transfer-Encoding: 8bit\n" | ||||
| "Generated-By: kwrite\n" | ||||
| 
 | ||||
| msgid "You are here:" | ||||
| msgstr "Sie sind hier:" | ||||
| 
 | ||||
| msgid "Concept" | ||||
| msgstr "Begriff" | ||||
| 
 | ||||
|  | @ -80,6 +83,8 @@ msgstr "Thema bearbeiten..." | |||
| msgid "Modify topic." | ||||
| msgstr "Thema ändern" | ||||
| 
 | ||||
| # blog | ||||
| 
 | ||||
| msgid "Edit Blog Post..." | ||||
| msgstr "Eintrag bearbeiten..." | ||||
| 
 | ||||
|  | @ -101,6 +106,34 @@ msgstr "Tagebucheintrag anlegen" | |||
| msgid "Export Blog" | ||||
| 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" | ||||
| msgstr "Glossareintrag" | ||||
| 
 | ||||
|  |  | |||
|  | @ -183,12 +183,57 @@ So we use the PersonWorkItems view, assigning john to the query. | |||
| 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.expert.report import IReportInstance | ||||
|   >>> component.provideAdapter(WorkReportInstance, | ||||
|   ...                          provides=IReportInstance, | ||||
|   ...                          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 | ||||
| ============= | ||||
|  |  | |||
|  | @ -28,59 +28,192 @@ from cybertools.composer.report.base import Report | |||
| from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria | ||||
| from cybertools.composer.report.field import Field | ||||
| 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 loops.common import adapted, baseObject | ||||
| from loops.expert.field import TargetField | ||||
| from loops.expert.report import ReportInstance | ||||
| 
 | ||||
| results_template = ViewPageTemplateFile('results.pt') | ||||
| from loops import util | ||||
| 
 | ||||
| 
 | ||||
| task = Field('task', u'Task', | ||||
|                 description=u'The task to which work items belong.', | ||||
|                 executionSteps=['query', 'output', 'sort']) | ||||
| work = Field('work', u'Work', | ||||
|                 description=u'The short description of the work.', | ||||
|                 executionSteps=['output']) | ||||
| workDescription = Field('workDescription', u'Work Description', | ||||
|                 description=u'The long description of the work.', | ||||
|                 executionSteps=['output']) | ||||
| day = Field('day', u'Day', | ||||
|                 description=u'The day the work was done.', | ||||
|                 executionSteps=['output', 'sort']) | ||||
| class DateField(Field): | ||||
| 
 | ||||
|     part = 'date' | ||||
|     format = 'short' | ||||
|     renderer = 'right' | ||||
| 
 | ||||
|     def getValue(self, row): | ||||
|         value = self.getRawValue(row) | ||||
|         if value is None: | ||||
|             return None | ||||
|         return timeStamp2Date(value) | ||||
| 
 | ||||
|     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', | ||||
|                 description=u'The first day from which to select work.', | ||||
|                 executionSteps=['query']) | ||||
| dayTo = Field('dayTo', u'End Day', | ||||
|                 description=u'The last day until which to select work.', | ||||
|                 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): | ||||
| 
 | ||||
|     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): | ||||
| 
 | ||||
|     type = "deliverables" | ||||
|     label = u'Work Report' | ||||
|     type = "work_statement" | ||||
|     label = u'Work Statement' | ||||
| 
 | ||||
|     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): | ||||
|         # TODO: take from persistent report where appropriate | ||||
|         crit = [LeafQueryCriteria(f.name, f.operator, None, f) | ||||
|                     for f in self.getAllQueryFields()] | ||||
|         form = self.view.request.form | ||||
|         crit = self.context.queryCriteria or [] | ||||
|         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) | ||||
| 
 | ||||
|     def getResultsRenderer(self, name, defaultMacros): | ||||
|         return results_template.macros[name] | ||||
| 
 | ||||
|     def selectObjects(self, parts): | ||||
|         task = parts.get('task') | ||||
|         if not task: | ||||
|             return [] | ||||
|         return [] | ||||
|         result = [] | ||||
|         tasks = [util.getObjectForUid(t) for t in parts.pop('tasks').comparisonValue] | ||||
|         for t in list(tasks): | ||||
|             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']) | ||||
|  |  | |||
|  | @ -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> | ||||
		Loading…
	
	Add table
		
		Reference in a new issue