diff --git a/browser/form.py b/browser/form.py index 1ebe424..4914583 100644 --- a/browser/form.py +++ b/browser/form.py @@ -45,6 +45,7 @@ from cybertools.composer.schema.grid.field import grid_macros from cybertools.composer.schema.interfaces import ISchemaFactory from cybertools.composer.schema.browser.common import schema_macros, schema_edit_macros from cybertools.composer.schema.schema import FormState +from cybertools.meta.interfaces import IOptions from cybertools.stateful.interfaces import IStateful from cybertools.typology.interfaces import IType, ITypeManager from cybertools.util.format import toUnicode @@ -739,6 +740,13 @@ class EditConcept(EditObject): return obj.getParentRelations(predicates=predicates, parent=concept) def assignConcept(self, obj, concept, predicate): + if IOptions(adapted(concept.conceptType)).children_append: + sibRelations = concept.getChildRelations() + if sibRelations: + maxOrder = max([r.order for r in sibRelations]) + if maxOrder > 0: + return obj.assignParent(concept, predicate, + order=maxOrder+1) obj.assignParent(concept, predicate) def deassignConcept(self, obj, concept, predicates): diff --git a/browser/loops.css b/browser/loops.css index 963101c..52b9a53 100644 --- a/browser/loops.css +++ b/browser/loops.css @@ -526,6 +526,12 @@ div.comment { background-color: #eeeeff; } +tr.agenda-item-headline td, +tr.agenda-item-headline td a, +tr.agenda-item-headline td a[href] { + color: white; +} + /* dojo stuff */ .dijitDialog { diff --git a/browser/loops.js b/browser/loops.js index aad4de6..f7310ec 100644 --- a/browser/loops.js +++ b/browser/loops.js @@ -125,22 +125,18 @@ function setConceptTypeForComboBox(typeId, cbId) { } var dialog; -var dialogName; function objectDialog(dlgName, url) { dojo.require('dijit.Dialog'); dojo.require('dojo.parser'); dojo.require('dijit.form.FilteringSelect'); dojo.require('dojox.data.QueryReadStore'); - if (dialogName == undefined || dialogName != dlgName || dialogName == '') { - if (dialog != undefined) { - dialog.destroyRecursive(); - } - dialogName = dlgName; - dialog = new dijit.Dialog({ - href: url - }, dojo.byId('dialog.' + dlgName)); + if (dialog != undefined) { + dialog.destroyRecursive(); } + dialog = new dijit.Dialog({ + href: url + }, dojo.byId('dialog.' + dlgName)); dialog.show(); } diff --git a/browser/skin/lobo/lobo.css b/browser/skin/lobo/lobo.css index 3ce5c26..ec06252 100644 --- a/browser/skin/lobo/lobo.css +++ b/browser/skin/lobo/lobo.css @@ -510,6 +510,12 @@ img.notselected { text-align: right; } +/* work */ + +.work_event { + background-color: #f3f3ff; +} + /* lobo layout-specific classes */ .legend { @@ -591,6 +597,10 @@ div.comment { /* calendar, work items */ +.MinutesAndAgendaTitles a[href] { + color: white; +} + .today { color: #444488; font-weight: bold; diff --git a/compound/book/browser.py b/compound/book/browser.py index 9117e95..d6e2232 100644 --- a/compound/book/browser.py +++ b/compound/book/browser.py @@ -50,6 +50,18 @@ class Base(object): for p in self.context.getParents([self.isPartOfPredicate]): return self.nodeView.getViewForTarget(p) + @Lazy + def tabview(self): + if self.editable: + return 'index.html' + + +class BookOverview(Base, ConceptView): + + @Lazy + def macro(self): + return book_template.macros['book'] + class SectionView(Base, ConceptView): @@ -58,17 +70,24 @@ class SectionView(Base, ConceptView): return book_template.macros['section'] @Lazy - def tabview(self): - if self.editable: - return 'index.html' + def documentTypeType(self): + return self.conceptManager['documenttype'] + + @Lazy + def sectionType(self): + return self.conceptManager['section'] def getCssClassForResource(self, r): - documentType = self.conceptManager['documenttype'] for c in r.context.getConcepts([self.defaultPredicate]): - if c.conceptType == documentType: + if c.conceptType == self.documentTypeType: return getName(c) return 'textelement' + def getParentsForResource(self, r): + for c in r.context.getConcepts([self.defaultPredicate]): + if c != self.context and c.conceptType != self.documentTypeType: + yield c + # layout parts - probably obsolete: diff --git a/compound/book/configure.zcml b/compound/book/configure.zcml index 98d0b40..9c5a628 100644 --- a/compound/book/configure.zcml +++ b/compound/book/configure.zcml @@ -17,6 +17,14 @@ + + + + +
+

+

+
+
+ + +
-
- - - - +
+
+
diff --git a/expert/browser/results.pt b/expert/browser/results.pt index 7d30c4f..2fd8129 100644 --- a/expert/browser/results.pt +++ b/expert/browser/results.pt @@ -68,9 +68,19 @@ + + + + + + + - diff --git a/expert/field.py b/expert/field.py index 3399de9..2379c4f 100644 --- a/expert/field.py +++ b/expert/field.py @@ -28,6 +28,7 @@ from zope.schema.interfaces import IVocabularyFactory, IContextSourceBinder from cybertools.composer.report.field import Field as BaseField from cybertools.composer.report.field import TableCellStyle from cybertools.composer.report.result import ResultSet +from cybertools.stateful.interfaces import IStateful, IStatesDefinition from cybertools.util.date import timeStamp2Date from loops.common import baseObject from loops.expert.report import ReportInstance @@ -115,6 +116,23 @@ class DateField(Field): return value.isoformat()[:10] +class StateField(Field): + + statesDefinition = 'workItemStates' + renderer = 'state' + + def getDisplayValue(self, row): + if IStateful.providedBy(row.context): + stf = row.context + else: + stf = component.getAdapter(row.context, IStateful, + name=self.statesDefinition) + stateObject = stf.getStateObject() + icon = stateObject.icon or 'led%s.png' % stateObject.color + return dict(title=util._(stateObject.title), + icon='cybertools.icons/' + icon) + + class VocabularyField(Field): vocabulary = None diff --git a/locales/de/LC_MESSAGES/loops.mo b/locales/de/LC_MESSAGES/loops.mo index 088068b..cafb12f 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 bd2aae2..9033888 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-07-21 12:00 CET\n" +"PO-Revision-Date: 2012-08-20 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -254,6 +254,18 @@ msgstr "Termin bearbeiten" msgid "Modify follow-up event." msgstr "Folgetermin bearbeiten" +msgid "Create Agenda Item..." +msgstr "Tagesordnungspunkt anlegen..." + +msgid "Create a new agenda item." +msgstr "Einen neuen Tagesordnungspunkt anlegen." + +msgid "Edit Agenda Item..." +msgstr "Tagesordnungspunkt bearbeiten..." + +msgid "Modify agenda item." +msgstr "Tagesordnungspunkt bearbeiten" + msgid "Create Task..." msgstr "Aufgabe anlegen..." @@ -275,6 +287,12 @@ msgstr "Eine neues Projekt anlegen." msgid "Create Work Item..." msgstr "Aktivität anlegen..." +msgid "Create a work item for this object." +msgstr "Eine Aktivität zu diesem Objekt anlegen." + +msgid "Add Work Item" +msgstr "Aktivität anlegen/bearbeiten" + msgid "Edit Video..." msgstr "Video bearbeiten..." @@ -296,6 +314,9 @@ msgstr "Besprechungsprotokoll für dieses Objekt anzeigen." msgid "Download Meeting Minutes" msgstr "Besprechungsprotokoll generieren" +msgid "Copy Agenda Items" +msgstr "Tagesordnungspunkte kopieren" + msgid "Participants" msgstr "Teilnehmer" @@ -303,7 +324,7 @@ msgid "The names of the persons taking part in the event." msgstr "Die Namen der Personen, die an der Besprechung teilnehmen." msgid "label_responsible" -msgstr "Vortragender" +msgstr "Vortragende/r" msgid "desc_responsible" msgstr "Person, die diesen Tagesordnungpunkt vertritt." @@ -315,10 +336,19 @@ msgid "desc_discussion" msgstr "Diskussion" msgid "label_consequences" -msgstr "Schlussfolgerungen" +msgstr "Schlussfolgerung" msgid "desc_consequences" -msgstr "Schlussfolgerungen" +msgstr "Schlussfolgerung" + +msgid "header_workitems" +msgstr "Aufgaben" + +msgid "header_responsible" +msgstr "zuständig" + +msgid "header_deadline" +msgstr "Termin" msgid "Task/Action" msgstr "Aufgabe" diff --git a/organize/browser/event.py b/organize/browser/event.py index ad9ff99..90f0f76 100644 --- a/organize/browser/event.py +++ b/organize/browser/event.py @@ -39,6 +39,7 @@ from loops.browser.node import NodeView from loops.common import adapted, baseObject from loops.concept import Concept from loops.organize.work.meeting import MeetingMinutes +from loops.organize.tracking.report import TrackDetails from loops.setup import addAndConfigureObject from loops.util import _ from loops import util @@ -79,6 +80,24 @@ actions.register('editFollowUpEvent', 'portlet', TargetAction, prerequisites=['registerDojoDateWidget'], ) +actions.register('createAgendaItem', 'portlet', DialogAction, + title=_(u'Create Agenda Item...'), + description=_(u'Create a new agenda item.'), + viewName='create_concept.html', + dialogName='createAgendaItem', + typeToken='.loops/concepts/agendaitem', + fixedType=True, + innerForm='inner_concept_form.html', + prerequisites=['registerDojoDateWidget'], +) + +actions.register('editAgendaItem', 'portlet', DialogAction, + title=_(u'Edit Agenda Item...'), + description=_(u'Modify agenda item.'), + viewName='edit_concept.html', + dialogName='editAgendaItem', +) + class Events(ConceptView): @@ -317,9 +336,21 @@ class CreateFollowUpEvent(CreateConcept, BaseFollowUpController): result = super(CreateFollowUpEvent, self).update() form = self.request.form toBeAssigned = form.get('cb_select_tasks') or [] - for uid in toBeAssigned: - task = util.getObjectForUid(uid) - self.createFollowUpTask(adapted(task)) + taskId = newTask = None + workItems = self.view.loopsRoot.getRecordManager()['work'] + for id in sorted(toBeAssigned): + if not '.' in id: + taskId = id + task = util.getObjectForUid(id) + newTask = self.createFollowUpTask(adapted(task)) + else: + tId, trackId = id.split('.') + if tId == taskId: + track = workItems.get(trackId) + if track is not None: + td = TrackDetails(self.view, track) + newTId = self.view.getUidForObject(newTask) + track.doAction('move', td.personId, task=newTId) return result def createFollowUpTask(self, source): @@ -333,12 +364,13 @@ class CreateFollowUpEvent(CreateConcept, BaseFollowUpController): conceptType=taskType, title=source.title, description=source.description, - responsible=source.start, + responsible=source.responsible, discussion=source.discussion, consequences=source.consequences) stask.assignChild(newTask, self.followsPredicate) for rel in stask.getParentRelations(): - if rel.predicate != self.view.typePredicate: + if rel.predicate not in ( + self.view.typePredicate, self.followsPredicate): if rel.first == bevt: parent = self.object else: diff --git a/organize/browser/view_macros.pt b/organize/browser/view_macros.pt index b3eb002..9eb86b9 100644 --- a/organize/browser/view_macros.pt +++ b/organize/browser/view_macros.pt @@ -58,13 +58,14 @@
+ tal:define="item nocall:item|nocall:view; + showState python:True">

-

Tasks

+
diff --git a/organize/work/meeting.pt b/organize/work/meeting.pt index 8361644..0fb82bb 100644 --- a/organize/work/meeting.pt +++ b/organize/work/meeting.pt @@ -4,7 +4,8 @@
+ results reportView/results; + showState python:True">
@@ -19,91 +20,189 @@
-
- - -
- - -
-

Meeting Minutes

-

-

-
- - - - - + fields results/context/fields; + showCheckboxes nothing; + showState nothing"> +
+
+ + + +
+
-
-
-
+ -
+ + + +
+
+
+
+ + + + + + + + + + - + + + + + + Participants + + + + + +
+ + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + reportView.getColumnRenderer(col)" /> + + + +
- Task/ActionWho?When?
+ +
- + tal:attributes="onclick + string:toggleCheckBoxes(this, '$cb_name:list')" /> + Copy Agenda Items +
      
+ +
+
+
+
+ + +
header_workitemsheader_responsibleheader_deadline +
- - -
-
+ -
+ - + + + - + + + + + + + + + + + + -
+ diff --git a/organize/work/meeting.py b/organize/work/meeting.py index 7aaa8a9..fae1cc7 100644 --- a/organize/work/meeting.py +++ b/organize/work/meeting.py @@ -66,10 +66,14 @@ class MeetingMinutes(ResultsConceptView): class MeetingMinutesDocument(WordDocument, MeetingMinutes): isToplevel = True + omitSectionElement = True def __init__(self, context, request): MeetingMinutes.__init__(self, context, request) + def __call__(self, *args, **kw): + return self.embed(*args, **kw) + @Lazy def macros(self): return meeting_template.macros diff --git a/organize/work/report.py b/organize/work/report.py index 2cd9628..1d15e8f 100644 --- a/organize/work/report.py +++ b/organize/work/report.py @@ -34,7 +34,7 @@ from cybertools.util.format import formatDate from cybertools.util.jeep import Jeep from loops.common import adapted, baseObject from loops.expert.browser.report import ReportConceptView -from loops.expert.field import Field, TargetField, DateField, \ +from loops.expert.field import Field, TargetField, DateField, StateField, \ TextField, UrlField from loops.expert.field import SubReport, SubReportField from loops.expert.report import ReportInstance @@ -50,13 +50,6 @@ class WorkStatementView(ReportConceptView): # fields -class StateField(Field): - - def getDisplayValue(self, row): - value = self.getValue(row) - return util._(value) - - class TrackDateField(Field): fieldType = 'date' @@ -66,7 +59,7 @@ class TrackDateField(Field): def getValue(self, row): value = self.getRawValue(row) - if value is None: + if not value: return None return timeStamp2Date(value) @@ -119,6 +112,10 @@ tasks = Field('tasks', u'Tasks', # work report fields +deadline = TrackDateField('deadline', u'Deadline', + description=u'The day the work has to be finished.', + cssClass='center', + executionSteps=['sort', 'output']) dayFrom = TrackDateField('dayFrom', u'Start Day', description=u'The first day from which to select work.', fieldType='date', @@ -161,6 +158,7 @@ effort = DurationField('effort', u'Effort', state = StateField('state', u'State', description=u'The state of the work.', cssClass='center', + statesDefinition='workItemStates', executionSteps=['query', 'output']) @@ -278,14 +276,17 @@ class WorkReportInstance(ReportInstance): class MeetingMinutesWorkRow(WorkRow): - pass + @Lazy + def isActive(self): + return self.context.state not in ( + 'finished', 'closed', 'cancelled', 'moved') class MeetingMinutesWork(WorkReportInstance, SubReport): rowFactory = MeetingMinutesWorkRow - fields = Jeep((workTitle, party, day, state)) #description, + fields = Jeep((workTitle, party, deadline, state)) #description, defaultOutputFields = fields defaultSortCriteria = (day,) states = ('planned', 'accepted', 'running', 'done', @@ -309,13 +310,20 @@ eventTitle = CalculatedField('eventTitle', u'Event Title', eventDescription = CalculatedField('eventDescription', u'Event Description', description=u'', executionSteps=(['header'])) +eventDate = DateField('eventDate', u'Event Date', + description=u'', + format=('date', 'short'), + executionSteps=(['header'])) eventStart = DateField('eventStart', u'Event Start', description=u'', - format=('dateTime', 'short'), + format=('time', 'short'), executionSteps=(['header'])) eventEnd = DateField('eventEnd', u'Event End', description=u'', - format=('dateTime', 'short'), + format=('time', 'short'), + executionSteps=(['header'])) +participants = CalculatedField('participants', u'Participants', + description=u'', executionSteps=(['header'])) taskTitle = UrlField('title', u'Task Title', description=u'The short description of the task.', @@ -325,6 +333,18 @@ taskDescription = TextField('description', u'Description', description=u'The long description of the task.', cssClass='header-2', executionSteps=['output']) +responsible = TextField('responsible', u'label_responsible', + description=u'Responsible.', + cssClass='header-2', + executionSteps=['output']) +discussion = TextField('discussion', u'label_discussion', + description=u'Discussion.', + cssClass='header-2', + executionSteps=['output']) +consequences = TextField('consequences', u'label_consequences', + description=u'Consequences.', + cssClass='header-2', + executionSteps=['output']) workItems = SubReportField('workItems', u'Work Items', description=u'A list of work items belonging to the task.', reportFactory=MeetingMinutesWork, @@ -345,6 +365,10 @@ class TaskRow(BaseRow): def eventDescription(self): return self.event.description + @Lazy + def eventDate(self): + return self.event.start + @Lazy def eventStart(self): return self.event.start @@ -353,8 +377,13 @@ class TaskRow(BaseRow): def eventEnd(self): return self.event.end + @Lazy + def participants(self): + return self.event.participants + useRowProperty = BaseRow.useRowProperty attributeHandlers = dict( + eventDate=useRowProperty, eventStart=useRowProperty, eventEnd=useRowProperty, ) @@ -367,8 +396,10 @@ class MeetingMinutes(WorkReportInstance): rowFactory = TaskRow - fields = Jeep((eventTitle, eventStart, eventEnd, eventDescription, - tasks, taskTitle, taskDescription, workItems)) + fields = Jeep((eventTitle, eventDate, eventStart, eventEnd, + eventDescription, participants, + tasks, taskTitle, responsible, taskDescription, + discussion, consequences, workItems)) defaultOutputFields = fields states = ('planned', 'accepted', 'done', 'done_x', 'finished') taskTypeNames = ('agendaitem',) diff --git a/organize/work/work_macros.pt b/organize/work/work_macros.pt index 0dde42f..0d67bbc 100644 --- a/organize/work/work_macros.pt +++ b/organize/work/work_macros.pt @@ -24,8 +24,8 @@ 2009-01 - + + xx_dojoType="dijit.form.Form" + tal:define="workItemTypes view/workItemTypes; + workItemType view/workItemType">
Add Work Item
+ + + + + + +
-
- -
- - - -
- -
- / -
+
+ +
+
+
+
+ +
+ + - +
+
+
+ +
+ / +
+
@@ -187,6 +229,10 @@ Task: + + Deadline: + + Start - End: @@ -219,7 +265,8 @@ tal:attributes="href python:view.getUrlForTarget(party)" tal:content="party/title" /> - + Moved To: