diff --git a/expert/browser/report.py b/expert/browser/report.py index b33c715..ccfc26a 100644 --- a/expert/browser/report.py +++ b/expert/browser/report.py @@ -75,9 +75,32 @@ class ResultsView(NodeView): @Lazy def params(self): params = dict(self.request.form) - params.pop('report_execute', None) + params = self.parseParams(params) return params + def parseParams(self, params): + params.pop('report_execute', None) + if 'limits' in params: + params['limits'] = self.parseLimitsParam(params['limits']) + return params + + def parseLimitsParam(self, value): + if not value: + return None + if isinstance(value, basestring): + limits = value.split(',') + else: + limits = value + if len(limits) < 2: + limits.append(None) + result = [] + for p in limits[:2]: + if not p: + result.append(None) + else: + result.append(int(p)) + return result + @Lazy def report(self): return adapted(self.virtualTargetObject) @@ -147,7 +170,8 @@ class ResultsConceptView(ConceptView): reportType = self.reportType or self.report.reportType ri = component.getAdapter(self.report, IReportInstance, name=reportType) - ri.view = self.nodeView + #ri.view = self.nodeView + ri.view = self return ri def results(self): diff --git a/expert/report.py b/expert/report.py index f5ccc85..1f66bce 100644 --- a/expert/report.py +++ b/expert/report.py @@ -95,15 +95,21 @@ class ReportInstance(BaseReport): crit = self.queryCriteria if crit is None: return [] + limits = self.limits if dynaParams is not None: for k, v in dynaParams.items(): + if k == 'limits': + limits = v + break if k in crit.parts.keys(): crit.parts[k].comparisonValue = v parts = Jeep(crit.parts) result = list(self.selectObjects(parts)) # may modify parts qc = CompoundQueryCriteria(parts) return ResultSet(self, result, rowFactory=self.rowFactory, - sortCriteria=self.getSortCriteria(), queryCriteria=qc) + sortCriteria=self.getSortCriteria(), queryCriteria=qc, + limits=limits) + def selectObjects(self, parts): # to be implemented by subclass return [] diff --git a/knowledge/browser.py b/knowledge/browser.py index ff9781e..8a7a281 100644 --- a/knowledge/browser.py +++ b/knowledge/browser.py @@ -32,6 +32,7 @@ from loops.browser.common import BaseView from loops.browser.concept import ConceptView from loops.expert.browser.report import ResultsConceptView from loops.knowledge.interfaces import IPerson, ITask +from loops.knowledge.qualification import QualificationRecord from loops.organize.work.browser import CreateWorkItemForm, CreateWorkItem from loops.organize.party import getPersonForUser from loops.util import _ @@ -120,6 +121,8 @@ class PersonQualificationView(ResultsConceptView): class CreateQualificationRecordForm(CreateWorkItemForm): macros = knowledge_macros + recordManagerName = 'qualification' + trackFactory = QualificationRecord @Lazy def macro(self): diff --git a/knowledge/configure.zcml b/knowledge/configure.zcml index 2552184..da2bb30 100644 --- a/knowledge/configure.zcml +++ b/knowledge/configure.zcml @@ -100,6 +100,19 @@ factory="loops.knowledge.browser.Candidates" permission="zope.View" /> + + + + diff --git a/knowledge/data/loops_knowledge_de.dmp b/knowledge/data/loops_knowledge_de.dmp index 2ca69b4..3b2ce7b 100644 --- a/knowledge/data/loops_knowledge_de.dmp +++ b/knowledge/data/loops_knowledge_de.dmp @@ -1,17 +1,17 @@ -type(u'competence', u'Competence', viewName=u'', - typeInterface=u'', options=u'action.portlet:edit_concept') +type(u'competence', u'Kompetenz', viewName=u'', + typeInterface=u'', options=u'action.portlet:create_subtype,edit_concept') type(u'person', u'Person', viewName=u'', typeInterface=u'loops.knowledge.interfaces.IPerson', - options=u'action.portlet:editPerson') + options=u'action.portlet:createQualification,editPerson') type(u'task', u'Aufgabe', viewName=u'', typeInterface=u'loops.knowledge.interfaces.ITask', - options=u'action.portlet:createTopic,editTopic') + options=u'action.portlet:createTask,editTask') type(u'topic', u'Thema', viewName=u'', typeInterface=u'loops.knowledge.interfaces.ITopic', - options=u'action.portlet:createTopic,editTopic') + options=u'action.portlet:createTask,createTopic,editTopic') type(u'training', u'Schulung', viewName=u'', typeInterface=u'loops.organize.interfaces.ITask', - options=u'action.portlet:create_subtype,edit_concept') + options=u'action.portlet:edit_concept') concept(u'general', u'Allgemein', u'domain') concept(u'system', u'System', u'domain') @@ -26,19 +26,19 @@ concept(u'issubtype', u'is Subtype', u'predicate', options=u'hide_children', predicateInterface='loops.interfaces.IIsSubtype') # structure -child(u'competence', u'general', u'standard') -child(u'depends', u'general', u'standard') -child(u'knows', u'general', u'standard') -child(u'person', u'general', u'standard') -child(u'provides', u'general', u'standard') -child(u'requires', u'general', u'standard') -child(u'task', u'general', u'standard') -child(u'topic', u'general', u'standard') -child(u'training', u'general', u'standard') +child(u'general', u'competence', u'standard') +child(u'general', u'depends', u'standard') +child(u'general', u'knows', u'standard') +child(u'general', u'person', u'standard') +child(u'general', u'provides', u'standard') +child(u'general', u'requires', u'standard') +child(u'general', u'task', u'standard') +child(u'general', u'topic', u'standard') +child(u'general', u'training', u'standard') -child(u'issubtype', u'system', u'standard') +child(u'system', u'issubtype', u'standard') -child(u'training', u'competence', u'issubtype', usePredicate=u'provides') +child(u'competence', u'training', u'issubtype', usePredicate=u'provides') # records records(u'qualification', u'loops.knowledge.qualification.QualificationRecord') diff --git a/knowledge/data/loops_knowledge_update_de.dmp b/knowledge/data/loops_knowledge_update_de.dmp new file mode 100644 index 0000000..4080f43 --- /dev/null +++ b/knowledge/data/loops_knowledge_update_de.dmp @@ -0,0 +1,44 @@ +type(u'competence', u'Kompetenz', viewName=u'', + typeInterface=u'', options=u'action.portlet:create_subtype,edit_concept') +# type(u'person', u'Person', viewName=u'', +# typeInterface=u'loops.knowledge.interfaces.IPerson', +# options=u'action.portlet:editPerson') +# type(u'task', u'Aufgabe', viewName=u'', +# typeInterface=u'loops.knowledge.interfaces.ITask', +# options=u'action.portlet:createTask,editTask') +# type(u'topic', u'Thema', viewName=u'', +# typeInterface=u'loops.knowledge.interfaces.ITopic', +# options=u'action.portlet:createTask,createTopic,editTopic') +type(u'training', u'Schulung', viewName=u'', + typeInterface=u'loops.organize.interfaces.ITask', + options=u'action.portlet:edit_concept') + +concept(u'general', u'Allgemein', u'domain') +concept(u'system', u'System', u'domain') + +# predicates +concept(u'depends', u'depends', u'predicate') +concept(u'knows', u'knows', u'predicate') +concept(u'provides', u'provides', u'predicate') +concept(u'requires', u'requires', u'predicate') + +concept(u'issubtype', u'is Subtype', u'predicate', options=u'hide_children', + predicateInterface='loops.interfaces.IIsSubtype') + +# structure +child(u'general', u'competence', u'standard') +child(u'general', u'depends', u'standard') +child(u'general', u'knows', u'standard') +#child(u'general', u'person', u'standard') +child(u'general', u'provides', u'standard') +child(u'general', u'requires', u'standard') +#child(u'general', u'task', u'standard') +#child(u'general', u'topic', u'standard') +child(u'general', u'training', u'standard') + +child(u'system', u'issubtype', u'standard') + +child(u'competence', u'training', u'issubtype', usePredicate=u'provides') + +# records +records(u'qualification', u'loops.knowledge.qualification.QualificationRecord') diff --git a/knowledge/knowledge_macros.pt b/knowledge/knowledge_macros.pt index 5d12f77..f775b71 100644 --- a/knowledge/knowledge_macros.pt +++ b/knowledge/knowledge_macros.pt @@ -62,6 +62,48 @@
+ + +
Add Qualification Record
+
+ +
+
+
+
+ + +   + + + + + +
+
+ +
+
+
diff --git a/knowledge/qualification.py b/knowledge/qualification.py index 62b115e..f123c39 100644 --- a/knowledge/qualification.py +++ b/knowledge/qualification.py @@ -38,8 +38,12 @@ from loops.organize.work.base import WorkItem, WorkItems @implementer(IStatesDefinition) def qualificationStates(): return StatesDefinition('qualification', + State('new', 'new', ('assign',), + color='grey'), State('open', 'open', - ('register', 'pass', 'fail', 'cancel', 'modify'), + ('register', + #'pass', 'fail', + 'cancel', 'modify'), color='red'), State('registered', 'registered', ('register', 'pass', 'fail', 'unregister', 'cancel', 'modify'), @@ -61,14 +65,17 @@ def qualificationStates(): State('open_x', 'open', ('modify',), color='red'), State('registered_x', 'registered', ('modify',), color='yellow'), # transitions: + Transition('assign', 'assign', 'open'), Transition('register', 'register', 'registered'), Transition('pass', 'pass', 'passed'), Transition('fail', 'fail', 'failed'), Transition('unregister', 'unregister', 'open'), Transition('cancel', 'cancel', 'cancelled'), + Transition('modify', 'modify', 'open'), Transition('close', 'close', 'closed'), Transition('open', 'open', 'open'), - initialState='open') + #initialState='open') + initialState='new') # TODO: handle assignment to competence class QualificationRecord(WorkItem): diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 149decd..c902330 100644 Binary files a/locales/de/LC_MESSAGES/loops.mo and b/locales/de/LC_MESSAGES/loops.mo differ diff --git a/locales/de/LC_MESSAGES/loops.po b/locales/de/LC_MESSAGES/loops.po index 165397a..76f2957 100644 --- a/locales/de/LC_MESSAGES/loops.po +++ b/locales/de/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: $Id$\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2012-05-30 12:00 CET\n" +"PO-Revision-Date: 2012-06-10 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -242,6 +242,9 @@ msgstr "Folgetermin anlegen..." msgid "Create an event that is linked to this one." msgstr "Einen neuen Termin anlegen, der mit diesem verknüpft ist." +msgid "Create Follow-up Event for: $event" +msgstr "Folgetermin anlegen für: $event" + msgid "Edit Event..." msgstr "Termin bearbeiten..." @@ -290,6 +293,9 @@ msgstr "Besprechungsprotokoll anzeigen..." msgid "Show meeting minutes for this object." msgstr "Besprechungsprotokoll für dieses Objekt anzeigen." +msgid "Download Meeting Minutes" +msgstr "Besprechungsprotokoll generieren" + msgid "Task/Action" msgstr "Aufgabe" @@ -395,6 +401,9 @@ msgstr "Thema" msgid "Task" msgstr "Aufgabe" +msgid "Tasks" +msgstr "Aufgaben" + msgid "Domain" msgstr "Bereich" diff --git a/organize/README.txt b/organize/README.txt index 09431c0..349eb44 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -260,7 +260,7 @@ Automatic security settings on persons >>> from zope.traversing.api import getName >>> list(sorted(getName(c) for c in concepts['person'].getChildren())) - [u'general', u'jim', u'john', u'martha', u'person.newuser'] + [u'jim', u'john', u'martha', u'person.newuser'] Person objects that have a user assigned to them receive this user (principal) as their owner. @@ -390,6 +390,11 @@ Events listing >>> list(listing.events()) [] +Creation of follow-up event +--------------------------- + + >>> from loops.organize.browser.event import CreateFollowUpEvent + Send Email to Members ===================== diff --git a/organize/browser/event.py b/organize/browser/event.py index 470fe8a..560abad 100644 --- a/organize/browser/event.py +++ b/organize/browser/event.py @@ -23,9 +23,11 @@ Definition of view classes and other browser related stuff for tasks. import calendar from datetime import date, datetime, timedelta from urllib import urlencode -from zope import interface, component +from zope.app.container.interfaces import INameChooser from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy +from zope import interface, component +from zope.traversing.api import getName from cybertools.browser.action import actions from cybertools.meta.interfaces import IOptions @@ -34,8 +36,12 @@ from loops.browser.concept import ConceptView from loops.browser.form import CreateConceptPage, CreateConcept from loops.browser.form import EditConceptPage, EditConcept from loops.browser.node import NodeView -from loops.common import adapted +from loops.common import adapted, baseObject +from loops.concept import Concept +from loops.organize.work.meeting import MeetingMinutes +from loops.setup import addAndConfigureObject from loops.util import _ +from loops import util organize_macros = ViewPageTemplateFile('view_macros.pt') @@ -247,12 +253,39 @@ class CalendarInfo(NodeView): # special forms -class CreateFollowUpEventForm(CreateConceptPage): +class CreateFollowUpEventForm(CreateConceptPage, MeetingMinutes): fixedType = True typeToken = '.loops/concepts/event' form_action = 'create_followup_event' - showAssignments = True + showAssignments = False + + @Lazy + def macro(self): + return organize_macros.macros['create_followup_event'] + + @Lazy + def baseEvent(self): + return adapted(self.virtualTargetObject) + + @Lazy + def title(self): + event = self.baseEvent + evView = ConceptView(event, self.request) + eventTitle = u'%s, %s' % (event.title, evView.data['start']) + return _(u'Create Follow-up Event for: $event', + mapping=dict(event=eventTitle)) + + @Lazy + def data(self): + data = self.getData() + data['title'] = self.baseEvent.title + data['description'] = self.baseEvent.description + return data + + def results(self): + return self.reportInstance.getResults( + dict(tasks=util.getUidForObject(self.virtualTargetObject))) class EditFollowUpEventForm(EditConceptPage, CreateFollowUpEventForm): @@ -271,6 +304,55 @@ class CreateFollowUpEvent(CreateConcept, BaseFollowUpController): defaultTypeToken = '.loops/concepts/event' + @Lazy + def followsPredicate(self): + return self.view.conceptManager['follows'] + + @Lazy + def baseEvent(self): + return adapted(self.view.virtualTargetObject) + + def update(self): + result = super(CreateFollowUpEvent, self).update() + form = self.request.form + toBeAssigned = form.get('cb_select_tasks') or [] + print '***', toBeAssigned + for uid in toBeAssigned: + task = util.getObjectForUid(uid) + self.createFollowUpTask(adapted(task)) + return result + + def createFollowUpTask(self, source): + cm = self.view.conceptManager + stask = baseObject(source) + bevt = baseObject(self.baseEvent) + taskType = stask.conceptType + taskName = getName(stask) + name = INameChooser(cm).chooseName(taskName, stask) + newTask = addAndConfigureObject(cm, Concept, name, + conceptType=taskType, + title=source.title, + description=source.description, + start=source.start, + end=source.end) + stask.assignChild(newTask, self.followsPredicate) + for rel in stask.getParentRelations(): + if rel.predicate != self.view.typePredicate: + if rel.first == bevt: + parent = self.object + else: + parent = rel.first + newTask.assignParent(parent, rel.predicate, + order=rel.order, relevance=rel.relevance) + return newTask + + def assignConcepts(self, obj): + bevt = baseObject(self.baseEvent) + bevt.assignChild(obj, self.followsPredicate) + for rel in bevt.getParentRelations(): + if rel.predicate != self.view.typePredicate: + obj.assignParent(rel.first, rel.predicate) + class EditFollowUpEvent(EditConcept, BaseFollowUpController): diff --git a/organize/browser/view_macros.pt b/organize/browser/view_macros.pt index 51f5434..b3eb002 100644 --- a/organize/browser/view_macros.pt +++ b/organize/browser/view_macros.pt @@ -1,5 +1,4 @@ - - + @@ -56,6 +55,26 @@ + +

+ + +

Tasks

+ + + + + + +
@@ -113,3 +132,6 @@

+ + + diff --git a/organize/work/browser.py b/organize/work/browser.py index dfcc9d1..f0d86fb 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -307,6 +307,8 @@ class UserWorkItems(PersonWorkItems): class CreateWorkItemForm(ObjectForm, BaseTrackView): template = work_macros + recordManagerName = 'work' + trackFactory = WorkItem def checkPermissions(self): return canAccessObject(self.task or self.target) @@ -325,9 +327,10 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): def track(self): id = self.request.form.get('id') if id is not None: - workItems = self.loopsRoot.getRecordManager()['work'] + workItems = self.loopsRoot.getRecordManager()[ + self.recordManagerName] return workItems.get(id) - return WorkItem(None, 0, None, {}) + return self.trackFactory(None, 0, None, {}) @Lazy def title(self): diff --git a/organize/work/configure.zcml b/organize/work/configure.zcml index a0df764..4c88e42 100644 --- a/organize/work/configure.zcml +++ b/organize/work/configure.zcml @@ -103,6 +103,12 @@ class="loops.organize.work.meeting.MeetingMinutes" permission="zope.View" /> + + + reportView nocall:item; + results reportView/results">

+ + Download Meeting Minutes +
 
+
+ + +
+ + +
+

Meeting Minutes

+

+

+
+ + - + + +
+
+
+
+ +
+ tal:define="showCheckboxes cb_name|nothing"> + + +
+ Task/Action
+ @@ -49,6 +94,8 @@
diff --git a/organize/work/meeting.py b/organize/work/meeting.py index cf4abf9..7aaa8a9 100644 --- a/organize/work/meeting.py +++ b/organize/work/meeting.py @@ -24,6 +24,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy from cybertools.browser.action import actions +from cybertools.docgen.base import WordDocument from loops.browser.action import TargetAction from loops.expert.browser.report import ResultsConceptView from loops.util import _ @@ -41,6 +42,7 @@ actions.register('meeting_minutes', 'portlet', TargetAction, class MeetingMinutes(ResultsConceptView): reportName = 'meeting_minutes' + reportDownload = 'meeting_minutes.doc' @Lazy def meeting_macros(self): @@ -59,3 +61,25 @@ class MeetingMinutes(ResultsConceptView): if renderer == 'subreport': return self.meeting_macros[renderer] return self.result_macros[renderer] + + +class MeetingMinutesDocument(WordDocument, MeetingMinutes): + + isToplevel = True + + def __init__(self, context, request): + MeetingMinutes.__init__(self, context, request) + + @Lazy + def macros(self): + return meeting_template.macros + + @Lazy + def reportRenderer(self): + return self.macros['document'] + + @Lazy + def content(self): + return self.reportRenderer + + diff --git a/organize/work/report.py b/organize/work/report.py index 4bbd9e0..deb8865 100644 --- a/organize/work/report.py +++ b/organize/work/report.py @@ -26,14 +26,14 @@ from zope.component import adapter 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.field import Field, CalculatedField 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, TextField, UrlField +from loops.expert.field import TargetField, DateField, TextField, UrlField from loops.expert.field import SubReport, SubReportField from loops.expert.report import ReportInstance from loops import util @@ -44,7 +44,8 @@ class StateField(Field): value = self.getValue(row) return util._(value) -class DateField(Field): + +class TrackDateField(Field): part = 'date' format = 'short' @@ -65,7 +66,7 @@ class DateField(Field): return u'' -class TimeField(DateField): +class TrackTimeField(TrackDateField): part = 'time' @@ -105,14 +106,14 @@ dayFrom = Field('dayFrom', u'Start Day', dayTo = Field('dayTo', u'End Day', description=u'The last day until which to select work.', executionSteps=['query']) -day = DateField('day', u'Day', +day = TrackDateField('day', u'Day', description=u'The day the work was done.', cssClass='center', executionSteps=['sort', 'output']) -timeStart = TimeField('start', u'Start', +timeStart = TrackTimeField('start', u'Start', description=u'The time the unit of work was started.', executionSteps=['sort', 'output']) -timeEnd = TimeField('end', u'End', +timeEnd = TrackTimeField('end', u'End', description=u'The time the unit of work was finished.', executionSteps=['output']) task = TargetField('taskId', u'Task', @@ -272,7 +273,21 @@ class MeetingMinutesWork(WorkReportInstance, SubReport): return [] -taskTitle = UrlField('title', u'Title', +eventTitle = CalculatedField('eventTitle', u'Event Title', + description=u'', + executionSteps=(['header'])) +eventDescription = CalculatedField('eventDescription', u'Event Description', + description=u'', + executionSteps=(['header'])) +eventStart = DateField('eventStart', u'Event Start', + description=u'', + format=('dateTime', 'short'), + executionSteps=(['header'])) +eventEnd = DateField('eventEnd', u'Event End', + description=u'', + format=('dateTime', 'short'), + executionSteps=(['header'])) +taskTitle = UrlField('title', u'Task Title', description=u'The short description of the task.', cssClass='header-1', executionSteps=['output']) @@ -288,27 +303,46 @@ workItems = SubReportField('workItems', u'Work Items', class TaskRow(BaseRow): - pass + @Lazy + def event(self): + return self.parent.context.view.adapted + + @Lazy + def eventTitle(self): + return self.event.title + + @Lazy + def eventDescription(self): + return self.event.description + + @Lazy + def eventStart(self): + return self.event.start + + @Lazy + def eventEnd(self): + return self.event.end + + useRowProperty = BaseRow.useRowProperty + attributeHandlers = dict( + eventStart=useRowProperty, + eventEnd=useRowProperty, + ) class MeetingMinutes(WorkReportInstance): - # TODO: - # header (event) fields: title, description, from/to, - # location, participants (or put in description?) - # result set field for work items - # work item fields: title, description, party, deadline, state - type = "meeting_minutes" label = u'Meeting Minutes' rowFactory = TaskRow - fields = Jeep((tasks, taskTitle, taskDescription, workItems)) + fields = Jeep((eventTitle, eventStart, eventEnd, eventDescription, + tasks, taskTitle, taskDescription, workItems)) defaultOutputFields = fields states = ('planned', 'accepted', 'done', 'done_x', 'finished') def selectObjects(self, parts): - return self.getTasks(parts)[1:] + return [adapted(t) for t in self.getTasks(parts)[1:]]