work in progress: reporting; example 'work statement' report is operational
This commit is contained in:
		
							parent
							
								
									55fbb7eefc
								
							
						
					
					
						commit
						0a1d2ec262
					
				
					 5 changed files with 150 additions and 102 deletions
				
			
		|  | @ -15,7 +15,25 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <div metal:define-macro="results"> | <div metal:define-macro="results"> | ||||||
|   Default Results Listing |   <h2 i18n:translate="">Work Items</h2> | ||||||
|  |   <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> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -24,11 +42,18 @@ | ||||||
| </metal:standard> | </metal:standard> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <metal:standard define-macro="target"> | <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)"> |   <td tal:define="value python:col.getDisplayValue(row)"> | ||||||
|     <a tal:attributes="href value/url" |     <a tal:omit-tag="not:value/url" | ||||||
|  |        tal:attributes="href value/url" | ||||||
|        tal:content="value/title" /></td> |        tal:content="value/title" /></td> | ||||||
| </metal:standard> | </metal:target> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
							
								
								
									
										43
									
								
								expert/field.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								expert/field.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | ||||||
|  | # | ||||||
|  | #  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 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)) | ||||||
|  | 
 | ||||||
|  | @ -220,16 +220,20 @@ in a results view. | ||||||
|   >>> from loops.expert.browser.report import ReportView, ResultsView |   >>> from loops.expert.browser.report import ReportView, ResultsView | ||||||
|   >>> resultsView = ResultsView(reportNode, TestRequest()) |   >>> resultsView = ResultsView(reportNode, TestRequest()) | ||||||
| 
 | 
 | ||||||
|   >>> results = resultsView.results().getResult() |   >>> results = resultsView.results()#.getResult() | ||||||
|   >>> len(results) |   >>> len(list(results)) | ||||||
|   1 |   1 | ||||||
|   >>> for row in results: |   >>> for row in results: | ||||||
|   ...     for col in resultsView.displayedColumns: |   ...     for col in resultsView.displayedColumns: | ||||||
|   ...         print col.getDisplayValue(row), |   ...         print col.getDisplayValue(row), | ||||||
|   ...     print |   ...     print | ||||||
|   08/12/28 1230487200 1230491700 |   08/12/28 19:00 20:15 | ||||||
|     {'url': '.../home/report/.36', 'title': u'loops Development'} |     {'url': '.../home/report/.36', 'title': u'loops Development'} | ||||||
|     {'url': '.../home/report/.33', 'title': u'john'}  4500 900 finished |     {'url': '.../home/report/.33', 'title': u'john'}  01:15 00:15 finished | ||||||
|  | 
 | ||||||
|  |   >>> results.totals.data | ||||||
|  |   {'effort': 900} | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| Fin de partie | Fin de partie | ||||||
| ============= | ============= | ||||||
|  |  | ||||||
|  | @ -33,41 +33,57 @@ 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.report import ReportInstance | from loops.expert.report import ReportInstance | ||||||
| from loops import util | from loops import util | ||||||
| 
 | 
 | ||||||
| results_template = ViewPageTemplateFile('results.pt') |  | ||||||
| 
 | 
 | ||||||
|  | class DateField(Field): | ||||||
| 
 | 
 | ||||||
| class TargetField(Field): |     part = 'date' | ||||||
| 
 |     format = 'short' | ||||||
|     renderer = 'target' |     renderer = 'right' | ||||||
| 
 | 
 | ||||||
|     def getValue(self, row): |     def getValue(self, row): | ||||||
|         value = self.getRawValue(row) |         value = self.getRawValue(row) | ||||||
|         return util.getObjectForUid(value) |  | ||||||
| 
 |  | ||||||
|     def getDisplayValue(self, row): |  | ||||||
|         value = self.getValue(row) |  | ||||||
|         if value is None: |         if value is None: | ||||||
|             return dict(title=self.getRawValue(row), url=u'') |             return None | ||||||
|         view = row.parent.context.view |         return timeStamp2Date(value) | ||||||
|         return dict(title=value.title, url=view.getUrlForTarget(value)) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class DayField(Field): |  | ||||||
| 
 |  | ||||||
|     def getValue(self, row): |  | ||||||
|         return timeStamp2Date(self.getRawValue(row)) |  | ||||||
| 
 | 
 | ||||||
|     def getDisplayValue(self, row): |     def getDisplayValue(self, row): | ||||||
|         value = self.getValue(row) |         value = self.getValue(row) | ||||||
|         if value: |         if value: | ||||||
|             view = row.parent.context.view |             view = row.parent.context.view | ||||||
|             return formatDate(value, 'date', 'short', view.languageInfo.language) |             return formatDate(value, self.part, self.format, | ||||||
|  |                               view.languageInfo.language) | ||||||
|         return u'' |         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', | tasks = Field('tasks', u'Tasks', | ||||||
|                 description=u'The tasks from which work items should be selected.', |                 description=u'The tasks from which work items should be selected.', | ||||||
|                 executionSteps=['query']) |                 executionSteps=['query']) | ||||||
|  | @ -77,13 +93,13 @@ dayFrom = Field('dayFrom', u'Start Day', | ||||||
| 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 = DayField('day', u'Day', | day = DateField('day', u'Day', | ||||||
|                 description=u'The day the work was done.', |                 description=u'The day the work was done.', | ||||||
|                 executionSteps=['sort', 'output']) |                 executionSteps=['sort', 'output']) | ||||||
| timeStart = Field('start', u'Start Time', | timeStart = TimeField('start', u'Start', | ||||||
|                 description=u'The time the unit of work was started.', |                 description=u'The time the unit of work was started.', | ||||||
|                 executionSteps=['sort', 'output']) |                 executionSteps=['sort', 'output']) | ||||||
| timeEnd = Field('end', u'End Time', | timeEnd = TimeField('end', u'End', | ||||||
|                 description=u'The time the unit of work was finished.', |                 description=u'The time the unit of work was finished.', | ||||||
|                 executionSteps=['output']) |                 executionSteps=['output']) | ||||||
| task = TargetField('taskId', u'Task', | task = TargetField('taskId', u'Task', | ||||||
|  | @ -91,19 +107,19 @@ task = TargetField('taskId', u'Task', | ||||||
|                 executionSteps=['output']) |                 executionSteps=['output']) | ||||||
| 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=['sort', 'output']) |                 executionSteps=['query', 'sort', 'output']) | ||||||
| title = Field('title', u'Title', | title = 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', | description = Field('description', u'Description', | ||||||
|                 description=u'The long description of the work.', |                 description=u'The long description of the work.', | ||||||
|                 executionSteps=['x_output']) |                 executionSteps=['x_output']) | ||||||
| duration = Field('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']) | ||||||
| effort = Field('effort', u'Effort', | effort = DurationField('effort', u'Effort', | ||||||
|                 description=u'The effort of the work.', |                 description=u'The effort of the work.', | ||||||
|                 executionSteps=['output', 'sum']) |                 executionSteps=['output', 'totals']) | ||||||
| state = Field('state', u'State', | 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']) | ||||||
|  | @ -122,7 +138,19 @@ class WorkRow(BaseRow): | ||||||
|     def getDay(self, attr): |     def getDay(self, attr): | ||||||
|         return self.context.timeStamp |         return self.context.timeStamp | ||||||
| 
 | 
 | ||||||
|     attributeHandlers = dict(day=getDay) |     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): | ||||||
|  | @ -131,37 +159,46 @@ class WorkReportInstance(ReportInstance): | ||||||
|     label = u'Work Report' |     label = u'Work Report' | ||||||
| 
 | 
 | ||||||
|     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, title, description, | ||||||
|                    duration, effort, state)) |                    duration, effort, state)) | ||||||
|  | 
 | ||||||
|     defaultOutputFields = fields |     defaultOutputFields = fields | ||||||
|  |     defaultSortCriteria = (day, timeStart,) | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def queryCriteria(self): |     def queryCriteria(self): | ||||||
|  |         form = self.view.request.form | ||||||
|         crit = self.context.queryCriteria |         crit = self.context.queryCriteria | ||||||
|         if crit is None: |         if crit is None: | ||||||
|             f = self.fields['tasks'] |             f = self.fields['tasks'] | ||||||
|             tasks = baseObject(self.context).getChildren([self.hasReportPredicate]) |             tasks = baseObject(self.context).getChildren([self.hasReportPredicate]) | ||||||
|  |             tasks = [util.getUidForObject(task) for task in tasks] | ||||||
|             crit = [LeafQueryCriteria(f.name, f.operator, tasks, f)] |             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): | ||||||
|         result = [] |         result = [] | ||||||
|         tasks = parts.pop('tasks').comparisonValue |         tasks = [util.getObjectForUid(t) for t in parts.pop('tasks').comparisonValue] | ||||||
|         for t in list(tasks): |         for t in list(tasks): | ||||||
|             tasks.extend(self.getAllSubtasks(t)) |             tasks.extend(self.getAllSubtasks(t)) | ||||||
|         for t in tasks: |         for t in tasks: | ||||||
|             result.extend(self.selectWorkItems(t, parts)) |             result.extend(self.selectWorkItems(t, parts)) | ||||||
|         # TODO: remove parts already used for selection from parts list |         # remove parts already used for selection from parts list: | ||||||
|  |         parts.pop('userName', None) | ||||||
|         return result |         return result | ||||||
| 
 | 
 | ||||||
|     def selectWorkItems(self, task, parts): |     def selectWorkItems(self, task, parts): | ||||||
|         wi = self.workItems |  | ||||||
|         states = ['done', 'done_x', 'finished'] |         states = ['done', 'done_x', 'finished'] | ||||||
|         return wi.query(task=util.getUidForObject(task), state=states) |         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): |     def getAllSubtasks(self, concept): | ||||||
|         result = [] |         result = [] | ||||||
|  |  | ||||||
|  | @ -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