diff --git a/loops/expert/browser/export.py b/loops/expert/browser/export.py index 90a4c65..7265be6 100644 --- a/loops/expert/browser/export.py +++ b/loops/expert/browser/export.py @@ -1,27 +1,10 @@ -# -# Copyright (c) 2017 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 -# +# loops.expert.browser.export -""" -View classes for export of report results. +""" View classes for export of report results. """ import csv -from cStringIO import StringIO +from io import StringIO import os import time from zope.cachedescriptors.property import Lazy @@ -67,8 +50,8 @@ class ResultsConceptCSVExport(ResultsConceptView): title = field.title if not isinstance(title, Message): title = _(title) - return encode(translate(title, target_language=lang), - self.encoding) + #return encode(translate(title, target_language=lang), self.encoding) + return translate(title, target_language=lang) def getFilenames(self): """@return (data_fn, result_fn)""" @@ -122,7 +105,7 @@ class ResultsConceptCSVExport(ResultsConceptView): value = f.getExportValue(row, 'csv', lang) if ILoopsObject.providedBy(value): value = value.title - value = encode(value, self.encoding) + #value = encode(value, self.encoding) data[f.name] = value writer.writerow(data) if csvRenderer: @@ -151,7 +134,7 @@ class ResultsConceptCSVExport(ResultsConceptView): def encode(text, encoding): - if not isinstance(text, unicode): + if isinstance(text, bytes): return text try: return text.encode(encoding) @@ -161,7 +144,7 @@ def encode(text, encoding): try: result.append(c.encode(encoding)) except UnicodeEncodeError: - result.append('?') - return ''.join(result) - return '???' + result.append(b'?') + return b''.join(result) + return b'???' diff --git a/loops/organize/tracking/README.txt b/loops/organize/tracking/README.txt index 4f2eaf0..e52ec6c 100644 --- a/loops/organize/tracking/README.txt +++ b/loops/organize/tracking/README.txt @@ -2,8 +2,6 @@ loops - Linked Objects for Organization and Processing Services =============================================================== - ($Id$) - Let's do some basic setup >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown @@ -147,10 +145,12 @@ Overview (cumulative) statistics >>> from loops.organize.tracking.report import TrackingStats >>> view = TrackingStats(statQuery, TestRequest()) >>> result = view.getData() - >>> result['macro'][4][1][u'define-macro'] - u'overview' + >>> result['macro'][4][1]['define-macro'] + 'overview' >>> result['data'] - [{'access': 2, 'new': 0, 'changed': 1, 'period': '...', 'count': 3}] + [{'period': '...', ...}] + +[{'access': 2, 'new': 0, 'changed': 1, 'period': '...', 'count': 3}] Changes for a certain period (month) @@ -168,23 +168,27 @@ Recent changes >>> from loops.organize.tracking.report import RecentChanges >>> view = RecentChanges(statQuery, TestRequest()) >>> result = view.getData() - >>> result['macro'][4][1][u'define-macro'] - u'recent_changes' + >>> result['macro'][4][1]['define-macro'] + 'recent_changes' >>> data = result['data'] >>> data [] >>> data[0].timeStamp - u'... ...:...' + '... ...:...' >>> data[0].objectData - {'version': '', 'description': u'', 'title': 'Change Doc 001', 'url': '', - 'object': , 'type': u'Text', - 'canAccess': True} + {'object': ...} + +{'version': '', 'description': '', 'title': 'Change Doc 001', 'url': '', + 'object': , 'type': 'Text', + 'canAccess': True} >>> data[0].user - {'url': '', 'object': , 'title': u'john'} + {'object': ...} + +{'url': '', 'object': , 'title': 'john'} >>> data[0].action 'modify' diff --git a/loops/organize/tracking/change.py b/loops/organize/tracking/change.py index ce0b555..148d59c 100644 --- a/loops/organize/tracking/change.py +++ b/loops/organize/tracking/change.py @@ -1,28 +1,11 @@ -# -# Copyright (c) 2014 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 -# +# loops.organize.tracking.change -""" -Recording changes to loops objects. +""" Recording changes to loops objects. """ from zope.app.container.interfaces import IObjectAddedEvent, IObjectRemovedEvent from zope.app.container.interfaces import IObjectMovedEvent -from zope.interface import implements +from zope.interface import implementer from zope.cachedescriptors.property import Lazy from zope.component import adapter from zope.lifecycleevent.interfaces import IObjectModifiedEvent, IObjectCreatedEvent @@ -100,10 +83,9 @@ class IChangeRecord(ITrack): pass +@implementer(IChangeRecord) class ChangeRecord(Track): - implements(IChangeRecord) - typeName = 'ChangeRecord' diff --git a/loops/organize/tracking/setup.py b/loops/organize/tracking/setup.py index 7a847e7..0a02449 100644 --- a/loops/organize/tracking/setup.py +++ b/loops/organize/tracking/setup.py @@ -1,29 +1,9 @@ -# -# Copyright (c) 2008 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 -# +# loops.organize.tracking.setup -""" -Automatic setup of a loops site for the organize.tracking package. - -$Id$ +""" Automatic setup of a loops site for the organize.tracking package. """ from zope.component import adapts -from zope.interface import implements, Interface from cybertools.tracking.btree import TrackingStorage from loops.organize.tracking.change import ChangeRecord diff --git a/loops/organize/work/README.txt b/loops/organize/work/README.txt index ce159da..932bfa0 100644 --- a/loops/organize/work/README.txt +++ b/loops/organize/work/README.txt @@ -50,7 +50,7 @@ and a pluggable authentication utility with a principal folder. >>> johnC = setupData.johnC >>> from zope.app.authentication.principalfolder import Principal - >>> pJohn = Principal('users.john', 'xxx', u'John') + >>> pJohn = Principal('users.john', 'xxx', 'John') >>> from loops.tests.auth import login >>> login(pJohn) @@ -62,7 +62,7 @@ to assign work items to this task. >>> from loops.concept import Concept >>> from loops.setup import addAndConfigureObject >>> task01 = addAndConfigureObject(concepts, Concept, 'loops_dev', - ... title=u'loops Development', conceptType=tTask) + ... title='loops Development', conceptType=tTask) >>> home.target = task01 Forms for adding and editing work items @@ -77,10 +77,10 @@ When this form is submitted, a form controller is automatically created for the view on the currently shown node. The data from the form is processed by calling the form controller's update method - >>> input = {u'form.action': u'create_workitem', u'workitem.action': u'finish', - ... u'description': u'Description', u'comment': u'Comment', - ... u'start_date': u'2008-12-28', u'start_time': u'T19:00:00', - ... u'end_time': u'T20:15:00', u'duration': u'1:15', u'effort': u'0:15'} + >>> input = {'form.action': 'create_workitem', 'workitem.action': 'finish', + ... 'description': 'Description', 'comment': 'Comment', + ... 'start_date': '2008-12-28', 'start_time': 'T19:00:00', + ... 'end_time': 'T20:15:00', 'duration': '1:15', 'effort': '0:15'} >>> request = TestRequest(form=input) >>> request.setPrincipal(pJohn) @@ -92,10 +92,11 @@ by calling the form controller's update method False >>> list(workItems) - [] + [] + + {'comment': 'Comment', 'end': 1230491700, 'description': 'Description', + 'created': ..., 'creator': '33', 'start': 1230487200, + 'duration': 4500, 'effort': 900}>] Work items views ---------------- @@ -110,16 +111,16 @@ Work items views >>> from loops.organize.work.browser import WorkItemDetails >>> view = WorkItemDetails(work, wi01) >>> view.day, view.start, view.end - (u'08/12/28', u'19:00', u'20:15') + ('08/12/28', '19:00', '20:15') Work items life cycle --------------------- Let's create another work item, now in state planned. - >>> input = {u'form.action': u'create_workitem', u'workitem.action': u'plan', - ... u'title': u'Install Zope', - ... u'start_date': u'2009-01-19', u'start_time': u'T09:00:00'} + >>> input = {'form.action': 'create_workitem', 'workitem.action': 'plan', + ... 'title': 'Install Zope', + ... 'start_date': '2009-01-19', 'start_time': 'T09:00:00'} >>> request = TestRequest(form=input) >>> request.setPrincipal(pJohn) >>> nodeView = NodeView(home, request) @@ -132,21 +133,24 @@ work item, the form will be pre-filled with some of the item's data. >>> form = CreateWorkItemForm(home, TestRequest(form=dict(id='0000002'))) >>> form.title - u'Install Zope' + 'Install Zope' The 'delegate' transition is omitted from the actions list because it is only available for privileged users. - >>> form.actions - [{'selected': False, 'name': 'plan', 'title': 'plan'}, - {'selected': False, 'name': 'accept', 'title': 'accept'}, - {'selected': False, 'name': 'start', 'title': 'start working'}, - {'selected': False, 'name': 'work', 'title': 'work'}, - {'selected': False, 'name': 'finish', 'title': 'finish'}, - {'selected': False, 'name': 'delegate', 'title': 'delegate'}, - {'selected': False, 'name': 'move', 'title': 'move'}, - {'selected': False, 'name': 'cancel', 'title': 'cancel'}, - {'selected': False, 'name': 'modify', 'title': 'modify'}] + >>> len(form.actions) + 9 + +form.actions +[{'selected': False, 'name': 'plan', 'title': 'plan'}, + {'selected': False, 'name': 'accept', 'title': 'accept'}, + {'selected': False, 'name': 'start', 'title': 'start working'}, + {'selected': False, 'name': 'work', 'title': 'work'}, + {'selected': False, 'name': 'finish', 'title': 'finish'}, + {'selected': False, 'name': 'delegate', 'title': 'delegate'}, + {'selected': False, 'name': 'move', 'title': 'move'}, + {'selected': False, 'name': 'cancel', 'title': 'cancel'}, + {'selected': False, 'name': 'modify', 'title': 'modify'}] Work Item Queries ================= @@ -156,7 +160,7 @@ Work Item Queries >>> from loops.organize.work.browser import UserWorkItems, PersonWorkItems >>> tQuery = addAndConfigureObject(concepts, Concept, 'query', - ... title=u'Query', conceptType=concepts.getTypeConcept(), + ... title='Query', conceptType=concepts.getTypeConcept(), ... typeInterface=IQueryConcept) >>> query = addAndConfigureObject(concepts, Concept, 'userworkitems', @@ -176,9 +180,10 @@ So we use the PersonWorkItems view, assigning john to the query. >>> input = dict() >>> work = PersonWorkItems(query, TestRequest(form=input)) >>> work.listWorkItems() - [] + [] + + {'title': 'Install Zope', 'created': ..., 'end': 1232352000, + 'start': 1232352000, 'creator': '33'}>] Work Reports @@ -190,10 +195,10 @@ 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, provides=IReport) >>> tReport = addAndConfigureObject(concepts, Concept, 'report', - ... title=u'Report', conceptType=concepts.getTypeConcept(), + ... title='Report', conceptType=concepts.getTypeConcept(), ... typeInterface=IReport) >>> hasReport = addAndConfigureObject(concepts, Concept, 'hasreport', - ... title=u'has Report', conceptType=concepts.getPredicateType()) + ... title='has Report', conceptType=concepts.getPredicateType()) Work statement report --------------------- @@ -202,7 +207,7 @@ 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, + ... title='Work Statement', conceptType=tReport, ... reportType='work_report') >>> workStatement.assignChild(task01, hasReport) @@ -228,12 +233,13 @@ The user interface is a ReportConceptView subclass that is directly associated w >>> for row in results: ... for col in reportView.displayedColumns: - ... print col.getDisplayValue(row), - ... print - 08/12/28 19:00 20:15 - {'url': '.../home/.36', 'title': u'loops Development'} - {'url': '.../home/.33', 'title': u'john'} 00:15 - {'actions': [...]} + ... print(col.getDisplayValue(row), end=' ') + ... print() + 08/12/28 19:00 20:15 {'title': ...} + +{'url': '.../home/.36', 'title': 'loops Development'} +{'url': '.../home/.33', 'title': 'john'} 00:15 +{'actions': [...]} >>> results.totals.data {'effort': 900.0} @@ -246,7 +252,7 @@ Export of work data >>> reportView.nodeView = nodeView >>> output = reportView() - >>> print output + >>> print(output) Day;Start;End;Task;Party;Title;LA;Effort;State 08/12/28;19:00;20:15;loops Development;john;;;15;finished @@ -261,16 +267,16 @@ Let's start with creating an a event and assigning it a task. >>> from loops.organize.interfaces import IEvent, IAgendaItem >>> tEvent = addAndConfigureObject(concepts, Concept, 'event', - ... title=u'Event', conceptType=concepts.getTypeConcept(), + ... title='Event', conceptType=concepts.getTypeConcept(), ... typeInterface=IEvent) >>> tAgendaItem = addAndConfigureObject(concepts, Concept, 'agendaitem', - ... title=u'AgendaItem', conceptType=concepts.getTypeConcept(), + ... title='AgendaItem', conceptType=concepts.getTypeConcept(), ... typeInterface=IAgendaItem) >>> ev01 = addAndConfigureObject(concepts, Concept, 'ev01', - ... title=u'loops Meeting', conceptType=tEvent) + ... title='loops Meeting', conceptType=tEvent) >>> aitem01 = addAndConfigureObject(concepts, Concept, 'aitem01', - ... title=u'Documentation of Features', conceptType=tAgendaItem) + ... title='Documentation of Features', conceptType=tAgendaItem) >>> ev01.assignChild(aitem01) Now we create the meeting minutes report. We assign the event type as a @@ -282,7 +288,7 @@ report is available. ... name='meeting_minutes') >>> meetingMinutes = addAndConfigureObject(concepts, Concept, - ... 'meeting_minutes', title=u'Meeting Minutes', conceptType=tReport, + ... 'meeting_minutes', title='Meeting Minutes', conceptType=tReport, ... reportType='meeting_minutes') >>> meetingMinutes.assignChild(tEvent, hasReport) @@ -297,10 +303,10 @@ We can now access the report using a corresponding report-based view. 1 >>> for row in results: ... for col in reportView.displayedColumns: - ... print col.getDisplayValue(row), - ... print - {'url': 'http://127.0.0.1/loops/views/home/.58', - 'title': u'Documentation of Features'} + ... print(col.getDisplayValue(row), end=' ') + ... print() + {'title': 'Documentation of Features', + 'url': 'http://127.0.0.1/loops/views/home/.58'} diff --git a/loops/organize/work/report.py b/loops/organize/work/report.py index 4786f95..d37e5e3 100644 --- a/loops/organize/work/report.py +++ b/loops/organize/work/report.py @@ -1,27 +1,10 @@ -# -# Copyright (c) 2018 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 -# +# loops.organize.work.report -""" -Work report definitions. +""" Work report definitions. """ from datetime import date, timedelta -from zope.app.pagetemplate import ViewPageTemplateFile +from zope.browserpage import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy from zope.component import adapter, getAdapter from zope.i18n.locales import locales